diff options
Diffstat (limited to 'core/Piwik.php')
-rw-r--r-- | core/Piwik.php | 2018 |
1 files changed, 1065 insertions, 953 deletions
diff --git a/core/Piwik.php b/core/Piwik.php index 67fd9a24c4..af7c5f083c 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -10,279 +10,69 @@ * @package Piwik */ +// no direct access +defined('PIWIK_INCLUDE_PATH') or die; + /** * @see core/Translate.php */ require_once PIWIK_INCLUDE_PATH . '/core/Translate.php'; /** - * @see mysqli_set_charset - * @see parse_ini_file - */ -require_once PIWIK_INCLUDE_PATH . '/libs/upgradephp/common.php'; - -/** * Main piwik helper class. * Contains static functions you can call from the plugins. - * + * * @package Piwik */ class Piwik { const CLASSES_PREFIX = "Piwik_"; - + public static $idPeriods = array( 'day' => 1, 'week' => 2, 'month' => 3, 'year' => 4, ); - -/* - * Prefix/unprefix class name - */ - - /** - * Prefix class name (if needed) - * - * @param string $class - * @return string - */ - static public function prefixClass( $class ) - { - if(substr_count($class, Piwik::CLASSES_PREFIX) > 0) - { - return $class; - } - return Piwik::CLASSES_PREFIX.$class; - } - - /** - * Unprefix class name (if needed) - * - * @param string $class - * @return string - */ - static public function unprefixClass( $class ) - { - $lenPrefix = strlen(Piwik::CLASSES_PREFIX); - if(substr($class, 0, $lenPrefix) == Piwik::CLASSES_PREFIX) - { - return substr($class, $lenPrefix); - } - return $class; - } - -/* - * Installation / Uninstallation - */ - - /** - * Installation helper - */ - static public function install() - { - Piwik_Common::mkdir(Zend_Registry::get('config')->smarty->compile_dir); - } - - /** - * Uninstallation helper - */ - static public function uninstall() - { - Piwik_Db_Schema::getInstance()->dropTables(); - } - - /** - * Returns true if Piwik is installed - * - * @since 0.6.3 - * - * @return bool True if installed; false otherwise - */ - static public function isInstalled() - { - return Piwik_Db_Schema::getInstance()->hasTables(); - } - -/* - * File and directory operations - */ - - /** - * Copy recursively from $source to $target. - * - * @param string $source eg. './tmp/latest' - * @param string $target eg. '.' - * @param bool $excludePhp - */ - static public function copyRecursive($source, $target, $excludePhp=false ) - { - if ( is_dir( $source ) ) - { - @mkdir( $target ); - $d = dir( $source ); - while ( false !== ( $entry = $d->read() ) ) - { - if ( $entry == '.' || $entry == '..' ) - { - continue; - } - - $sourcePath = $source . '/' . $entry; - if ( is_dir( $sourcePath ) ) - { - self::copyRecursive( $sourcePath, $target . '/' . $entry, $excludePhp ); - continue; - } - $destPath = $target . '/' . $entry; - self::copy($sourcePath, $destPath, $excludePhp); - } - $d->close(); - } - else - { - self::copy($source, $target, $excludePhp); - } - } - - /** - * Copy individual file from $source to $target. - * - * @param string $source eg. './tmp/latest/index.php' - * @param string $target eg. './index.php' - * @param bool $excludePhp - * @return bool - */ - static public function copy($source, $dest, $excludePhp=false) - { - static $phpExtensions = array('php', 'tpl'); - - if($excludePhp) - { - $path_parts = pathinfo($source); - if(in_array($path_parts['extension'], $phpExtensions)) - { - return true; - } - } - - if(!@copy( $source, $dest )) - { - @chmod($dest, 0755); - if(!@copy( $source, $dest )) - { - throw new Exception(" - Error while copying file to <code>$dest</code>. <br /> - Please check that the web server has enough permission to overwrite this file. <br /> - For example, on a linux server, if your apache user is www-data you can try to execute:<br /> - <code>chown -R www-data:www-data ".Piwik_Common::getPathToPiwikRoot()."</code><br /> - <code>chmod -R 0755 ".Piwik_Common::getPathToPiwikRoot()."</code><br /> - "); - } - } - return true; - } - - /** - * Recursively delete a directory - * - * @param string $dir Directory name - * @param boolean $deleteRootToo Delete specified top-level directory as well - */ - static public function unlinkRecursive($dir, $deleteRootToo) - { - if(!$dh = @opendir($dir)) - { - return; - } - while (false !== ($obj = readdir($dh))) - { - if($obj == '.' || $obj == '..') - { - continue; - } - - if (!@unlink($dir . '/' . $obj)) - { - self::unlinkRecursive($dir.'/'.$obj, true); - } - } - closedir($dh); - if ($deleteRootToo) - { - @rmdir($dir); - } - return; - } - - /** - * Recursively find pathnames that match a pattern - * @see glob() - * - * @param string $sDir directory - * @param string $sPattern pattern - * @param int $nFlags glob() flags - * @return array - */ - public static function globr($sDir, $sPattern, $nFlags = NULL) - { - if(($aFiles = glob("$sDir/$sPattern", $nFlags)) == false) - { - $aFiles = array(); - } - if(($aDirs = glob("$sDir/*", GLOB_ONLYDIR)) != false) - { - foreach ($aDirs as $sSubDir) - { - $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags); - $aFiles = array_merge($aFiles, $aSubFiles); - } - } - return $aFiles; - } - + /** * Checks that the directories Piwik needs write access are actually writable * Displays a nice error page if permissions are missing on some directories - * - * @param array $directoriesToCheck Array of directory names to check */ static public function checkDirectoriesWritableOrDie( $directoriesToCheck = null ) { $resultCheck = Piwik::checkDirectoriesWritable( $directoriesToCheck ); - if( array_search(false, $resultCheck) === false ) - { - return; - } - $directoryList = ''; - foreach($resultCheck as $dir => $bool) - { - $realpath = Piwik_Common::realpath($dir); - if(!empty($realpath) && $bool === false) + if( array_search(false, $resultCheck) !== false ) + { + $directoryList = ''; + foreach($resultCheck as $dir => $bool) { - $directoryList .= "<code>chmod 777 $realpath</code><br />"; + $realpath = Piwik_Common::realpath($dir); + if(!empty($realpath) && $bool === false) + { + $directoryList .= "<code>chmod 777 $realpath</code><br>"; + } } + $directoryList .= ''; + $directoryMessage = "<p><b>Piwik couldn't write to some directories</b>.</p> <p>Try to Execute the following commands on your Linux server:</P>"; + $directoryMessage .= $directoryList; + $directoryMessage .= "<p>If this doesn't work, you can try to create the directories with your FTP software, and set the CHMOD to 777 (with your FTP software, right click on the directories, permissions)."; + $directoryMessage .= "<p>After applying the modifications, you can <a href='index.php'>refresh the page</a>."; + $directoryMessage .= "<p>If you need more help, try <a href='misc/redirectToUrl.php?url=http://piwik.org'>Piwik.org</a>."; + + Piwik_ExitWithMessage($directoryMessage, false, true); } - $directoryList .= ''; - $directoryMessage = "<p><b>Piwik couldn't write to some directories</b>.</p> <p>Try to Execute the following commands on your Linux server:</P>"; - $directoryMessage .= $directoryList; - $directoryMessage .= "<p>If this doesn't work, you can try to create the directories with your FTP software, and set the CHMOD to 777 (with your FTP software, right click on the directories, permissions)."; - $directoryMessage .= "<p>After applying the modifications, you can <a href='index.php'>refresh the page</a>."; - $directoryMessage .= "<p>If you need more help, try <a href='misc/redirectToUrl.php?url=http://piwik.org'>Piwik.org</a>."; - - Piwik_ExitWithMessage($directoryMessage, false, true); } - + /** * Checks if directories are writable and create them if they do not exist. - * + * * @param array $directoriesToCheck array of directories to check - if not given default Piwik directories that needs write permission are checked * @return array direcory name => true|false (is writable) */ static public function checkDirectoriesWritable($directoriesToCheck = null) { - if( $directoriesToCheck == null ) + if( $directoriesToCheck == null ) { $directoriesToCheck = array( '/config', @@ -290,9 +80,9 @@ class Piwik '/tmp/templates_c', '/tmp/cache', '/tmp/latest', - ); + ); } - + $resultCheck = array(); foreach($directoriesToCheck as $directoryToCheck) { @@ -300,12 +90,12 @@ class Piwik { $directoryToCheck = PIWIK_USER_PATH . $directoryToCheck; } - + if(!file_exists($directoryToCheck)) { Piwik_Common::mkdir($directoryToCheck, 0755, false); } - + $directory = Piwik_Common::realpath($directoryToCheck); $resultCheck[$directory] = false; if($directory !== false // realpath() returns FALSE on failure @@ -316,167 +106,28 @@ class Piwik } return $resultCheck; } - + /** - * Generate .htaccess files at runtime to avoid permission problems. - */ - static public function createHtAccessFiles() - { - // deny access to these folders - $directoriesToProtect = array( - '/config', - '/core', - '/lang', - ); - foreach($directoriesToProtect as $directoryToProtect) - { - Piwik_Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect); - } - - // more selective allow/deny filters - $allowAny = "<Files \"*\">\nAllow from all\nSatisfy any\n</Files>\n"; - $allowStaticAssets = "<Files ~ \"\\.(test\.php|gif|ico|jpg|png|js|css|swf)$\">\nSatisfy any\nAllow from all\n</Files>\n"; - $denyDirectPhp = "<Files ~ \"\\.(php|php4|php5|inc|tpl)$\">\nDeny from all\n</Files>\n"; - $directoriesToProtect = array( - '/js' => $allowAny, - '/libs' => $denyDirectPhp . $allowStaticAssets, - '/plugins' => $denyDirectPhp . $allowStaticAssets, - '/themes' => $denyDirectPhp . $allowStaticAssets, - ); - foreach($directoriesToProtect as $directoryToProtect => $content) - { - Piwik_Common::createHtAccess(PIWIK_INCLUDE_PATH . $directoryToProtect, $content); - } - } - - /** - * Generate web.config files at runtime - * - * Note: for IIS 7 and above - */ - static public function createWebConfigFiles() - { - @file_put_contents(PIWIK_INCLUDE_PATH . '/web.config', -'<?xml version="1.0" encoding="UTF-8"?> -<configuration> - <system.webServer> - <security> - <requestFiltering> - <hiddenSegments> - <add segment="config" /> - <add segment="core" /> - <add segment="lang" /> - </hiddenSegments> - <fileExtensions> - <add fileExtension=".tpl" allowed="false" /> - </fileExtensions> - </requestFiltering> - </security> - <directoryBrowse enabled="false" /> - <defaultDocument> - <files> - <remove value="index.php" /> - <add value="index.php" /> - </files> - </defaultDocument> - </system.webServer> -</configuration>'); - - // deny direct access to .php files - $directoriesToProtect = array( - '/libs', - '/plugins', - ); - foreach($directoriesToProtect as $directoryToProtect) - { - @file_put_contents(PIWIK_INCLUDE_PATH . $directoryToProtect . '/web.config', -'<?xml version="1.0" encoding="UTF-8"?> -<configuration> - <system.webServer> - <security> - <requestFiltering> - <denyUrlSequences> - <add sequence=".php" /> - </denyUrlSequences> - </requestFiltering> - </security> - </system.webServer> -</configuration>'); - } - } - - /** - * Get file integrity information (in PIWIK_INCLUDE_PATH). + * Returns the Javascript code to be inserted on every page to track * - * @return array(bool, string, ...) Return code (true/false), followed by zero or more error messages + * @param int $idSite + * @param string $piwikUrl http://path/to/piwik/directory/ + * @param string $actionName + * @return string */ - static public function getFileIntegrityInformation() - { - $exclude = array( - 'robots.txt', - ); - $messages = array(); - $messages[] = true; - - // ignore dev environments - if(file_exists(PIWIK_INCLUDE_PATH . '/.svn')) - { - $messages[] = Piwik_Translate('General_WarningFileIntegritySkipped'); - return $messages; - } - - $manifest = PIWIK_INCLUDE_PATH . '/config/manifest.inc.php'; - if(!file_exists($manifest)) - { - $messages[] = Piwik_Translate('General_WarningFileIntegrityNoManifest'); - return $messages; - } - - require_once $manifest; - - $files = Manifest::$files; - - $hasMd5file = function_exists('md5_file'); - foreach($files as $path => $props) - { - if(in_array($path, $exclude)) - { - continue; - } - - $file = PIWIK_INCLUDE_PATH . '/' . $path; - - if(!file_exists($file)) - { - $messages[] = Piwik_Translate('General_ExceptionMissingFile', $file); - } - else if(filesize($file) != $props[0]) - { - $messages[] = Piwik_Translate('General_ExceptionFilesizeMismatch', array($file, $props[0], filesize($file))); - } - else if($hasMd5file && (@md5_file($file) !== $props[1])) - { - $messages[] = Piwik_Translate('General_ExceptionFileIntegrity', $file); - } - } - - if(count($messages) > 1) - { - $messages[0] = false; - } - - if(!$hasMd5file) - { - $messages[] = Piwik_Translate('General_WarningFileIntegrityNoMd5file'); - } - - return $messages; + static public function getJavascriptCode($idSite, $piwikUrl, $actionName = "''") + { + $jsTag = file_get_contents( PIWIK_INCLUDE_PATH . "/core/Tracker/javascriptTag.tpl"); + $jsTag = nl2br(htmlentities($jsTag)); + $piwikUrl = preg_match('~^(http|https)://(.*)$~', $piwikUrl, $matches); + $piwikUrl = $matches[2]; + $jsTag = str_replace('{$actionName}', $actionName, $jsTag); + $jsTag = str_replace('{$idSite}', $idSite, $jsTag); + $jsTag = str_replace('{$piwikUrl}', $piwikUrl, $jsTag); + $jsTag = str_replace('{$hrefTitle}', Piwik::getRandomTitle(), $jsTag); + return $jsTag; } -/* - * PHP environment settings - */ - /** * Set maximum script execution time. * @@ -489,46 +140,15 @@ class Piwik @set_time_limit($executionTime); } - /** - * Get php memory_limit - * - * Prior to PHP 5.2.1, or on Windows, --enable-memory-limit is not a - * compile-time default, so ini_get('memory_limit') may return false. - * - * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes - * @return int memory limit in megabytes - */ static public function getMemoryLimitValue() { if($memory = ini_get('memory_limit')) { - // handle shorthand byte options (case-insensitive) - $shorthandByteOption = substr($memory, -1); - switch($shorthandByteOption) - { - case 'G': - case 'g': - return substr($memory, 0, -1) * 1024; - case 'M': - case 'm': - return substr($memory, 0, -1); - case 'K': - case 'k': - return substr($memory, 0, -1) / 1024; - } - return $memory / 1048576; + return substr($memory, 0, strlen($memory) - 1); } return false; } - - /** - * Set PHP memory limit - * - * Note: system settings may prevent scripts from overriding the master value - * - * @param int $minimumMemoryLimit - * @return bool true if set; false otherwise - */ + static public function setMemoryLimit($minimumMemoryLimit) { $currentValue = self::getMemoryLimitValue(); @@ -540,12 +160,7 @@ class Piwik } return false; } - - /** - * Raise PHP memory limit if below the minimum required - * - * @return bool true if set; false otherwise - */ + static public function raiseMemoryLimitIfNecessary() { $minimumMemoryLimit = Zend_Registry::get('config')->General->minimum_memory_limit; @@ -555,25 +170,22 @@ class Piwik { return self::setMemoryLimit($minimumMemoryLimit); } - + return false; } - -/* - * Logging and error handling - */ - + static public function log($message = '') { Zend_Registry::get('logger_message')->logEvent($message); - Zend_Registry::get('logger_message')->logEvent( "<br />" . PHP_EOL); + Zend_Registry::get('logger_message')->logEvent( "<br>" . PHP_EOL); } - + + static public function error($message = '') { trigger_error($message, E_USER_ERROR); } - + /** * Display the message in a nice red font with a nice icon * ... and dies @@ -582,54 +194,46 @@ class Piwik { $output = "<style>a{color:red;}</style>\n". "<div style='color:red;font-family:Georgia;font-size:120%'>". - "<p><img src='themes/default/images/error_medium.png' style='vertical-align:middle; float:left;padding:20 20 20 20' />". + "<p><img src='themes/default/images/error_medium.png' style='vertical-align:middle; float:left;padding:20 20 20 20'>". $message. "</p></div>"; print(Piwik_Log_Formatter_ScreenFormatter::getFormattedString($output)); exit; } - -/* - * Profiling - */ - + /** - * Get total number of queries + * Computes the division of i1 by i2. If either i1 or i2 are not number, or if i2 has a value of zero + * we return 0 to avoid the division by zero. * - * @return int number of queries + * @param numeric $i1 + * @param numeric $i2 + * @return numeric The result of the division or zero */ + static public function secureDiv( $i1, $i2 ) + { + if ( is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0) + { + return $i1 / $i2; + } + return 0; + } static public function getQueryCount() { $profiler = Zend_Registry::get('db')->getProfiler(); return $profiler->getTotalNumQueries(); } - - /** - * Get total elapsed time (in seconds) - * - * @return int elapsed time - */ static public function getDbElapsedSecs() { $profiler = Zend_Registry::get('db')->getProfiler(); return $profiler->getTotalElapsedSecs(); } - - /** - * Print number of queries and elapsed time - */ static public function printQueryCount() { $totalTime = self::getDbElapsedSecs(); $queryCount = self::getQueryCount(); Piwik::log("Total queries = $queryCount (total sql time = ".round($totalTime,2)."s)"); } - - /** - * Print profiling report for the tracker - * - * @param Piwik_Tracker_Db $db Tracker database object (or null) - */ + static public function printSqlProfilingReportTracker( $db = null ) { if(!function_exists('maxSumMsFirst')) @@ -639,20 +243,20 @@ class Piwik return $a['sum_time_ms'] < $b['sum_time_ms']; } } - + if(is_null($db)) { $db = Piwik_Tracker::getDatabase(); } $tableName = Piwik_Common::prefixTable('log_profiling'); - + $all = $db->fetchAll('SELECT * FROM '.$tableName ); - if($all === false) + if($all === false) { return; } uasort($all, 'maxSumMsFirst'); - + $infoIndexedByQuery = array(); foreach($all as $infoQuery) { @@ -660,23 +264,24 @@ class Piwik $count = $infoQuery['count']; $sum_time_ms = $infoQuery['sum_time_ms']; $infoIndexedByQuery[$query] = array('count' => $count, 'sumTimeMs' => $sum_time_ms); - } + } Piwik::getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery); } /** - * Outputs SQL Profiling reports + * Outputs SQL Profiling reports * It is automatically called when enabling the SQL profiling in the config file enable_sql_profiler + * */ static function printSqlProfilingReportZend() { $profiler = Zend_Registry::get('db')->getProfiler(); - + if(!$profiler->getEnabled()) { throw new Exception("To display the profiler you should enable enable_sql_profiler on your config/config.ini.php file"); } - + $infoIndexedByQuery = array(); foreach($profiler->getQueryProfiles() as $query) { @@ -701,9 +306,9 @@ class Piwik } } uasort( $infoIndexedByQuery, 'sortTimeDesc'); - - Piwik::log('<hr /><b>SQL Profiler</b>'); - Piwik::log('<hr /><b>Summary</b>'); + + Piwik::log('<hr><b>SQL Profiler</b>'); + Piwik::log('<hr><b>Summary</b>'); $totalTime = $profiler->getTotalElapsedSecs(); $queryCount = $profiler->getTotalNumQueries(); $longestTime = 0; @@ -716,63 +321,44 @@ class Piwik } $str = 'Executed ' . $queryCount . ' queries in ' . round($totalTime,3) . ' seconds' . "\n"; $str .= '(Average query length: ' . round($totalTime / $queryCount,3) . ' seconds)' . "\n"; - $str .= '<br />Queries per second: ' . round($queryCount / $totalTime,1) . "\n"; - $str .= '<br />Longest query length: ' . round($longestTime,3) . " seconds (<code>$longestQuery</code>) \n"; + $str .= '<br>Queries per second: ' . round($queryCount / $totalTime,1) . "\n"; + $str .= '<br>Longest query length: ' . round($longestTime,3) . " seconds (<code>$longestQuery</code>) \n"; Piwik::log($str); Piwik::getSqlProfilingQueryBreakdownOutput($infoIndexedByQuery); } - - /** - * Log a breakdown by query - * - * @param array $infoIndexedByQuery - */ + static private function getSqlProfilingQueryBreakdownOutput( $infoIndexedByQuery ) { - Piwik::log('<hr /><b>Breakdown by query</b>'); + Piwik::log('<hr><b>Breakdown by query</b>'); $output = ''; - foreach($infoIndexedByQuery as $query => $queryInfo) + foreach($infoIndexedByQuery as $query => $queryInfo) { $timeMs = round($queryInfo['sumTimeMs'],1); $count = $queryInfo['count']; $avgTimeString = ''; - if($count > 1) + if($count > 1) { $avgTimeMs = $timeMs / $count; - $avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)"; + $avgTimeString = " (average = <b>". round($avgTimeMs,1) . "ms</b>)"; } $query = preg_replace('/([\t\n\r ]+)/', ' ', $query); $output .= "Executed <b>$count</b> time". ($count==1?'':'s') ." in <b>".$timeMs."ms</b> $avgTimeString <pre>\t$query</pre>"; } Piwik::log($output); } - - /** - * Print timer - */ + static public function printTimer() { echo Zend_Registry::get('timer'); } - /** - * Print memory leak - * - * @param string $prefix - * @param string $suffix - */ - static public function printMemoryLeak($prefix = '', $suffix = '<br />') + static public function printMemoryLeak($prefix = '', $suffix = '<br>') { echo $prefix; echo Zend_Registry::get('timer')->getMemoryLeak(); echo $suffix; } - - /** - * Print memory usage - * - * @param string $prefixString - */ + static public function printMemoryUsage( $prefixString = null ) { $memory = false; @@ -784,7 +370,7 @@ class Piwik { $memory = memory_get_usage(); } - + if($memory !== false) { $usage = round( $memory / 1024 / 1024, 2); @@ -799,220 +385,385 @@ class Piwik Piwik::log("Memory usage function not found."); } } - -/* - * Amounts, Percentages, Currency, Time, Math Operations, and Pretty Printing - */ - - /** - * Computes the division of i1 by i2. If either i1 or i2 are not number, or if i2 has a value of zero - * we return 0 to avoid the division by zero. - * - * @param numeric $i1 - * @param numeric $i2 - * @return numeric The result of the division or zero - */ - static public function secureDiv( $i1, $i2 ) + + static public function getPrettySizeFromBytes($size) { - if ( is_numeric($i1) && is_numeric($i2) && floatval($i2) != 0) + $bytes = array('','K','M','G','T'); + foreach($bytes as $val) { - return $i1 / $i2; + if($size > 1024) + { + $size = $size / 1024; + } + else + { + break; + } } - return 0; + return round($size, 1)." ".$val; } /** - * Safely compute a percentage. Return 0 to avoid division by zero. + * Returns true if PHP was invoked as CGI or command-line interface (shell) * - * @param numeric $dividend - * @param numeric $divisor - * @param int $precision - * @return numeric + * @deprecated deprecated in 0.4.4 + * @see Piwik_Common::isPhpCliMode() + * @return bool true if PHP invoked as a CGI or from CLI */ - static public function getPercentageSafe($dividend, $divisor, $precision = 0) + static public function isPhpCliMode() { - if($divisor == 0) - { - return 0; - } - return round(100 * $dividend / $divisor, $precision); + return Piwik_Common::isPhpCliMode(); } - - /** - * Get currency symbol for a site - * - * @param int $idSite - * @return string - */ - static public function getCurrency($idSite) + + static public function getCurrency() { - static $symbols = null; - if(is_null($symbols)) + static $symbol = null; + if(is_null($symbol)) { - $symbols = Piwik_SitesManager_API::getInstance()->getCurrencySymbols(); + $symbol = trim(Zend_Registry::get('config')->General->default_currency); } - $site = new Piwik_Site($idSite); - return $symbols[$site->getCurrency()]; + return $symbol; } - /** - * Pretty format monetary value for a site - * - * @param numeric $value - * @param int $idSite - * @return string - */ - static public function getPrettyMoney($value, $idSite) + static public function getPrettyMoney($value) { - $currencyBefore = self::getCurrency($idSite); - $currencyAfter = ''; - - // manually put the currency symbol after the amount for euro - // (maybe more currencies prefer this notation?) - if(in_array($currencyBefore,array('€'))) - { - $currencyAfter = ' '.$currencyBefore; - $currencyBefore = ''; - } - return sprintf("$currencyBefore %s$currencyAfter", $value); + $symbol = self::getCurrency(); + return sprintf("$symbol%.2f", $value); } - - /** - * Pretty format a memory size value - * - * @param numeric $size in bytes - * @return string - */ - static public function getPrettySizeFromBytes($size) + + static public function getPercentageSafe($dividend, $divisor, $precision = 0) { - $bytes = array('','K','M','G','T'); - foreach($bytes as $val) + if($divisor == 0) { - if($size > 1024) - { - $size = $size / 1024; - } - else - { - break; - } + return 0; } - return round($size, 1)." ".$val; + return round(100 * $dividend / $divisor, $precision); } - - /** - * Pretty format a time - * - * @param numeric $numberOfSeconds - * @return string - */ + static public function getPrettyTimeFromSeconds($numberOfSeconds) { $numberOfSeconds = (double)$numberOfSeconds; $days = floor($numberOfSeconds / 86400); - + $minusDays = $numberOfSeconds - $days * 86400; $hours = floor($minusDays / 3600); - + $minusDaysAndHours = $minusDays - $hours * 3600; $minutes = floor($minusDaysAndHours / 60 ); - + $seconds = $minusDaysAndHours - $minutes * 60; - + if($days > 0) { - $return = sprintf(Piwik_Translate('General_DaysHours'), $days, $hours); + return sprintf("%d days %d hours", $days, $hours); } elseif($hours > 0) { - $return = sprintf(Piwik_Translate('General_HoursMinutes'), $hours, $minutes); + return sprintf("%d hours %d min", $hours, $minutes); } elseif($minutes > 0) { - $return = sprintf(Piwik_Translate('General_MinutesSeconds'), $minutes, $seconds); + return sprintf("%d min %ds", $minutes, $seconds); } else { - $return = sprintf(Piwik_Translate('General_Seconds'), $seconds); + return sprintf("%ds", $seconds); } - return str_replace(' ', ' ', $return); - } - - /** - * Returns the Javascript code to be inserted on every page to track - * - * @param int $idSite - * @param string $piwikUrl http://path/to/piwik/directory/ - * @param string $actionName - * @return string - */ - static public function getJavascriptCode($idSite, $piwikUrl, $actionName = "''") - { - $jsTag = file_get_contents( PIWIK_INCLUDE_PATH . "/core/Tracker/javascriptTag.tpl"); - $jsTag = nl2br(htmlentities($jsTag)); - $piwikUrl = preg_match('~^(http|https)://(.*)$~', $piwikUrl, $matches); - $piwikUrl = $matches[2]; - $jsTag = str_replace('{$actionName}', $actionName, $jsTag); - $jsTag = str_replace('{$idSite}', $idSite, $jsTag); - $jsTag = str_replace('{$piwikUrl}', $piwikUrl, $jsTag); - $jsTag = str_replace('{$hrefTitle}', Piwik::getRandomTitle(), $jsTag); - return $jsTag; } - - /** - * Generate a title for image tags - * - * @return string - */ + static public function getRandomTitle() { - static $titles = array( - 'Web analytics', - 'Analytics', - 'Real time web analytics', - 'Real time analytics', - 'Open source analytics', - 'Open source web analytics', - 'Google Analytics alternative', - 'Open source Google Analytics', - 'Free analytics', - 'Analytics software', - 'Free web analytics', - 'Free web statistics', - ); - $id = abs(intval(md5(Piwik_Url::getCurrentHost()))); + $titles = array( 'Web analytics', + 'Analytics', + 'Web analytics api', + 'Open source analytics', + 'Open source web analytics', + 'Google Analytics alternative', + 'open source Google Analytics', + 'Free analytics', + 'Analytics software', + 'Free web analytics', + 'Free web statistics', + 'Web 2.0 analytics', + 'Statistics web 2.0', + ); + $id = abs(intval(md5(substr(Piwik_Url::getCurrentHost(),7)))); $title = $titles[ $id % count($titles)]; return $title; } - -/* - * Access - */ - - /** - * Get current user login - * - * @return string - */ + + static public function getTableCreateSql( $tableName ) + { + $tables = Piwik::getTablesCreateSql(); + + if(!isset($tables[$tableName])) + { + throw new Exception("The table '$tableName' SQL creation code couldn't be found."); + } + + return $tables[$tableName]; + } + + static public function getTablesCreateSql() + { + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $tables = array( + 'user' => "CREATE TABLE {$prefixTables}user ( + login VARCHAR(100) NOT NULL, + password CHAR(32) NOT NULL, + alias VARCHAR(45) NOT NULL, + email VARCHAR(100) NOT NULL, + token_auth CHAR(32) NOT NULL, + date_registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY(login), + UNIQUE INDEX uniq_keytoken(token_auth) + ) DEFAULT CHARSET=utf8 + ", + + 'access' => "CREATE TABLE {$prefixTables}access ( + login VARCHAR(100) NOT NULL, + idsite INTEGER UNSIGNED NOT NULL, + access VARCHAR(10) NULL, + PRIMARY KEY(login, idsite) + ) DEFAULT CHARSET=utf8 + ", + + 'site' => "CREATE TABLE {$prefixTables}site ( + idsite INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(90) NOT NULL, + main_url VARCHAR(255) NOT NULL, + ts_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + PRIMARY KEY(idsite) + ) DEFAULT CHARSET=utf8 + ", + + 'site_url' => "CREATE TABLE {$prefixTables}site_url ( + idsite INTEGER(10) UNSIGNED NOT NULL, + url VARCHAR(255) NOT NULL, + PRIMARY KEY(idsite, url) + ) DEFAULT CHARSET=utf8 + ", + + 'goal' => " CREATE TABLE `{$prefixTables}goal` ( + `idsite` int(11) NOT NULL, + `idgoal` int(11) NOT NULL, + `name` varchar(50) NOT NULL, + `match_attribute` varchar(20) NOT NULL, + `pattern` varchar(255) NOT NULL, + `pattern_type` varchar(10) NOT NULL, + `case_sensitive` tinyint(4) NOT NULL, + `revenue` float NOT NULL, + `deleted` tinyint(4) NOT NULL default '0', + PRIMARY KEY (`idsite`,`idgoal`) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_message' => "CREATE TABLE {$prefixTables}logger_message ( + idlogger_message INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + PRIMARY KEY(idlogger_message) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_api_call' => "CREATE TABLE {$prefixTables}logger_api_call ( + idlogger_api_call INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + class_name VARCHAR(255) NULL, + method_name VARCHAR(255) NULL, + parameter_names_default_values TEXT NULL, + parameter_values TEXT NULL, + execution_time FLOAT NULL, + caller_ip BIGINT UNSIGNED NULL, + timestamp TIMESTAMP NULL, + returned_value TEXT NULL, + PRIMARY KEY(idlogger_api_call) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_error' => "CREATE TABLE {$prefixTables}logger_error ( + idlogger_error INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + errno INTEGER UNSIGNED NULL, + errline INTEGER UNSIGNED NULL, + errfile VARCHAR(255) NULL, + backtrace TEXT NULL, + PRIMARY KEY(idlogger_error) + ) DEFAULT CHARSET=utf8 + ", + + 'logger_exception' => "CREATE TABLE {$prefixTables}logger_exception ( + idlogger_exception INTEGER UNSIGNED NOT NULL AUTO_INCREMENT, + timestamp TIMESTAMP NULL, + message TEXT NULL, + errno INTEGER UNSIGNED NULL, + errline INTEGER UNSIGNED NULL, + errfile VARCHAR(255) NULL, + backtrace TEXT NULL, + PRIMARY KEY(idlogger_exception) + ) DEFAULT CHARSET=utf8 + ", + + + 'log_action' => "CREATE TABLE {$prefixTables}log_action ( + idaction INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + hash INTEGER(10) UNSIGNED NOT NULL, + type TINYINT UNSIGNED NULL, + PRIMARY KEY(idaction), + INDEX index_type_hash (type, hash) + ) DEFAULT CHARSET=utf8 + ", + + 'log_visit' => "CREATE TABLE {$prefixTables}log_visit ( + idvisit INTEGER(10) UNSIGNED NOT NULL AUTO_INCREMENT, + idsite INTEGER(10) UNSIGNED NOT NULL, + visitor_localtime TIME NOT NULL, + visitor_idcookie CHAR(32) NOT NULL, + visitor_returning TINYINT(1) NOT NULL, + visit_first_action_time DATETIME NOT NULL, + visit_last_action_time DATETIME NOT NULL, + visit_server_date DATE NOT NULL, + visit_exit_idaction_url INTEGER(11) NOT NULL, + visit_entry_idaction_url INTEGER(11) NOT NULL, + visit_total_actions SMALLINT(5) UNSIGNED NOT NULL, + visit_total_time SMALLINT(5) UNSIGNED NOT NULL, + visit_goal_converted TINYINT(1) NOT NULL, + referer_type INTEGER UNSIGNED NULL, + referer_name VARCHAR(70) NULL, + referer_url TEXT NOT NULL, + referer_keyword VARCHAR(255) NULL, + config_md5config CHAR(32) NOT NULL, + config_os CHAR(3) NOT NULL, + config_browser_name VARCHAR(10) NOT NULL, + config_browser_version VARCHAR(20) NOT NULL, + config_resolution VARCHAR(9) NOT NULL, + config_pdf TINYINT(1) NOT NULL, + config_flash TINYINT(1) NOT NULL, + config_java TINYINT(1) NOT NULL, + config_director TINYINT(1) NOT NULL, + config_quicktime TINYINT(1) NOT NULL, + config_realplayer TINYINT(1) NOT NULL, + config_windowsmedia TINYINT(1) NOT NULL, + config_gears TINYINT(1) NOT NULL, + config_silverlight TINYINT(1) NOT NULL, + config_cookie TINYINT(1) NOT NULL, + location_ip BIGINT UNSIGNED NOT NULL, + location_browser_lang VARCHAR(20) NOT NULL, + location_country CHAR(3) NOT NULL, + location_continent CHAR(3) NOT NULL, + PRIMARY KEY(idvisit), + INDEX index_idsite_date (idsite, visit_server_date) + ) DEFAULT CHARSET=utf8 + ", + + 'log_conversion' => "CREATE TABLE `{$prefixTables}log_conversion` ( + `idvisit` int(10) unsigned NOT NULL, + `idsite` int(10) unsigned NOT NULL, + `visitor_idcookie` char(32) NOT NULL, + `server_time` datetime NOT NULL, + `visit_server_date` date NOT NULL, + `idaction_url` int(11) default NULL, + `idlink_va` int(11) default NULL, + `referer_idvisit` int(10) unsigned default NULL, + `referer_visit_server_date` date default NULL, + `referer_type` int(10) unsigned default NULL, + `referer_name` varchar(70) default NULL, + `referer_keyword` varchar(255) default NULL, + `visitor_returning` tinyint(1) NOT NULL, + `location_country` char(3) NOT NULL, + `location_continent` char(3) NOT NULL, + `url` text NOT NULL, + `idgoal` int(10) unsigned NOT NULL, + `revenue` float default NULL, + PRIMARY KEY (`idvisit`,`idgoal`), + KEY `index_idsite_date` (`idsite`,`visit_server_date`) + ) DEFAULT CHARSET=utf8 + ", + + 'log_link_visit_action' => "CREATE TABLE {$prefixTables}log_link_visit_action ( + idlink_va INTEGER(11) NOT NULL AUTO_INCREMENT, + idvisit INTEGER(10) UNSIGNED NOT NULL, + idaction_url INTEGER(10) UNSIGNED NOT NULL, + idaction_url_ref INTEGER(10) UNSIGNED NOT NULL, + idaction_name INTEGER(10) UNSIGNED, + time_spent_ref_action INTEGER(10) UNSIGNED NOT NULL, + PRIMARY KEY(idlink_va), + INDEX index_idvisit(idvisit) + ) DEFAULT CHARSET=utf8 + ", + + 'log_profiling' => "CREATE TABLE {$prefixTables}log_profiling ( + query TEXT NOT NULL, + count INTEGER UNSIGNED NULL, + sum_time_ms FLOAT NULL, + UNIQUE INDEX query(query(100)) + ) DEFAULT CHARSET=utf8 + ", + + 'option' => "CREATE TABLE `{$prefixTables}option` ( + option_name VARCHAR( 64 ) NOT NULL , + option_value LONGTEXT NOT NULL , + autoload TINYINT NOT NULL DEFAULT '1', + PRIMARY KEY ( option_name ) + ) DEFAULT CHARSET=utf8 + ", + + 'archive_numeric' => "CREATE TABLE {$prefixTables}archive_numeric ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value FLOAT NULL, + PRIMARY KEY(idarchive, name), + KEY `index_all` (`idsite`,`date1`,`date2`,`name`,`ts_archived`) + ) DEFAULT CHARSET=utf8 + ", + 'archive_blob' => "CREATE TABLE {$prefixTables}archive_blob ( + idarchive INTEGER UNSIGNED NOT NULL, + name VARCHAR(255) NOT NULL, + idsite INTEGER UNSIGNED NULL, + date1 DATE NULL, + date2 DATE NULL, + period TINYINT UNSIGNED NULL, + ts_archived DATETIME NULL, + value MEDIUMBLOB NULL, + PRIMARY KEY(idarchive, name), + KEY `index_all` (`idsite`,`date1`,`date2`,`name`,`ts_archived`) + ) DEFAULT CHARSET=utf8 + ", + ); + return $tables; + } + static public function getCurrentUserLogin() { return Zend_Registry::get('access')->getLogin(); } - + + static public function getCurrentUserTokenAuth() + { + return Zend_Registry::get('access')->getTokenAuth(); + } + /** - * Get current user's token auth + * Returns the plugin currently being used to display the page * - * @return string + * @return Piwik_Plugin */ - static public function getCurrentUserTokenAuth() + static public function getCurrentPlugin() { - return Zend_Registry::get('access')->getTokenAuth(); + return Piwik_PluginsManager::getInstance()->getLoadedPlugin(Piwik::getModule()); } - + /** * Returns true if the current user is either the super user, or the user $theUser * Used when modifying user preference: this usually requires super user or being the user itself. - * + * * @param string $theUser * @return bool */ @@ -1025,10 +776,8 @@ class Piwik return false; } } - + /** - * Check that current user is either the specified user or the superuser - * * @param string $theUser * @throws exception if the user is neither the super user nor the user $theUser */ @@ -1044,10 +793,9 @@ class Piwik throw new Piwik_Access_NoAccessException("The user has to be either the Super User or the user '$theUser' itself."); } } - + /** * Returns true if the current user is the Super User - * * @return bool */ static public function isUserIsSuperUser() @@ -1059,32 +807,21 @@ class Piwik return false; } } - + /** * Helper method user to set the current as Super User. * This should be used with great care as this gives the user all permissions. */ - static public function setUserIsSuperUser( $bool = true ) + static public function setUserIsSuperUser() { - Zend_Registry::get('access')->setSuperUser($bool); + Zend_Registry::get('access')->setSuperUser(); } - - /** - * Check that user is the superuser - * - * @throws Exception if not the superuser - */ + static public function checkUserIsSuperUser() { Zend_Registry::get('access')->checkUserIsSuperUser(); } - - /** - * Returns true if the user has admin access to the sites - * - * @param mixed $idSites - * @return bool - */ + static public function isUserHasAdminAccess( $idSites ) { try{ @@ -1094,23 +831,12 @@ class Piwik return false; } } - - /** - * Check user has admin access to the sites - * - * @param mixed $idSites - * @throws Exception if user doesn't have admin access to the sites - */ + static public function checkUserHasAdminAccess( $idSites ) { Zend_Registry::get('access')->checkUserHasAdminAccess( $idSites ); } - - /** - * Returns true if the user has admin access to any sites - * - * @return bool - */ + static public function isUserHasSomeAdminAccess() { try{ @@ -1120,23 +846,17 @@ class Piwik return false; } } - - /** - * Check user has admin access to any sites - * - * @throws Exception if user doesn't have admin access to any sites - */ + static public function checkUserHasSomeAdminAccess() { Zend_Registry::get('access')->checkUserHasSomeAdminAccess(); } - - /** - * Returns true if the user has view access to the sites - * - * @param mixed $idSites - * @return bool - */ + + static public function checkUserHasSomeViewAccess() + { + Zend_Registry::get('access')->checkUserHasSomeViewAccess(); + } + static public function isUserHasViewAccess( $idSites ) { try{ @@ -1146,140 +866,636 @@ class Piwik return false; } } + + static public function checkUserHasViewAccess( $idSites ) + { + Zend_Registry::get('access')->checkUserHasViewAccess( $idSites ); + } + + static public function prefixClass( $class ) + { + if(substr_count($class, Piwik::CLASSES_PREFIX) > 0) + { + return $class; + } + return Piwik::CLASSES_PREFIX.$class; + } + static public function unprefixClass( $class ) + { + $lenPrefix = strlen(Piwik::CLASSES_PREFIX); + if(substr($class, 0, $lenPrefix) == Piwik::CLASSES_PREFIX) + { + return substr($class, $lenPrefix); + } + return $class; + } /** - * Check user has view access to the sites + * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.) * - * @param mixed $idSites - * @throws Exception if user doesn't have view access to sites + * @return string */ - static public function checkUserHasViewAccess( $idSites ) + static public function getModule() { - Zend_Registry::get('access')->checkUserHasViewAccess( $idSites ); + return Piwik_Common::getRequestVar('module', '', 'string'); } - + /** - * Returns true if the user has view access to any sites + * Returns the current action read from the URL * - * @return bool + * @return string */ - static public function isUserHasSomeViewAccess() + static public function getAction() { - try{ - self::checkUserHasSomeViewAccess(); - return true; - } catch( Exception $e){ - return false; + return Piwik_Common::getRequestVar('action', '', 'string'); + } + + /** + * returns false if the URL to redirect to is already this URL + */ + static public function redirectToModule( $newModule, $newAction = '' ) + { + $currentModule = self::getModule(); + $currentAction = self::getAction(); + + if($currentModule != $newModule + || $currentAction != $newAction ) + { + + $newUrl = 'index.php' . Piwik_Url::getCurrentQueryStringWithParametersModified( + array('module' => $newModule, 'action' => $newAction) + ); + + Piwik_Url::redirectToUrl($newUrl); } + return false; } /** - * Check user has view access to any sites - * - * @throws Exception if user doesn't have view access to any sites + * Get "best" available transport method for sendHttpRequest() calls. */ - static public function checkUserHasSomeViewAccess() + static public function getTransportMethod() { - Zend_Registry::get('access')->checkUserHasSomeViewAccess(); + $method = 'curl'; + if(!extension_loaded('curl')) + { + $method = 'stream'; + if(@ini_get('allow_url_fopen') != '1') + { + $method = 'socket'; + if(preg_match('/(^|,|\s)fsockopen($|,|\s)/', @ini_get('disable_functions'))) + { + return null; + } + } + } + return $method; } -/* - * Current module, action, plugin - */ - /** - * Returns the name of the Login plugin currently being used. - * Must be used since it is not allowed to hardcode 'Login' in URLs - * in case another Login plugin is being used. + * Sends http request ensuring the request will fail before $timeout seconds * - * @return string + * If no $destinationPath is specified, the trimmed response (without header) is returned as a string. + * If a $destinationPath is specified, the response (without header) is saved to a file. + * + * @param string $aUrl + * @param int $timeout + * @param string $userAgent + * @param string $destinationPath + * @param int $followDepth + * @return true (or string) on success; false on HTTP response error code (1xx or 4xx); throws exception on all other errors */ - static public function getLoginPluginName() + static public function sendHttpRequest($aUrl, $timeout, $userAgent = null, $destinationPath = null, $followDepth = 0) { - return Zend_Registry::get('auth')->getName(); + // create output file + $file = null; + if($destinationPath) + { + if (($file = @fopen($destinationPath, 'wb')) === false || !is_resource($file)) + { + throw new Exception('Error while creating the file: ' . $destinationPath); + } + } + + return self::sendHttpRequestBy(self::getTransportMethod(), $aUrl, $timeout, $userAgent, $destinationPath, $file, $followDepth); + } + + static public function sendHttpRequestBy($method = 'socket', $aUrl, $timeout, $userAgent = null, $destinationPath = null, $file = null, $followDepth = 0) + { + if ($followDepth > 3) + { + throw new Exception('Too many redirects ('.$followDepth.')'); + } + + $contentLength = 0; + + if($method == 'socket') + { + // initialization + $url = @parse_url($aUrl); + if($url === false || !isset($url['scheme'])) + { + throw new Exception('Malformed URL: '.$aUrl); + } + + if($url['scheme'] != 'http') + { + throw new Exception('Invalid protocol/scheme: '.$url['scheme']); + } + $host = $url['host']; + $port = isset($url['port)']) ? $url['port'] : 80; + $path = isset($url['path']) ? $url['path'] : '/'; + if(isset($url['query'])) + { + $path .= '?'.$url['query']; + } + $errno = null; + $errstr = null; + + // connection attempt + if (($fsock = @fsockopen($host, $port, $errno, $errstr, $timeout)) === false || !is_resource($fsock)) + { + if(is_resource($file)) { @fclose($file); } + throw new Exception("Error while connecting to: $host. Please try again later. $errstr"); + } + + // send HTTP request header + fwrite($fsock, + "GET $path HTTP/1.0\r\n" + ."Host: $host".($port != 80 ? ':'.$port : '')."\r\n" + ."User-Agent: Piwik/".Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n" + .'Referer: http://'.Piwik_Common::getIpString()."/\r\n" + ."Connection: close\r\n" + ."\r\n" + ); + + $streamMetaData = array('timed_out' => false); + @stream_set_blocking($fsock, true); + @stream_set_timeout($fsock, $timeout); + + // process header + $status = null; + $expectRedirect = false; + $fileLength = 0; + + while (!feof($fsock)) + { + $line = fgets($fsock, 4096); + + $streamMetaData = @stream_get_meta_data($fsock); + if($streamMetaData['timed_out']) + { + if(is_resource($file)) { @fclose($file); } + @fclose($fsock); + throw new Exception('Timed out waiting for server response'); + } + + // a blank line marks the end of the server response header + if(rtrim($line, "\r\n") == '') + { + break; + } + + // parse first line of server response header + if(!$status) + { + // expect first line to be HTTP response status line, e.g., HTTP/1.1 200 OK + if(!preg_match('~^HTTP/(\d\.\d)\s+(\d+)(\s*.*)?~', $line, $m)) + { + if(is_resource($file)) { @fclose($file); } + @fclose($fsock); + throw new Exception('Expected server response code. Got '.rtrim($line, "\r\n")); + } + + $status = (integer) $m[2]; + + // Informational 1xx or Client Error 4xx + if ($status < 200 || $status >= 400) + { + if(is_resource($file)) { @fclose($file); } + @fclose($s); + return false; + } + + continue; + } + + // handle redirect + if(preg_match('/^Location:\s*(.+)/', rtrim($line, "\r\n"), $m)) + { + if(is_resource($file)) { @fclose($file); } + @fclose($s); + // Successful 2xx vs Redirect 3xx + if($status < 300) + { + throw new Exception('Unexpected redirect to Location: '.rtrim($line).' for status code '.$status); + } + return self::sendHttpRequest(trim($m[1]), $pathDestination, $tries+1); + } + + // save expected content length for later verification + if(preg_match('/^Content-Length:\s*(\d+)/', $line, $m)) + { + $contentLength = (integer) $m[1]; + } + } + + if(feof($fsock)) + { + throw new Exception('Unexpected end of transmission'); + } + + // process content/body + $response = ''; + + while (!feof($fsock)) + { + $line = fread($fsock, 8192); + + $streamMetaData = @stream_get_meta_data($fsock); + if($streamMetaData['timed_out']) + { + if(is_resource($file)) { @fclose($file); } + @fclose($fsock); + throw new Exception('Timed out waiting for server response'); + } + + if(is_resource($file)) + { + // save to file + $fileLength += fwrite($file, $line); + } + else + { + // concatenate to response string + $response .= $line; + } + } + + // determine success or failure + @fclose(@$fsock); + } + else if($method == 'stream') + { + $response = false; + + // we make sure the request takes less than a few seconds to fail + // we create a stream_context (works in php >= 5.2.1) + // we also set the socket_timeout (for php < 5.2.1) + $default_socket_timeout = @ini_get('default_socket_timeout'); + @ini_set('default_socket_timeout', $timeout); + + $ctx = null; + if(function_exists('stream_context_create')) { + $stream_options = array( + 'http' => array( + 'header' => 'User-Agent: Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : '')."\r\n" + .'Referer: http://'.Piwik_Common::getIpString()."/\r\n", + 'max_redirects' => 3, // PHP 5.1.0 + 'timeout' => $timeout, // PHP 5.2.1 + ) + ); + $ctx = stream_context_create($stream_options); + } + + $response = @file_get_contents($aUrl, 0, $ctx); + if(is_resource($file)) + { + // save to file + fwrite($file, $response); + } + + // restore the socket_timeout value + if(!empty($default_socket_timeout)) + { + @ini_set('default_socket_timeout', $default_socket_timeout); + } + } + else if($method == 'curl') + { + $ch = @curl_init(); + + $curl_options = array( + CURLOPT_URL => $aUrl, + CURLOPT_HEADER => false, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_BINARYTRANSFER => is_resource($file), + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 3, + CURLOPT_USERAGENT => 'Piwik/'.Piwik_Version::VERSION.($userAgent ? " $userAgent" : ''), + CURLOPT_REFERER => 'http://'.Piwik_Common::getIpString(), + ); + @curl_setopt_array($ch, $curl_options); + + $response = @curl_exec($ch); + if(is_resource($file)) + { + // save to file + fwrite($file, $response); + } + + @curl_close($ch); + unset($ch); + } + else + { + throw new Exception('Invalid request method: '.$method); + } + + if(is_resource($file)) + { + fflush($file); + @fclose($file); + if($contentLength && (($fileLength != $contentLength) || (filesize($destinationPath) != $contentLength))) + { + throw new Exception('File size error: '.$destinationPath.'; expected '.$contentLength.' bytes; received '.$fileLength.' bytes'); + } + return true; + } + + if($contentLength && strlen($response) != $contentLength) + { + throw new Exception('Content length error: expected '.$contentLength.' bytes; received '.$fileLength.' bytes'); + } + return trim($response); } /** - * Returns the plugin currently being used to display the page - * - * @return Piwik_Plugin + * Fetch the file at $url in the destination $pathDestination + * @param string $url + * @param string $pathDestination + * @param int $tries + * @return true on success, throws Exception on failure */ - static public function getCurrentPlugin() + static public function fetchRemoteFile($url, $pathDestination, $tries = 0) { - return Piwik_PluginsManager::getInstance()->getLoadedPlugin(Piwik::getModule()); + return self::sendHttpRequest($url, 10, 'Update', $pathDestination, $tries); } /** - * Returns the current module read from the URL (eg. 'API', 'UserSettings', etc.) + * Recursively delete a directory * - * @return string + * @param string $dir Directory name + * @param boolean $deleteRootToo Delete specified top-level directory as well */ - static public function getModule() + static public function unlinkRecursive($dir, $deleteRootToo) { - return Piwik_Common::getRequestVar('module', '', 'string'); + if(!$dh = @opendir($dir)) + { + return; + } + while (false !== ($obj = readdir($dh))) + { + if($obj == '.' || $obj == '..') + { + continue; + } + + if (!@unlink($dir . '/' . $obj)) + { + self::unlinkRecursive($dir.'/'.$obj, true); + } + } + closedir($dh); + if ($deleteRootToo) + { + @rmdir($dir); + } + return; + } + + /** + * Copy recursively from $source to $target. + * + * @param string $source eg. './tmp/latest' + * @param string $target eg. '.' + * @param bool $excludePhp + */ + static public function copyRecursive($source, $target, $excludePhp=false ) + { + if ( is_dir( $source ) ) + { + @mkdir( $target ); + $d = dir( $source ); + while ( false !== ( $entry = $d->read() ) ) + { + if ( $entry == '.' || $entry == '..' ) + { + continue; + } + + $sourcePath = $source . '/' . $entry; + if ( is_dir( $sourcePath ) ) + { + self::copyRecursive( $sourcePath, $target . '/' . $entry, $excludePhp ); + continue; + } + $destPath = $target . '/' . $entry; + self::copy($sourcePath, $destPath, $excludePhp); + } + $d->close(); + } + else + { + self::copy($source, $target, $excludePhp); + } + } + + /** + * Copy individual file from $source to $target. + * + * @param string $source eg. './tmp/latest/index.php' + * @param string $target eg. './index.php' + * @param bool $excludePhp + * @return bool + */ + static public function copy($source, $dest, $excludePhp=false) + { + static $phpExtensions = array('php', 'tpl'); + + if($excludePhp) + { + $path_parts = pathinfo($source); + if(in_array($path_parts['extension'], $phpExtensions)) + { + return true; + } + } + + if(!@copy( $source, $dest )) + { + @chmod($dest, 0755); + if(!@copy( $source, $dest )) + { + throw new Exception(" + Error while copying file to <code>$dest</code>. <br /> + Please check that the web server has enough permission to overwrite this file. <br/> + For example, on a linux server, if your apache user is www-data you can try to execute:<br> + <code>chown -R www-data:www-data ".Piwik_Common::getPathToPiwikRoot()."</code><br> + <code>chmod -R 0755 ".Piwik_Common::getPathToPiwikRoot()."</code><br> + "); + } + } + return true; } /** - * Returns the current action read from the URL + * Recursively find pathnames that match a pattern + * @see glob() * - * @return string + * @param string $sDir directory + * @param string $sPattern pattern + * @param int $nFlags glob() flags + * @return array */ - static public function getAction() + public static function globr($sDir, $sPattern, $nFlags = NULL) { - return Piwik_Common::getRequestVar('action', '', 'string'); + if(($aFiles = glob("$sDir/$sPattern", $nFlags)) == false) + { + $aFiles = array(); + } + if(($aDirs = glob("$sDir/*", GLOB_ONLYDIR)) != false) + { + foreach ($aDirs as $sSubDir) + { + $aSubFiles = self::globr($sSubDir, $sPattern, $nFlags); + $aFiles = array_merge($aFiles, $aSubFiles); + } + } + return $aFiles; } /** - * Redirect to module (and action) + * API was simplified in 0.2.27, but we maintain backward compatibility + * when calling Piwik::prefixTable * - * @param string $newModule - * @param string $newAction - * @return bool false if the URL to redirect to is already this URL + * @deprecated as of 0.2.27 */ - static public function redirectToModule( $newModule, $newAction = '', $parameters = array() ) + static public function prefixTable( $table ) { - $newUrl = 'index.php' . Piwik_Url::getCurrentQueryStringWithParametersModified( - array('module' => $newModule, 'action' => $newAction) - + $parameters - ); - Piwik_Url::redirectToUrl($newUrl); + return Piwik_Common::prefixTable($table); } - -/* - * Global database object - */ - + /** - * Create database object and connect to database + * Names of all the prefixed tables in piwik + * Doesn't use the DB */ - static public function createDatabaseObject( $dbInfos = null ) + static public function getTablesNames() { + $aTables = array_keys(self::getTablesCreateSql()); $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + $return = array(); + foreach($aTables as $table) + { + $return[] = $prefixTables.$table; + } + return $return; + } + + static $tablesInstalled = null; + + static public function getTablesInstalled($forceReload = true, $idSite = null) + { + if(is_null(self::$tablesInstalled) + || $forceReload === true) + { + $db = Zend_Registry::get('db'); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; + + $allTables = $db->fetchCol("SHOW TABLES"); + + // all the tables to be installed + $allMyTables = self::getTablesNames(); + + // we get the intersection between all the tables in the DB and the tables to be installed + $tablesInstalled = array_intersect($allMyTables, $allTables); + + // at this point we have only the piwik tables which is good + // but we still miss the piwik generated tables (using the class Piwik_TablePartitioning) + $idSiteInSql = "no"; + if(!is_null($idSite)) + { + $idSiteInSql = $idSite; + } + $allArchiveNumeric = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */ + SHOW TABLES LIKE '".$prefixTables."archive_numeric%'"); + $allArchiveBlob = $db->fetchCol("/* SHARDING_ID_SITE = ".$idSiteInSql." */ + SHOW TABLES LIKE '".$prefixTables."archive_blob%'"); + + $allTablesReallyInstalled = array_merge($tablesInstalled, $allArchiveNumeric, $allArchiveBlob); + + self::$tablesInstalled = $allTablesReallyInstalled; + } + return self::$tablesInstalled; + } + + static public function createDatabase( $dbName = null ) + { + if(is_null($dbName)) + { + $dbName = Zend_Registry::get('config')->database->dbname; + } + Piwik_Exec("CREATE DATABASE IF NOT EXISTS ".$dbName); + } + static public function dropDatabase() + { + $dbName = Zend_Registry::get('config')->database->dbname; + Piwik_Exec("DROP DATABASE IF EXISTS " . $dbName); + } + + static public function createDatabaseObject( $dbInfos = null ) + { + $config = Zend_Registry::get('config'); + if(is_null($dbInfos)) { $dbInfos = $config->database->toArray(); } - + $dbInfos['profiler'] = $config->Debug->enable_sql_profiler; - + $db = null; Piwik_PostEvent('Reporting.createDatabase', $db); if(is_null($db)) { - $adapter = $dbInfos['adapter']; - $db = Piwik_Db_Adapter::factory($adapter, $dbInfos); + if($dbInfos['port'][0] == '/') + { + $dbInfos['unix_socket'] = $dbInfos['port']; + unset($dbInfos['host']); + unset($dbInfos['port']); + } + + // not used by Zend Framework + unset($dbInfos['tables_prefix']); + unset($dbInfos['adapter']); + + $db = Piwik_Db::factory($config->database->adapter, $dbInfos); + $db->getConnection(); + + Zend_Db_Table::setDefaultAdapter($db); + $db->resetConfig(); // we don't want this information to appear in the logs } Zend_Registry::set('db', $db); } - - /** - * Disconnect from database - */ + static public function disconnectDatabase() { Zend_Registry::get('db')->closeConnection(); } + + /** + * Returns the MySQL database server version + * + * @deprecated 0.4.4 + */ + static public function getMysqlVersion() + { + return Piwik_FetchOne("SELECT VERSION()"); + } /** * Checks the database server version against the required minimum @@ -1304,30 +1520,23 @@ class Piwik return Zend_Registry::get('db')->isConnectionUTF8(); } -/* - * Global log object - */ - - /** - * Create log object - */ static public function createLogObject() { $configAPI = Zend_Registry::get('config')->log; - + $aLoggers = array( 'logger_api_call' => new Piwik_Log_APICall, 'logger_exception' => new Piwik_Log_Exception, 'logger_error' => new Piwik_Log_Error, 'logger_message' => new Piwik_Log_Message, - ); - + ); + foreach($configAPI as $loggerType => $aRecordTo) { if(isset($aLoggers[$loggerType])) { $logger = $aLoggers[$loggerType]; - + foreach($aRecordTo as $recordTo) { switch($recordTo) @@ -1335,15 +1544,15 @@ class Piwik case 'screen': $logger->addWriteToScreen(); break; - + case 'database': $logger->addWriteToDatabase(); break; - + case 'file': - $logger->addWriteToFile(); + $logger->addWriteToFile(); break; - + default: throw new Exception("'$recordTo' is not a valid Log type. Valid logger types are: screen, database, file."); break; @@ -1351,7 +1560,7 @@ class Piwik } } } - + foreach($aLoggers as $loggerType =>$logger) { if($logger->getWritersCount() == 0) @@ -1361,16 +1570,7 @@ class Piwik Zend_Registry::set($loggerType, $logger); } } - -/* - * Global config object - */ - - /** - * Create configuration object - * - * @param string $pathConfigFile - */ + static public function createConfigObject( $pathConfigFile = null ) { $config = new Piwik_Config($pathConfigFile); @@ -1378,181 +1578,93 @@ class Piwik $config->init(); } -/* - * Global access object - */ - - /** - * Create access object - */ static public function createAccessObject() { Zend_Registry::set('access', new Piwik_Access()); } - -/* - * User input validation - */ - - /** - * Returns true if the email is a valid email - * - * @param string email - * @return bool - */ - static public function isValidEmailString( $email ) + + static public function dropTables( $doNotDelete = array() ) { - return (preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z]{2,4}$/', $email) > 0); - } - - /** - * Returns true if the login is valid. - * Warning: does not check if the login already exists! You must use UsersManager_API->userExists as well. - * - * @param string $login - * @return bool or throws exception - */ - static public function checkValidLoginString( $userLogin ) - { - $loginMinimumLength = 3; - $loginMaximumLength = 100; - $l = strlen($userLogin); - if(!($l >= $loginMinimumLength - && $l <= $loginMaximumLength - && (preg_match('/^[A-Za-z0-9_.-]*$/', $userLogin) > 0)) - ) + $tablesAlreadyInstalled = self::getTablesInstalled(); + $db = Zend_Registry::get('db'); + + $doNotDeletePattern = '/('.implode('|',$doNotDelete).')/'; + + foreach($tablesAlreadyInstalled as $tableName) { - throw new Exception(Piwik_TranslateException('UsersManager_ExceptionInvalidLoginFormat', array($loginMinimumLength, $loginMaximumLength))); - } + + if( count($doNotDelete) == 0 + || (!in_array($tableName,$doNotDelete) + && !preg_match($doNotDeletePattern,$tableName) + ) + ) + { + $db->query("DROP TABLE `$tableName`"); + } + } } - -/* - * Date / Timezone - */ - + /** - * Returns true if the current php version supports timezone manipulation - * (most likely if php >= 5.2) - * + * Returns true if the email is a valid email + * + * @param string email * @return bool */ - static public function isTimezoneSupportEnabled() - { - return - function_exists( 'date_create' ) && - function_exists( 'date_default_timezone_set' ) && - function_exists( 'timezone_identifiers_list' ) && - function_exists( 'timezone_open' ) && - function_exists( 'timezone_offset_get' ); - } - -/* - * Database and table definition methods - */ - - /** - * Is the schema available? - * - * @return bool True if schema is available; false otherwise - */ - static public function isAvailable() - { - return Piwik_Db_Schema::getInstance()->isAvailable(); - } - - /** - * Get the SQL to create a specific Piwik table - * - * @param string $tableName - * @return string SQL - */ - static public function getTableCreateSql( $tableName ) - { - return Piwik_Db_Schema::getInstance()->getTableCreateSql($tableName); - } - - /** - * Get the SQL to create Piwik tables - * - * @return array of strings containing SQL - */ - static public function getTablesCreateSql() - { - return Piwik_Db_Schema::getInstance()->getTablesCreateSql(); - } - - /** - * Create database - * - * @param string $dbName - */ - static public function createDatabase( $dbName = null ) + static public function isValidEmailString( $email ) { - Piwik_Db_Schema::getInstance()->createDatabase($dbName); + return (preg_match('/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9_.-]+\.[a-zA-Z]{2,4}$/', $email) > 0); } - + /** - * Drop database + * Creates an entry in the User table for the "anonymous" user. */ - static public function dropDatabase() + static public function createAnonymousUser() { - Piwik_Db_Schema::getInstance()->dropDatabase(); + // The anonymous user is the user that is assigned by default + // note that the token_auth value is anonymous, which is assigned by default as well in the Login plugin + $db = Zend_Registry::get('db'); + $db->query("INSERT INTO ". Piwik::prefixTable("user") . " + VALUES ( 'anonymous', '', 'anonymous', 'anonymous@example.org', 'anonymous', CURRENT_TIMESTAMP );" ); } - - /** - * Create all tables - */ + static public function createTables() { - Piwik_Db_Schema::getInstance()->createTables(); - } + $db = Zend_Registry::get('db'); + $config = Zend_Registry::get('config'); + $prefixTables = $config->database->tables_prefix; - /** - * Creates an entry in the User table for the "anonymous" user. - */ - static public function createAnonymousUser() - { - Piwik_Db_Schema::getInstance()->createAnonymousUser(); - } + $tablesAlreadyInstalled = self::getTablesInstalled(); + $tablesToCreate = self::getTablesCreateSql(); + unset($tablesToCreate['archive_blob']); + unset($tablesToCreate['archive_numeric']); - /** - * Truncate all tables - */ - static public function truncateAllTables() - { - Piwik_Db_Schema::getInstance()->truncateAllTables(); + foreach($tablesToCreate as $tableName => $tableSql) + { + $tableName = $prefixTables . $tableName; + if(!in_array($tableName, $tablesAlreadyInstalled)) + { + $db->query( $tableSql ); + } + } } - - /** - * Drop specific tables - * - * @param array $doNotDelete Names of tables to not delete - */ - static public function dropTables( $doNotDelete = array() ) + + static public function truncateAllTables() { - Piwik_Db_Schema::getInstance()->dropTables($doNotDelete); + $tablesAlreadyInstalled = self::getTablesInstalled($forceReload = true); + foreach($tablesAlreadyInstalled as $table) + { + Piwik_Query("TRUNCATE `$table`"); + } } - - /** - * Names of all the prefixed tables in piwik - * Doesn't use the DB - * - * @return array Table names - */ - static public function getTablesNames() + + static public function install() { - return Piwik_Db_Schema::getInstance()->getTablesNames(); + Piwik_Common::mkdir(Zend_Registry::get('config')->smarty->compile_dir); } - - /** - * Get list of tables installed - * - * @param bool $forceReload Invalidate cache - * @param string $idSite - * @return array Tables installed - */ - static public function getTablesInstalled($forceReload = true, $idSite = null) + + static public function uninstall() { - return Piwik_Db_Schema::getInstance()->getTablesInstalled($forceReload, $idSite); + $db = Zend_Registry::get('db'); + $db->query( "DROP TABLE IF EXISTS ". implode(", ", self::getTablesNames()) ); } } |