diff options
author | Thomas Steur <thomas.steur@gmail.com> | 2014-09-02 13:19:21 +0400 |
---|---|---|
committer | Thomas Steur <thomas.steur@gmail.com> | 2014-09-02 13:19:21 +0400 |
commit | a6474178f9a770d56754636345082a3cc698eeb8 (patch) | |
tree | 894199addf0ce6f6c696c8fdb2735cdbcdb5757b /core | |
parent | e58a92d2512f7054c328f0e4370405d7bc0a17e8 (diff) | |
parent | f5fc3c8e62e0a90a6100346cf203d4dc037de22f (diff) |
Merge branch 'master' into 4996_content_tracking
Conflicts:
js/piwik.js
Diffstat (limited to 'core')
-rw-r--r-- | core/API/ApiRenderer.php | 4 | ||||
-rw-r--r-- | core/API/Request.php | 3 | ||||
-rw-r--r-- | core/API/ResponseBuilder.php | 2 | ||||
-rw-r--r-- | core/Cache/PersistentCache.php | 18 | ||||
-rw-r--r-- | core/Config.php | 1 | ||||
-rw-r--r-- | core/Date.php | 53 | ||||
-rw-r--r-- | core/Error.php | 16 | ||||
-rw-r--r-- | core/EventDispatcher.php | 32 | ||||
-rw-r--r-- | core/FrontController.php | 1 | ||||
-rw-r--r-- | core/Log.php | 14 | ||||
-rw-r--r-- | core/Piwik.php | 8 | ||||
-rw-r--r-- | core/Plugin/ConsoleCommand.php | 14 | ||||
-rw-r--r-- | core/Plugin/Manager.php | 113 | ||||
-rw-r--r-- | core/Plugin/Widgets.php | 39 | ||||
-rw-r--r-- | core/Profiler.php | 128 | ||||
-rw-r--r-- | core/Tracker.php | 10 | ||||
-rw-r--r-- | core/Tracker/GoalManager.php | 3 | ||||
-rw-r--r-- | core/Tracker/Visit.php | 6 | ||||
-rw-r--r-- | core/Translate/Validate/CoreTranslations.php | 6 | ||||
-rw-r--r-- | core/Updates/2.6.0-b1.php | 30 | ||||
-rw-r--r-- | core/Version.php | 2 |
21 files changed, 367 insertions, 136 deletions
diff --git a/core/API/ApiRenderer.php b/core/API/ApiRenderer.php index ada1e3cd5d..924bd46aab 100644 --- a/core/API/ApiRenderer.php +++ b/core/API/ApiRenderer.php @@ -80,6 +80,10 @@ abstract class ApiRenderer protected function buildDataTableRenderer($dataTable) { $format = self::getFormatFromClass(get_class($this)); + if ($format == 'json2') { + $format = 'json'; + } + $renderer = Renderer::factory($format); $renderer->setTable($dataTable); $renderer->setRenderSubTables(Common::getRequestVar('expanded', false, 'int', $this->request)); diff --git a/core/API/Request.php b/core/API/Request.php index d4bdee5d24..03b555ad18 100644 --- a/core/API/Request.php +++ b/core/API/Request.php @@ -17,6 +17,7 @@ use Piwik\PluginDeactivatedException; use Piwik\SettingsServer; use Piwik\Url; use Piwik\UrlHelper; +use Piwik\Log; /** * Dispatches API requests to the appropriate API method. @@ -216,6 +217,8 @@ class Request $toReturn = $response->getResponse($returnedValue, $module, $method); } catch (Exception $e) { + Log::debug($e); + $toReturn = $response->getResponseException($e); } return $toReturn; diff --git a/core/API/ResponseBuilder.php b/core/API/ResponseBuilder.php index 8fcf3e8f35..f5af551917 100644 --- a/core/API/ResponseBuilder.php +++ b/core/API/ResponseBuilder.php @@ -221,7 +221,7 @@ class ResponseBuilder $firstKey = key($array); } - $isAssoc = !empty($firstArray) && is_numeric($firstKey) && is_array($firstArray) && !Piwik::isMultiDimensionalArray($array) && count(array_filter(array_keys($firstArray), 'is_string')); + $isAssoc = !empty($firstArray) && is_numeric($firstKey) && is_array($firstArray) && count(array_filter(array_keys($firstArray), 'is_string')); if ($isAssoc) { $hideColumns = Common::getRequestVar('hideColumns', '', 'string', $this->request); diff --git a/core/Cache/PersistentCache.php b/core/Cache/PersistentCache.php index 3bb74fd2b1..5a98621a4c 100644 --- a/core/Cache/PersistentCache.php +++ b/core/Cache/PersistentCache.php @@ -12,6 +12,7 @@ use Piwik\CacheFile; use Piwik\Development; use Piwik\Piwik; use Piwik\SettingsServer; +use Piwik\Version; /** * Caching class that persists all cached values between requests. Meaning whatever you cache will be stored on the @@ -97,13 +98,13 @@ class PersistentCache if (SettingsServer::isTrackerApiRequest()) { $eventToPersist = 'Tracker.end'; - $mode = 'tracker'; + $mode = '-tracker'; } else { $eventToPersist = 'Request.dispatch.end'; - $mode = 'ui'; + $mode = '-ui'; } - $cache = self::getStorage()->get('StaticCache-' . $mode); + $cache = self::getStorage()->get(self::getCacheFilename() . $mode); if (is_array($cache)) { self::$content = $cache; @@ -112,6 +113,11 @@ class PersistentCache Piwik::addAction($eventToPersist, array(__CLASS__, 'persistCache')); } + private static function getCacheFilename() + { + return 'StaticCache-' . str_replace(array('.', '-'), '', Version::VERSION); + } + /** * @ignore */ @@ -119,12 +125,12 @@ class PersistentCache { if (self::$isDirty) { if (SettingsServer::isTrackerApiRequest()) { - $mode = 'tracker'; + $mode = '-tracker'; } else { - $mode = 'ui'; + $mode = '-ui'; } - self::getStorage()->set('StaticCache-' . $mode, self::$content); + self::getStorage()->set(self::getCacheFilename() . $mode, self::$content); } } diff --git a/core/Config.php b/core/Config.php index 8183b3fdc2..9d6326bb2e 100644 --- a/core/Config.php +++ b/core/Config.php @@ -135,7 +135,6 @@ class Config extends Singleton // Ensure local mods do not affect tests if (empty($pathGlobal)) { - $this->configCache['log'] = $this->configGlobal['log']; $this->configCache['Debug'] = $this->configGlobal['Debug']; $this->configCache['mail'] = $this->configGlobal['mail']; $this->configCache['General'] = $this->configGlobal['General']; diff --git a/core/Date.php b/core/Date.php index 7af59e1f2a..c5b4cd7af6 100644 --- a/core/Date.php +++ b/core/Date.php @@ -41,6 +41,26 @@ class Date const DATE_TIME_FORMAT = 'Y-m-d H:i:s'; /** + * Max days for months (non-leap-year). See {@link addPeriod()} implementation. + * + * @var int[] + */ + private static $maxDaysInMonth = array( + '1' => 31, + '2' => 28, + '3' => 31, + '4' => 30, + '5' => 31, + '6' => 30, + '7' => 31, + '8' => 31, + '9' => 30, + '10' => 31, + '11' => 30, + '12' => 31 + ); + + /** * The stored timestamp is always UTC based. * The returned timestamp via getTimestamp() will have the conversion applied * @var int|null @@ -673,14 +693,41 @@ class Date */ public function addPeriod($n, $period) { - if ($n < 0) { - $ts = strtotime("$n $period", $this->timestamp); + if (strtolower($period) == 'month') { // TODO: comments + $dateInfo = getdate($this->timestamp); + + $ts = mktime( + $dateInfo['hours'], + $dateInfo['minutes'], + $dateInfo['seconds'], + $dateInfo['mon'] + (int)$n, + 1, + $dateInfo['year'] + ); + + $daysToAdd = min($dateInfo['mday'], self::getMaxDaysInMonth($ts)) - 1; + $ts += self::NUM_SECONDS_IN_DAY * $daysToAdd; } else { - $ts = strtotime("+$n $period", $this->timestamp); + $time = $n < 0 ? "$n $period" : "+$n $period"; + + $ts = strtotime($time, $this->timestamp); } + return new Date($ts, $this->timezone); } + private static function getMaxDaysInMonth($timestamp) + { + $month = (int)date('m', $timestamp); + if (date('L', $timestamp) == 1 + && $month == 2 + ) { + return 29; + } else { + return self::$maxDaysInMonth[$month]; + } + } + /** * Subtracts a period from `$this` date and returns the result in a new Date instance. * diff --git a/core/Error.php b/core/Error.php index ac0029d987..7cff6fac0b 100644 --- a/core/Error.php +++ b/core/Error.php @@ -76,9 +76,15 @@ class Error $this->backtrace = $backtrace; } - public function getErrNoString() + /** + * Returns a string description of a PHP error number. + * + * @param int $errno `E_ERROR`, `E_WARNING`, `E_PARSE`, etc. + * @return string + */ + public static function getErrNoString($errno) { - switch ($this->errno) { + switch ($errno) { case E_ERROR: return "Error"; case E_WARNING: @@ -110,14 +116,14 @@ class Error case E_USER_DEPRECATED: return "User Deprecated"; default: - return "Unknown error ({$this->errno})"; + return "Unknown error ($errno)"; } } public static function formatFileAndDBLogMessage(&$message, $level, $tag, $datetime, $log) { if ($message instanceof Error) { - $message = $message->errfile . '(' . $message->errline . '): ' . $message->getErrNoString() + $message = $message->errfile . '(' . $message->errline . '): ' . Error::getErrNoString($message->errno) . ' - ' . $message->errstr . "\n" . $message->backtrace; $message = $log->formatMessage($level, $tag, $datetime, $message); @@ -146,7 +152,7 @@ class Error <strong>There is an error. Please report the message (Piwik " . (class_exists('Piwik\Version') ? Version::VERSION : '') . ") and full backtrace in the <a href='?module=Proxy&action=redirect&url=http://forum.piwik.org' target='_blank'>Piwik forums</a> (please do a Search first as it might have been reported already!).<br /><br/> "; - $htmlString .= $message->getErrNoString(); + $htmlString .= Error::getErrNoString($message->errno); $htmlString .= ":</strong> <em>{$message->errstr}</em> in <strong>{$message->errfile}</strong>"; $htmlString .= " on line <strong>{$message->errline}</strong>\n"; $htmlString .= "<br /><br />Backtrace --><div style=\"font-family:Courier;font-size:10pt\"><br />\n"; diff --git a/core/EventDispatcher.php b/core/EventDispatcher.php index b902bdef2c..ce0815c6ba 100644 --- a/core/EventDispatcher.php +++ b/core/EventDispatcher.php @@ -46,6 +46,21 @@ class EventDispatcher extends Singleton private $pendingEvents = array(); /** + * Plugin\Manager instance used to get list of loaded plugins. + * + * @var Piwik\Plugin\Manager + */ + private $pluginManager; + + /** + * Constructor. + */ + public function __construct($pluginManager = null) + { + $this->pluginManager = $pluginManager; + } + + /** * Triggers an event, executing all callbacks associated with it. * * @param string $eventName The name of the event, ie, API.getReportMetadata. @@ -64,7 +79,7 @@ class EventDispatcher extends Singleton } if (empty($plugins)) { - $plugins = \Piwik\Plugin\Manager::getInstance()->getPluginsLoadedAndActivated(); + $plugins = $this->getPluginManager()->getPluginsLoadedAndActivated(); } $callbacks = array(); @@ -72,7 +87,7 @@ class EventDispatcher extends Singleton // collect all callbacks to execute foreach ($plugins as $plugin) { if (is_string($plugin)) { - $plugin = \Piwik\Plugin\Manager::getInstance()->getLoadedPlugin($plugin); + $plugin = $this->getPluginManager()->getLoadedPlugin($plugin); } $hooks = $plugin->getListHooksRegistered(); @@ -94,6 +109,7 @@ class EventDispatcher extends Singleton // sort callbacks by their importance ksort($callbacks); + // execute callbacks in order foreach ($callbacks as $callbackGroup) { foreach ($callbackGroup as $callback) { @@ -183,5 +199,15 @@ class EventDispatcher extends Singleton return array($pluginFunction, $callbackGroup); } -} + /** + * TODO + */ + private function getPluginManager() + { + if ($this->pluginManager === null) { + $this->pluginManager = \Piwik\Plugin\Manager::getInstance(); + } + return $this->pluginManager; + } +}
\ No newline at end of file diff --git a/core/FrontController.php b/core/FrontController.php index db6f1772dc..a50fb152ca 100644 --- a/core/FrontController.php +++ b/core/FrontController.php @@ -320,6 +320,7 @@ class FrontController extends Singleton $this->handleProfiler(); $this->handleSSLRedirection(); + Plugin\Manager::getInstance()->loadPluginTranslations('en'); Plugin\Manager::getInstance()->loadActivatedPlugins(); if ($exceptionToThrow) { diff --git a/core/Log.php b/core/Log.php index 4e505f7b63..a860dd1989 100644 --- a/core/Log.php +++ b/core/Log.php @@ -256,7 +256,7 @@ class Log extends Singleton { return str_replace( array("%tag%", "%message%", "%datetime%", "%level%"), - array($tag, $message, $datetime, $this->getStringLevel($level)), + array($tag, trim($message), $datetime, $this->getStringLevel($level)), $this->logMessageFormat ); } @@ -418,6 +418,13 @@ class Log extends Singleton if (is_string($message) && !empty($sprintfParams) ) { + // handle array sprintf parameters + foreach ($sprintfParams as &$param) { + if (is_array($param)) { + $param = json_encode($param); + } + } + $message = vsprintf($message, $sprintfParams); } @@ -583,6 +590,7 @@ class Log extends Singleton */ Piwik::postEvent(self::FORMAT_SCREEN_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); } + $message = trim($message); return $message . "\n"; } @@ -636,6 +644,7 @@ class Log extends Singleton */ Piwik::postEvent(self::FORMAT_DATABASE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); } + $message = trim($message); return $message; } @@ -676,6 +685,9 @@ class Log extends Singleton */ Piwik::postEvent(self::FORMAT_FILE_MESSAGE_EVENT, array(&$message, $level, $tag, $datetime, $logger)); } + + $message = trim($message); + $message = str_replace("\n", "\n ", $message); return $message . "\n"; } } diff --git a/core/Piwik.php b/core/Piwik.php index befe249923..fcb49ee82f 100644 --- a/core/Piwik.php +++ b/core/Piwik.php @@ -747,12 +747,8 @@ class Piwik $first = reset($array); foreach ($array as $first) { if (is_array($first)) { - foreach ($first as $value) { - // Yes, this is a multi dim array - if (is_array($value)) { - return true; - } - } + // Yes, this is a multi dim array + return true; } } diff --git a/core/Plugin/ConsoleCommand.php b/core/Plugin/ConsoleCommand.php index adb2c33a49..884b6f60ee 100644 --- a/core/Plugin/ConsoleCommand.php +++ b/core/Plugin/ConsoleCommand.php @@ -20,20 +20,6 @@ use Symfony\Component\Console\Output\OutputInterface; */ class ConsoleCommand extends SymfonyCommand { - /** - * Constructor. - * - * @param string|null $name The name of the command, eg, `'generate:api'`. - */ - public function __construct($name = null) - { - if (!Common::isPhpCliMode()) { - throw new \RuntimeException('Only executable in CLI mode'); - } - - parent::__construct($name); - } - public function writeSuccessMessage(OutputInterface $output, $messages) { $lengths = array_map('strlen', $messages); diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php index b6ed33342a..49b073c8d3 100644 --- a/core/Plugin/Manager.php +++ b/core/Plugin/Manager.php @@ -9,12 +9,18 @@ namespace Piwik\Plugin; +use Piwik\Cache\PersistentCache; +use Piwik\Cache\PluginAwareStaticCache; +use Piwik\Cache\StaticCache; +use Piwik\CacheFile; use Piwik\Common; use Piwik\Config as PiwikConfig; use Piwik\Config; use Piwik\Db; +use Piwik\Development; use Piwik\EventDispatcher; use Piwik\Filesystem; +use Piwik\Log; use Piwik\Option; use Piwik\Plugin; use Piwik\Singleton; @@ -112,8 +118,30 @@ class Manager extends Singleton */ public function loadTrackerPlugins() { + $cache = new PersistentCache('PluginsTracker'); + + if ($cache->has()) { + $pluginsTracker = $cache->get(); + } else { + + $this->unloadPlugins(); + $this->loadActivatedPlugins(); + + $pluginsTracker = array(); + + foreach ($this->loadedPlugins as $pluginName => $plugin) { + if ($this->isTrackerPlugin($plugin)) { + $pluginsTracker[] = $pluginName; + } + } + + if (!empty($pluginsTracker)) { + $cache->set($pluginsTracker); + } + } + $this->unloadPlugins(); - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; + if (empty($pluginsTracker)) { return array(); } @@ -160,18 +188,6 @@ class Manager extends Singleton } /** - * Update Plugins_Tracker config - * - * @param array $plugins Plugins - */ - private function updatePluginsTrackerConfig($plugins) - { - $section = PiwikConfig::getInstance()->Plugins_Tracker; - $section['Plugins_Tracker'] = $plugins; - PiwikConfig::getInstance()->Plugins_Tracker = $section; - } - - /** * Update PluginsInstalled config * * @param array $plugins Plugins @@ -382,6 +398,7 @@ class Manager extends Singleton */ public function installLoadedPlugins() { + Log::verbose("Loaded plugins: " . implode(", ", array_keys($this->getLoadedPlugins()))); $messages = array(); foreach ($this->getLoadedPlugins() as $plugin) { try { @@ -646,11 +663,45 @@ class Manager extends Singleton if (empty($language)) { $language = Translate::getLanguageToLoad(); } - $plugins = $this->getLoadedPlugins(); - foreach ($plugins as $plugin) { - $this->loadTranslation($plugin, $language); + $cache = new CacheFile('tracker', 43200); // ttl=12hours + $cacheKey = 'PluginTranslations'; + + if (!empty($language)) { + $cacheKey .= '-' . trim($language); } + + if (!empty($this->loadedPlugins)) { + // makes sure to create a translation in case loaded plugins change (ie Tests vs Tracker vs UI etc) + $cacheKey .= '-' . md5(implode('', $this->getLoadedPluginsName())); + } + + $translations = $cache->get($cacheKey); + + if (!empty($translations) && + is_array($translations) && + !Development::isEnabled()) { + + Translate::mergeTranslationArray($translations); + return; + } + + $translations = array(); + $pluginNames = self::getAllPluginsNames(); + + foreach ($pluginNames as $pluginName) { + if ($this->isPluginLoaded($pluginName) || + $this->isPluginBundledWithCore($pluginName)) { + + $this->loadTranslation($pluginName, $language); + + if (isset($GLOBALS['Piwik_translations'][$pluginName])) { + $translations[$pluginName] = $GLOBALS['Piwik_translations'][$pluginName]; + } + } + } + + $cache->set($cacheKey, $translations); } /** @@ -987,7 +1038,10 @@ class Manager extends Singleton } // merge in specific language translations (to overwrite english defaults) - if ($defaultEnglishLangPath != $defaultLangPath && file_exists($defaultLangPath)) { + if (!empty($langCode) && + $defaultEnglishLangPath != $defaultLangPath && + file_exists($defaultLangPath)) { + $translations = $this->getTranslationsFromFile($defaultLangPath); $translationsLoaded = true; if (isset($translations[$pluginName])) { @@ -1056,18 +1110,6 @@ class Manager extends Singleton $saveConfig = true; } - if ($this->isTrackerPlugin($plugin)) { - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; - if (is_null($pluginsTracker)) { - $pluginsTracker = array(); - } - if (!in_array($pluginName, $pluginsTracker)) { - $pluginsTracker[] = $pluginName; - $this->updatePluginsTrackerConfig($pluginsTracker); - $saveConfig = true; - } - } - if ($saveConfig) { PiwikConfig::getInstance()->forceSave(); } @@ -1138,18 +1180,6 @@ class Manager extends Singleton $this->updatePluginsConfig($pluginsEnabled); } - private function removePluginFromTrackerConfig($pluginName) - { - $pluginsTracker = PiwikConfig::getInstance()->Plugins_Tracker['Plugins_Tracker']; - if (!is_null($pluginsTracker)) { - $key = array_search($pluginName, $pluginsTracker); - if ($key !== false) { - unset($pluginsTracker[$key]); - $this->updatePluginsTrackerConfig($pluginsTracker); - } - } - } - /** * @param string $pathToTranslationFile * @throws \Exception @@ -1230,7 +1260,6 @@ class Manager extends Singleton private function removePluginFromConfig($pluginName) { $this->removePluginFromPluginsConfig($pluginName); - $this->removePluginFromTrackerConfig($pluginName); PiwikConfig::getInstance()->forceSave(); } diff --git a/core/Plugin/Widgets.php b/core/Plugin/Widgets.php index ef8b059b77..3199e7a80b 100644 --- a/core/Plugin/Widgets.php +++ b/core/Plugin/Widgets.php @@ -14,9 +14,9 @@ use Piwik\WidgetsList; /** * Base class of all plugin widget providers. Plugins that define their own widgets can extend this class to easily - * add new widgets, to remove or to rename existing items. + * add new widgets or to remove widgets defined by other plugins. * - * For an example, see the {@link https://github.com/piwik/piwik/blob/master/plugins/ExampleRssWidget/Widget.php} plugin. + * For an example, see the {@link https://github.com/piwik/piwik/blob/master/plugins/ExamplePlugin/Widgets.php} plugin. * * @api */ @@ -27,11 +27,17 @@ class Widgets private $module = ''; + /** + * @ignore + */ public function __construct() { $this->module = $this->getModule(); } + /** + * @ignore + */ public function getCategory() { return $this->category; @@ -46,16 +52,23 @@ class Widgets } /** + * Adds a widget. You can add a widget by calling this method and passing the name of the widget as well as a method + * name that will be executed to render the widget. The method can be defined either directly here in this widget + * class or in the controller in case you want to reuse the same action for instance in the menu etc. * @api */ protected function addWidget($name, $method, $parameters = array()) { - // to be developer friendly we could check whether such a method exists (in controller or widget) and if - // not throw an exception so the developer does not have to handle with typos etc. I do not want to do this - // right now because of performance but if we add a development setting in config we could do such check $this->addWidgetWithCustomCategory($this->category, $name, $method, $parameters); } + /** + * Adds a widget with a custom category. By default all widgets that you define in your class will be added under + * the same category which is defined in the {@link $category} property. Sometimes you may have a widget that + * belongs to a different category where this method comes handy. It does the same as {@link addWidget()} but + * allows you to define the category name as well. + * @api + */ protected function addWidgetWithCustomCategory($category, $name, $method, $parameters = array()) { $this->checkIsValidWidget($name, $method); @@ -68,12 +81,17 @@ class Widgets } /** + * Here you can add one or multiple widgets. To do so call the method {@link addWidget()} or + * {@link addWidgetWithCustomCategory()}. * @api */ protected function init() { } + /** + * @ignore + */ public function getWidgets() { $this->widgets = array(); @@ -84,7 +102,12 @@ class Widgets } /** - * Configures the widgets. Here you can for instance remove widgets. + * Allows you to configure previously added widgets. + * For instance you can remove any widgets defined by any plugin by calling the + * {@link \Piwik\WidgetsList::remove()} method. + * + * @param WidgetsList $widgetsList + * @api */ public function configureWidgetsList(WidgetsList $widgetsList) { @@ -93,12 +116,16 @@ class Widgets /** * @return \Piwik\Plugin\Widgets[] + * @ignore */ public static function getAllWidgets() { return PluginManager::getInstance()->findComponents('Widgets', 'Piwik\\Plugin\\Widgets'); } + /** + * @ignore + */ public static function factory($module, $action) { if (empty($module) || empty($action)) { diff --git a/core/Profiler.php b/core/Profiler.php index e9297464ee..7468b1f5d6 100644 --- a/core/Profiler.php +++ b/core/Profiler.php @@ -8,6 +8,9 @@ */ namespace Piwik; +use Exception; +use XHProfRuns_Default; + /** * Class Profiler helps with measuring memory, and profiling the database. * To enable set in your config.ini.php @@ -22,6 +25,13 @@ namespace Piwik; class Profiler { /** + * Whether xhprof has been setup or not. + * + * @var bool + */ + private static $isXhprofSetup = false; + + /** * Returns memory usage * * @return string @@ -186,30 +196,37 @@ class Profiler * Initializes Profiling via XHProf. * See: https://github.com/piwik/piwik/blob/master/tests/README.xhprof.md */ - public static function setupProfilerXHProf($mainRun = false) + public static function setupProfilerXHProf($mainRun = false, $setupDuringTracking = false) { - if(SettingsServer::isTrackerApiRequest()) { + if (!$setupDuringTracking + && SettingsServer::isTrackerApiRequest() + ) { // do not profile Tracker return; } - $path = PIWIK_INCLUDE_PATH . '/tests/lib/xhprof-0.9.4/xhprof_lib/utils/xhprof_runs.php'; - - if(!file_exists($path)) { + if (self::$isXhprofSetup) { return; } - if(!function_exists('xhprof_enable')) { - return; + $xhProfPath = PIWIK_INCLUDE_PATH . '/vendor/facebook/xhprof/extension/modules/xhprof.so'; + if (!file_exists($xhProfPath)) { + throw new Exception("Cannot find xhprof, run 'composer install --dev' and build the extension."); + } + + if (!function_exists('xhprof_enable')) { + throw new Exception("Cannot find xhprof_enable, make sure to add 'extension=$xhProfPath' to your php.ini."); } - if(!is_writable(ini_get("xhprof.output_dir"))) { - throw new \Exception("The profiler output dir '" .ini_get("xhprof.output_dir"). "' should exist and be writable."); + $outputDir = ini_get("xhprof.output_dir"); + if (empty($outputDir)) { + throw new Exception("The profiler output dir is not set. Add 'xhprof.output_dir=...' to your php.ini."); + } + if (!is_writable($outputDir)) { + throw new Exception("The profiler output dir '" . ini_get("xhprof.output_dir") . "' should exist and be writable."); } - require_once $path; - require_once PIWIK_INCLUDE_PATH . '/tests/lib/xhprof-0.9.4/xhprof_lib/utils/xhprof_lib.php'; - if(!function_exists('xhprof_error')) { + if (!function_exists('xhprof_error')) { function xhprof_error($out) { echo substr($out, 0, 300) . '...'; } @@ -223,56 +240,89 @@ class Profiler xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY); - if($mainRun) { + $baseUrlStored = ""; + if ($mainRun) { self::setProfilingRunIds(array()); + + $baseUrlStored = SettingsPiwik::getPiwikUrl(); } - register_shutdown_function(function () use($profilerNamespace, $mainRun) { + register_shutdown_function(function () use($profilerNamespace, $mainRun, $baseUrlStored) { $xhprofData = xhprof_disable(); - $xhprofRuns = new \XHProfRuns_Default(); + $xhprofRuns = new XHProfRuns_Default(); $runId = $xhprofRuns->save_run($xhprofData, $profilerNamespace); - if(empty($runId)) { + if (empty($runId)) { die('could not write profiler run'); } - $runs = self::getProfilingRunIds(); - $runs[] = $runId; -// $weights = array_fill(0, count($runs), 1); -// $aggregate = xhprof_aggregate_runs($xhprofRuns, $runs, $weights, $profilerNamespace); -// $runId = $xhprofRuns->save_run($aggregate, $profilerNamespace); - - if($mainRun) { - $runIds = implode(',', $runs); + + $runs = Profiler::getProfilingRunIds(); + array_unshift($runs, $runId); + + if ($mainRun) { + Profiler::aggregateXhprofRuns($runs, $profilerNamespace, $saveTo = $runId); + $out = "\n\n"; $baseUrl = "http://" . @$_SERVER['HTTP_HOST'] . "/" . @$_SERVER['REQUEST_URI']; - $baseUrlStored = SettingsPiwik::getPiwikUrl(); - if(strlen($baseUrlStored) > strlen($baseUrl)) { + if (strlen($baseUrlStored) > strlen($baseUrl)) { $baseUrl = $baseUrlStored; } - $baseUrl = "\n" . $baseUrl - ."tests/lib/xhprof-0.9.4/xhprof_html/?source=$profilerNamespace&run="; - - $out .= "Profiler report is available at:"; - $out .= $baseUrl . $runId; - if($runId != $runIds) { - $out .= "\n\nProfiler Report aggregating all runs triggered from this process: "; - $out .= $baseUrl . $runIds; - } + $baseUrl = $baseUrlStored . "vendor/facebook/xhprof/xhprof_html/?source=$profilerNamespace&run=$runId"; + + $out .= "Profiler report is available at:\n"; + $out .= $baseUrl; $out .= "\n\n"; - echo ($out); + + echo $out; } else { - self::setProfilingRunIds($runs); + Profiler::setProfilingRunIds($runs); } }); + + self::$isXhprofSetup = true; + } + + /** + * Aggregates xhprof runs w/o normalizing (xhprof_aggregate_runs will always average data which + * does not fit Piwik's use case). + */ + public static function aggregateXhprofRuns($runIds, $profilerNamespace, $saveToRunId) + { + $xhprofRuns = new XHProfRuns_Default(); + + $aggregatedData = array(); + + foreach ($runIds as $runId) { + $xhprofRunData = $xhprofRuns->get_run($runId, $profilerNamespace, $description); + + foreach ($xhprofRunData as $key => $data) { + if (empty($aggregatedData[$key])) { + $aggregatedData[$key] = $data; + } else { + // don't aggregate main() metrics since only the super run has the correct metrics for the entire run + if ($key == "main()") { + continue; + } + + $aggregatedData[$key]["ct"] += $data["ct"]; // call count + $aggregatedData[$key]["wt"] += $data["wt"]; // incl. wall time + $aggregatedData[$key]["cpu"] += $data["cpu"]; // cpu time + $aggregatedData[$key]["mu"] += $data["mu"]; // memory usage + $aggregatedData[$key]["pmu"] = max($aggregatedData[$key]["pmu"], $data["pmu"]); // peak mem usage + } + } + } + + $xhprofRuns->save_run($aggregatedData, $profilerNamespace, $saveToRunId); } - private static function setProfilingRunIds($ids) + public static function setProfilingRunIds($ids) { file_put_contents( self::getPathToXHProfRunIds(), json_encode($ids) ); @chmod(self::getPathToXHProfRunIds(), 0777); } - private static function getProfilingRunIds() + public static function getProfilingRunIds() { $runIds = file_get_contents( self::getPathToXHProfRunIds() ); $array = json_decode($runIds, $assoc = true); diff --git a/core/Tracker.php b/core/Tracker.php index 3b6e5ba178..ef9c7d7e4f 100644 --- a/core/Tracker.php +++ b/core/Tracker.php @@ -227,6 +227,9 @@ class Tracker */ public function main($args = null) { + if(!SettingsPiwik::isPiwikInstalled()) { + return $this->handleEmptyRequest(); + } try { $tokenAuth = $this->initRequests($args); } catch (Exception $ex) { @@ -250,7 +253,7 @@ class Tracker } } else { - $this->handleEmptyRequest(new Request($_GET + $_POST)); + $this->handleEmptyRequest(); } Piwik::postEvent('Tracker.end'); @@ -715,8 +718,11 @@ class Tracker } } - protected function handleEmptyRequest(Request $request) + protected function handleEmptyRequest(Request $request = null) { + if(is_null($request)) { + $request = new Request($_GET + $_POST); + } $countParameters = $request->getParamsCount(); if ($countParameters == 0) { $this->setState(self::STATE_EMPTY_REQUEST); diff --git a/core/Tracker/GoalManager.php b/core/Tracker/GoalManager.php index 12e3865d2c..1a0944ad9a 100644 --- a/core/Tracker/GoalManager.php +++ b/core/Tracker/GoalManager.php @@ -805,6 +805,9 @@ class GoalManager */ protected function isUrlMatchingGoal($goal, $pattern_type, $url) { + $url = Common::unsanitizeInputValue($url); + $goal['pattern'] = Common::unsanitizeInputValue($goal['pattern']); + switch ($pattern_type) { case 'regex': $pattern = $goal['pattern']; diff --git a/core/Tracker/Visit.php b/core/Tracker/Visit.php index 69e791992f..90b58a88b0 100644 --- a/core/Tracker/Visit.php +++ b/core/Tracker/Visit.php @@ -562,6 +562,12 @@ class Visit implements VisitInterface protected function getAllVisitDimensions() { $dimensions = VisitDimension::getAllDimensions(); + + $dimensionNames = array(); + foreach($dimensions as $dimension) { + $dimensionNames[] = $dimension->getColumnName(); + } + Common::printDebug("Following dimensions have been collected from plugins: " . implode(", ", $dimensionNames)); return $dimensions; } } diff --git a/core/Translate/Validate/CoreTranslations.php b/core/Translate/Validate/CoreTranslations.php index 9fbfc39b8d..bb52dc1ec8 100644 --- a/core/Translate/Validate/CoreTranslations.php +++ b/core/Translate/Validate/CoreTranslations.php @@ -18,7 +18,6 @@ class CoreTranslations extends ValidateAbstract /** * Error States */ - const ERRORSTATE_MINIMUMTRANSLATIONS = 'At least 250 translations required'; const ERRORSTATE_LOCALEREQUIRED = 'Locale required'; const ERRORSTATE_TRANSLATORINFOREQUIRED = 'Translator info required'; const ERRORSTATE_TRANSLATOREMAILREQUIRED = 'Translator email required'; @@ -54,11 +53,6 @@ class CoreTranslations extends ValidateAbstract { $this->message = null; - if (250 > count($translations, COUNT_RECURSIVE)) { - $this->message = self::ERRORSTATE_MINIMUMTRANSLATIONS; - return false; - } - if (empty($translations['General']['Locale'])) { $this->message = self::ERRORSTATE_LOCALEREQUIRED; return false; diff --git a/core/Updates/2.6.0-b1.php b/core/Updates/2.6.0-b1.php new file mode 100644 index 0000000000..5819ca95f9 --- /dev/null +++ b/core/Updates/2.6.0-b1.php @@ -0,0 +1,30 @@ +<?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\Updates; + +use Piwik\Config; +use Piwik\Updates; + +/** + * Update for version 2.6.0-b1. + */ +class Updates_2_6_0_b1 extends Updates +{ + /** + * Here you can define any action that should be performed during the update. For instance executing SQL statements, + * renaming config entries, updating files, etc. + */ + static function update() + { + $config = Config::getInstance(); + $config->Plugins_Tracker = array(); + $config->forceSave(); + } +} diff --git a/core/Version.php b/core/Version.php index abd38d650f..843a22004b 100644 --- a/core/Version.php +++ b/core/Version.php @@ -21,5 +21,5 @@ final class Version * The current Piwik version. * @var string */ - const VERSION = '2.5.0'; + const VERSION = '2.6.0-b1'; } |