Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
authorThomas Steur <thomas.steur@gmail.com>2014-09-02 13:19:21 +0400
committerThomas Steur <thomas.steur@gmail.com>2014-09-02 13:19:21 +0400
commita6474178f9a770d56754636345082a3cc698eeb8 (patch)
tree894199addf0ce6f6c696c8fdb2735cdbcdb5757b /core
parente58a92d2512f7054c328f0e4370405d7bc0a17e8 (diff)
parentf5fc3c8e62e0a90a6100346cf203d4dc037de22f (diff)
Merge branch 'master' into 4996_content_tracking
Conflicts: js/piwik.js
Diffstat (limited to 'core')
-rw-r--r--core/API/ApiRenderer.php4
-rw-r--r--core/API/Request.php3
-rw-r--r--core/API/ResponseBuilder.php2
-rw-r--r--core/Cache/PersistentCache.php18
-rw-r--r--core/Config.php1
-rw-r--r--core/Date.php53
-rw-r--r--core/Error.php16
-rw-r--r--core/EventDispatcher.php32
-rw-r--r--core/FrontController.php1
-rw-r--r--core/Log.php14
-rw-r--r--core/Piwik.php8
-rw-r--r--core/Plugin/ConsoleCommand.php14
-rw-r--r--core/Plugin/Manager.php113
-rw-r--r--core/Plugin/Widgets.php39
-rw-r--r--core/Profiler.php128
-rw-r--r--core/Tracker.php10
-rw-r--r--core/Tracker/GoalManager.php3
-rw-r--r--core/Tracker/Visit.php6
-rw-r--r--core/Translate/Validate/CoreTranslations.php6
-rw-r--r--core/Updates/2.6.0-b1.php30
-rw-r--r--core/Version.php2
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 --&gt;<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';
}