PDF rausgenommen

This commit is contained in:
aschwarz
2023-01-23 11:03:31 +01:00
parent 82d562a322
commit a6523903eb
28078 changed files with 4247552 additions and 2 deletions

View File

@@ -0,0 +1,57 @@
<?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\Columns;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\ComputedMetric;
use Piwik\Plugin\Report;
/**
* A factory to create computed metrics.
*
* @api since Piwik 3.2.0
*/
class ComputedMetricFactory
{
/**
* @var MetricsList
*/
private $metricsList = null;
/**
* Generates a new report metric factory.
* @param MetricsList $list A report list instance
* @ignore
*/
public function __construct(MetricsList $list)
{
$this->metricsList = $list;
}
/**
* @return \Piwik\Plugin\ComputedMetric
*/
public function createComputedMetric($metricName1, $metricName2, $aggregation)
{
$metric1 = $this->metricsList->getMetric($metricName1);
if (!$metric1 instanceof ArchivedMetric || !$metric1->getDimension()) {
throw new \Exception('Only possible to create computed metric for an archived metric with a dimension');
}
$dimension1 = $metric1->getDimension();
$metric = new ComputedMetric($metricName1, $metricName2, $aggregation);
$metric->setCategory($dimension1->getCategoryId());
return $metric;
}
}

View File

@@ -0,0 +1,907 @@
<?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\Columns;
use Piwik\Common;
use Piwik\Db;
use Piwik\Piwik;
use Piwik\Plugin;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\ComponentFactory;
use Piwik\Plugin\Segment;
use Exception;
use Piwik\CacheId;
use Piwik\Cache as PiwikCache;
use Piwik\Plugin\Manager as PluginManager;
use Piwik\Metrics\Formatter;
/**
* @api
* @since 3.1.0
*/
abstract class Dimension
{
const COMPONENT_SUBNAMESPACE = 'Columns';
/**
* Segment type 'dimension'. Can be used along with {@link setType()}.
* @api
*/
const TYPE_DIMENSION = 'dimension';
const TYPE_BINARY = 'binary';
const TYPE_TEXT = 'text';
const TYPE_ENUM = 'enum';
const TYPE_MONEY = 'money';
const TYPE_BYTE = 'byte';
const TYPE_DURATION_MS = 'duration_ms';
const TYPE_DURATION_S = 'duration_s';
const TYPE_NUMBER = 'number';
const TYPE_FLOAT = 'float';
const TYPE_URL = 'url';
const TYPE_DATE = 'date';
const TYPE_TIME = 'time';
const TYPE_DATETIME = 'datetime';
const TYPE_TIMESTAMP = 'timestamp';
const TYPE_BOOL = 'bool';
const TYPE_PERCENT = 'percent';
/**
* This will be the name of the column in the database table if a $columnType is specified.
* @var string
* @api
*/
protected $columnName = '';
/**
* If a columnType is defined, we will create a column in the MySQL table having this type. Please make sure
* MySQL understands this type. Once you change the column type the Piwik platform will notify the user to
* perform an update which can sometimes take a long time so be careful when choosing the correct column type.
* @var string
* @api
*/
protected $columnType = '';
/**
* Holds an array of segment instances
* @var Segment[]
*/
protected $segments = array();
/**
* Defines what kind of data type this dimension holds. By default the type is auto-detected based on
* `$columnType` but sometimes it may be needed to correct this value. Depending on this type, a dimension will be
* formatted differently for example.
* @var string
* @api since Piwik 3.2.0
*/
protected $type = '';
/**
* Translation key for name singular
* @var string
*/
protected $nameSingular = '';
/**
* Translation key for name plural
* @var string
* @api since Piwik 3.2.0
*/
protected $namePlural = '';
/**
* Translation key for category
* @var string
*/
protected $category = '';
/**
* By defining a segment name a user will be able to filter their visitors by this column. If you do not want to
* define a segment for this dimension, simply leave the name empty.
* @api since Piwik 3.2.0
*/
protected $segmentName = '';
/**
* Sets a callback which will be executed when user will call for suggested values for segment.
*
* @var callable
* @api since Piwik 3.2.0
*/
protected $suggestedValuesCallback;
/**
* Here you should explain which values are accepted/useful for your segment, for example:
* "1, 2, 3, etc." or "comcast.net, proxad.net, etc.". If the value needs any special encoding you should mention
* this as well. For example "Any URL including protocol. The URL must be URL encoded."
*
* @var string
* @api since Piwik 3.2.0
*/
protected $acceptValues;
/**
* Defines to which column in the MySQL database the segment belongs (if one is conifugred). Defaults to
* `$this.dbTableName . '.'. $this.columnName` but you can customize it eg like `HOUR(log_visit.visit_last_action_time)`.
*
* @param string $sqlSegment
* @api since Piwik 3.2.0
*/
protected $sqlSegment;
/**
* Interesting when specifying a segment. Sometimes you want users to set segment values that differ from the way
* they are actually stored. For instance if you want to allow to filter by any URL than you might have to resolve
* this URL to an action id. Or a country name maybe has to be mapped to a 2 letter country code. You can do this by
* specifing either a callable such as `array('Classname', 'methodName')` or by passing a closure.
* There will be four values passed to the given closure or callable: `string $valueToMatch`, `string $segment`
* (see {@link setSegment()}), `string $matchType` (eg SegmentExpression::MATCH_EQUAL or any other match constant
* of this class) and `$segmentName`.
*
* If the closure returns NULL, then Piwik assumes the segment sub-string will not match any visitor.
*
* @var string|\Closure
* @api since Piwik 3.2.0
*/
protected $sqlFilter;
/**
* Similar to {@link $sqlFilter} you can map a given segment value to another value. For instance you could map
* "new" to 0, 'returning' to 1 and any other value to '2'. You can either define a callable or a closure. There
* will be only one value passed to the closure or callable which contains the value a user has set for this
* segment.
* @var string|array
* @api since Piwik 3.2.0
*/
protected $sqlFilterValue;
/**
* Defines whether this dimension (and segment based on this dimension) is available to anonymous users.
* @var bool
* @api since Piwik 3.2.0
*/
protected $allowAnonymous = true;
/**
* The name of the database table this dimension refers to
* @var string
* @api
*/
protected $dbTableName = '';
/**
* By default the metricId is automatically generated based on the dimensionId. This might sometimes not be as
* readable and quite long. If you want more expressive metric names like `nb_visits` compared to
* `nb_corehomevisitid`, you can eg set a metricId `visit`.
*
* @var string
* @api since Piwik 3.2.0
*/
protected $metricId = '';
/**
* To be implemented when a column references another column
* @return Join|null
* @api since Piwik 3.2.0
*/
public function getDbColumnJoin()
{
return null;
}
/**
* @return Discriminator|null
* @api since Piwik 3.2.0
*/
public function getDbDiscriminator()
{
return null;
}
/**
* To be implemented when a column represents an enum.
* @return array
* @api since Piwik 3.2.0
*/
public function getEnumColumnValues()
{
return array();
}
/**
* Get the metricId which is used to generate metric names based on this dimension.
* @return string
*/
public function getMetricId()
{
if (!empty($this->metricId)) {
return $this->metricId;
}
$id = $this->getId();
return str_replace(array('.', ' ', '-'), '_', strtolower($id));
}
/**
* Installs the action dimension in case it is not installed yet. The installation is already implemented based on
* the {@link $columnName} and {@link $columnType}. If you want to perform additional actions beside adding the
* column to the database - for instance adding an index - you can overwrite this method. We recommend to call
* this parent method to get the minimum required actions and then add further custom actions since this makes sure
* the column will be installed correctly. We also recommend to change the default install behavior only if really
* needed. FYI: We do not directly execute those alter table statements here as we group them together with several
* other alter table statements do execute those changes in one step which results in a faster installation. The
* column will be added to the `log_link_visit_action` MySQL table.
*
* Example:
* ```
public function install()
{
$changes = parent::install();
$changes['log_link_visit_action'][] = "ADD INDEX index_idsite_servertime ( idsite, server_time )";
return $changes;
}
```
*
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
* be executed on the given table. Example:
* ```
array(
'log_link_visit_action' => array("ADD COLUMN `$this->columnName` $this->columnType", "ADD INDEX ...")
);
```
* @api
*/
public function install()
{
if (empty($this->columnName) || empty($this->columnType) || empty($this->dbTableName)) {
return array();
}
// TODO if table does not exist, create it with a primary key, but at this point we cannot really create it
// cause we need to show the query in the UI first and user needs to be able to create table manually.
// we cannot return something like "create table " here as it would be returned for each table etc.
// we need to do this in column updater etc!
return array(
$this->dbTableName => array("ADD COLUMN `$this->columnName` $this->columnType")
);
}
/**
* Updates the action dimension in case the {@link $columnType} has changed. The update is already implemented based
* on the {@link $columnName} and {@link $columnType}. This method is intended not to overwritten by plugin
* developers as it is only supposed to make sure the column has the correct type. Adding additional custom "alter
* table" actions would not really work since they would be executed with every {@link $columnType} change. So
* adding an index here would be executed whenever the columnType changes resulting in an error if the index already
* exists. If an index needs to be added after the first version is released a plugin update class should be
* created since this makes sure it is only executed once.
*
* @return array An array containing the table name as key and an array of MySQL alter table statements that should
* be executed on the given table. Example:
* ```
array(
'log_link_visit_action' => array("MODIFY COLUMN `$this->columnName` $this->columnType", "DROP COLUMN ...")
);
```
* @ignore
*/
public function update()
{
if (empty($this->columnName) || empty($this->columnType) || empty($this->dbTableName)) {
return array();
}
return array(
$this->dbTableName => array("MODIFY COLUMN `$this->columnName` $this->columnType")
);
}
/**
* Uninstalls the dimension if a {@link $columnName} and {@link columnType} is set. In case you perform any custom
* actions during {@link install()} - for instance adding an index - you should make sure to undo those actions by
* overwriting this method. Make sure to call this parent method to make sure the uninstallation of the column
* will be done.
* @throws Exception
* @api
*/
public function uninstall()
{
if (empty($this->columnName) || empty($this->columnType) || empty($this->dbTableName)) {
return;
}
try {
$sql = "ALTER TABLE `" . Common::prefixTable($this->dbTableName) . "` DROP COLUMN `$this->columnName`";
Db::exec($sql);
} catch (Exception $e) {
if (!Db::get()->isErrNo($e, '1091')) {
throw $e;
}
}
}
/**
* Returns the ID of the category (typically a translation key).
* @return string
*/
public function getCategoryId()
{
return $this->category;
}
/**
* Returns the translated name of this dimension which is typically in singular.
*
* @return string
*/
public function getName()
{
if (!empty($this->nameSingular)) {
return Piwik::translate($this->nameSingular);
}
return $this->nameSingular;
}
/**
* Returns a translated name in plural for this dimension.
* @return string
* @api since Piwik 3.2.0
*/
public function getNamePlural()
{
if (!empty($this->namePlural)) {
return Piwik::translate($this->namePlural);
}
return $this->getName();
}
/**
* Defines whether an anonymous user is allowed to view this dimension
* @return bool
* @api since Piwik 3.2.0
*/
public function isAnonymousAllowed()
{
return $this->allowAnonymous;
}
/**
* Sets (overwrites) the SQL segment
* @param $segment
* @api since Piwik 3.2.0
*/
public function setSqlSegment($segment)
{
$this->sqlSegment = $segment;
}
/**
* Sets (overwrites the dimension type)
* @param $type
* @api since Piwik 3.2.0
*/
public function setType($type)
{
$this->type = $type;
}
/**
* A dimension should group values by using this method. Otherwise the same row may appear several times.
*
* @param mixed $value
* @param int $idSite
* @return mixed
* @api since Piwik 3.2.0
*/
public function groupValue($value, $idSite)
{
switch ($this->type) {
case Dimension::TYPE_URL:
return str_replace(array('http://', 'https://'), '', $value);
case Dimension::TYPE_BOOL:
return !empty($value) ? '1' : '0';
case Dimension::TYPE_DURATION_MS:
return number_format($value / 1000, 2); // because we divide we need to group them and cannot do this in formatting step
}
return $value;
}
/**
* Formats the dimension value. By default, the dimension is formatted based on the set dimension type.
*
* @param mixed $value
* @param int $idSite
* @param Formatter $formatter
* @return mixed
* @api since Piwik 3.2.0
*/
public function formatValue($value, $idSite, Formatter $formatter)
{
switch ($this->type) {
case Dimension::TYPE_BOOL:
if (empty($value)) {
return Piwik::translate('General_No');
}
return Piwik::translate('General_Yes');
case Dimension::TYPE_ENUM:
$values = $this->getEnumColumnValues();
if (isset($values[$value])) {
return $values[$value];
}
break;
case Dimension::TYPE_MONEY:
return $formatter->getPrettyMoney($value, $idSite);
case Dimension::TYPE_FLOAT:
return $formatter->getPrettyNumber((float) $value, $precision = 2);
case Dimension::TYPE_NUMBER:
return $formatter->getPrettyNumber($value);
case Dimension::TYPE_DURATION_S:
return $formatter->getPrettyTimeFromSeconds($value, $displayAsSentence = false);
case Dimension::TYPE_DURATION_MS:
return $formatter->getPrettyTimeFromSeconds($value, $displayAsSentence = true);
case Dimension::TYPE_PERCENT:
return $formatter->getPrettyPercentFromQuotient($value);
case Dimension::TYPE_BYTE:
return $formatter->getPrettySizeFromBytes($value);
}
return $value;
}
/**
* Overwrite this method to configure segments. To do so just create an instance of a {@link \Piwik\Plugin\Segment}
* class, configure it and call the {@link addSegment()} method. You can add one or more segments for this
* dimension. Example:
*
* ```
* $segment = new Segment();
* $segment->setSegment('exitPageUrl');
* $segment->setName('Actions_ColumnExitPageURL');
* $segment->setCategory('General_Visit');
* $this->addSegment($segment);
* ```
*/
protected function configureSegments()
{
if ($this->segmentName && $this->category
&& ($this->sqlSegment || ($this->columnName && $this->dbTableName))
&& $this->nameSingular) {
$segment = new Segment();
$this->addSegment($segment);
}
}
/**
* Configures metrics for this dimension.
*
* For certain dimension types, some metrics will be added automatically.
*
* @param MetricsList $metricsList
* @param DimensionMetricFactory $dimensionMetricFactory
*/
public function configureMetrics(MetricsList $metricsList, DimensionMetricFactory $dimensionMetricFactory)
{
if ($this->getMetricId() && $this->dbTableName && $this->columnName && $this->getNamePlural()) {
if (in_array($this->getType(), array(self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME, self::TYPE_TIMESTAMP))) {
// we do not generate any metrics from these types
return;
} elseif (in_array($this->getType(), array(self::TYPE_URL, self::TYPE_TEXT, self::TYPE_BINARY, self::TYPE_ENUM))) {
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_UNIQUE);
$metricsList->addMetric($metric);
} elseif (in_array($this->getType(), array(self::TYPE_BOOL))) {
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_SUM);
$metricsList->addMetric($metric);
} else {
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_SUM);
$metricsList->addMetric($metric);
$metric = $dimensionMetricFactory->createMetric(ArchivedMetric::AGGREGATION_MAX);
$metricsList->addMetric($metric);
}
}
}
/**
* Check whether a dimension has overwritten a specific method.
* @param $method
* @return bool
* @ignore
*/
public function hasImplementedEvent($method)
{
$method = new \ReflectionMethod($this, $method);
$declaringClass = $method->getDeclaringClass();
return 0 === strpos($declaringClass->name, 'Piwik\Plugins');
}
/**
* Adds a new segment. It automatically sets the SQL segment depending on the column name in case none is set
* already.
* @see \Piwik\Columns\Dimension::addSegment()
* @param Segment $segment
* @api
*/
protected function addSegment(Segment $segment)
{
if (!$segment->getSegment() && $this->segmentName) {
$segment->setSegment($this->segmentName);
}
if (!$segment->getType()) {
$metricTypes = array(self::TYPE_NUMBER, self::TYPE_FLOAT, self::TYPE_MONEY, self::TYPE_DURATION_S, self::TYPE_DURATION_MS);
if (in_array($this->getType(), $metricTypes, $strict = true)) {
$segment->setType(Segment::TYPE_METRIC);
} else {
$segment->setType(Segment::TYPE_DIMENSION);
}
}
if (!$segment->getCategoryId() && $this->category) {
$segment->setCategory($this->category);
}
if (!$segment->getName() && $this->nameSingular) {
$segment->setName($this->nameSingular);
}
$sqlSegment = $segment->getSqlSegment();
if (empty($sqlSegment) && !$segment->getUnionOfSegments()) {
if (!empty($this->sqlSegment)) {
$segment->setSqlSegment($this->sqlSegment);
} elseif ($this->dbTableName && $this->columnName) {
$segment->setSqlSegment($this->dbTableName . '.' . $this->columnName);
} else {
throw new Exception('Segment cannot be added because no sql segment is set');
}
}
if (!$this->suggestedValuesCallback) {
// we can generate effecient value callback for enums automatically
$enum = $this->getEnumColumnValues();
if (!empty($enum)) {
$this->suggestedValuesCallback = function ($idSite, $maxValuesToReturn) use ($enum) {
$values = array_values($enum);
return array_slice($values, 0, $maxValuesToReturn);
};
}
}
if (!$this->acceptValues) {
// we can generate accept values for enums automatically
$enum = $this->getEnumColumnValues();
if (!empty($enum)) {
$enumValues = array_values($enum);
$enumValues = array_slice($enumValues, 0, 20);
$this->acceptValues = 'Eg. ' . implode(', ', $enumValues);
};
}
if ($this->acceptValues && !$segment->getAcceptValues()) {
$segment->setAcceptedValues($this->acceptValues);
}
if (!$this->sqlFilterValue && !$segment->getSqlFilter() && !$segment->getSqlFilterValue()) {
// no sql filter configured, we try to configure automatically for enums
$enum = $this->getEnumColumnValues();
if (!empty($enum)) {
$this->sqlFilterValue = function ($value, $sqlSegmentName) use ($enum) {
if (isset($enum[$value])) {
return $value;
}
$id = array_search($value, $enum);
if ($id === false) {
$id = array_search(strtolower(trim(urldecode($value))), $enum);
if ($id === false) {
throw new \Exception("Invalid '$sqlSegmentName' segment value $value");
}
}
return $id;
};
};
}
if ($this->suggestedValuesCallback && !$segment->getSuggestedValuesCallback()) {
$segment->setSuggestedValuesCallback($this->suggestedValuesCallback);
}
if ($this->sqlFilterValue && !$segment->getSqlFilterValue()) {
$segment->setSqlFilterValue($this->sqlFilterValue);
}
if ($this->sqlFilter && !$segment->getSqlFilter()) {
$segment->setSqlFilter($this->sqlFilter);
}
if (!$this->allowAnonymous) {
$segment->setRequiresAtLeastViewAccess(true);
}
$this->segments[] = $segment;
}
/**
* Get the list of configured segments.
* @return Segment[]
* @ignore
*/
public function getSegments()
{
if (empty($this->segments)) {
$this->configureSegments();
}
return $this->segments;
}
/**
* Returns the name of the segment that this dimension defines
* @return string
* @api since Piwik 3.2.0
*/
public function getSegmentName()
{
return $this->segmentName;
}
/**
* Get the name of the dimension column.
* @return string
* @ignore
*/
public function getColumnName()
{
return $this->columnName;
}
/**
* Returns a sql segment expression for this dimension.
* @return string
* @api since Piwik 3.2.0
*/
public function getSqlSegment()
{
if (!empty($this->sqlSegment)) {
return $this->sqlSegment;
}
if ($this->dbTableName && $this->columnName) {
return $this->dbTableName . '.' . $this->columnName;
}
}
/**
* Check whether the dimension has a column type configured
* @return bool
* @ignore
*/
public function hasColumnType()
{
return !empty($this->columnType);
}
/**
* Returns the name of the database table this dimension belongs to.
* @return string
* @api since Piwik 3.2.0
*/
public function getDbTableName()
{
return $this->dbTableName;
}
/**
* Returns a unique string ID for this dimension. The ID is built using the namespaced class name
* of the dimension, but is modified to be more human readable.
*
* @return string eg, `"Referrers.Keywords"`
* @throws Exception if the plugin and simple class name of this instance cannot be determined.
* This would only happen if the dimension is located in the wrong directory.
* @api
*/
public function getId()
{
$className = get_class($this);
return $this->generateIdFromClass($className);
}
/**
* @param string $className
* @return string
* @throws Exception
* @ignore
*/
protected function generateIdFromClass($className)
{
// parse plugin name & dimension name
$regex = "/Piwik\\\\Plugins\\\\([^\\\\]+)\\\\" . self::COMPONENT_SUBNAMESPACE . "\\\\([^\\\\]+)/";
if (!preg_match($regex, $className, $matches)) {
throw new Exception("'$className' is located in the wrong directory.");
}
$pluginName = $matches[1];
$dimensionName = $matches[2];
return $pluginName . '.' . $dimensionName;
}
/**
* Gets an instance of all available visit, action and conversion dimension.
* @return Dimension[]
*/
public static function getAllDimensions()
{
$cacheId = CacheId::siteAware(CacheId::pluginAware('AllDimensions'));
$cache = PiwikCache::getTransientCache();
if (!$cache->contains($cacheId)) {
$plugins = PluginManager::getInstance()->getPluginsLoadedAndActivated();
$instances = array();
/**
* Triggered to add new dimensions that cannot be picked up automatically by the platform.
* This is useful if the plugin allows a user to create reports / dimensions dynamically. For example
* CustomDimensions or CustomVariables. There are a variable number of dimensions in this case and it
* wouldn't be really possible to create a report file for one of these dimensions as it is not known
* how many Custom Dimensions will exist.
*
* **Example**
*
* public function addDimension(&$dimensions)
* {
* $dimensions[] = new MyCustomDimension();
* }
*
* @param Dimension[] $reports An array of dimensions
*/
Piwik::postEvent('Dimension.addDimensions', array(&$instances));
foreach ($plugins as $plugin) {
foreach (self::getDimensions($plugin) as $instance) {
$instances[] = $instance;
}
}
/**
* Triggered to filter / restrict dimensions.
*
* **Example**
*
* public function filterDimensions(&$dimensions)
* {
* foreach ($dimensions as $index => $dimension) {
* if ($dimension->getName() === 'Page URL') {}
* unset($dimensions[$index]); // remove this dimension
* }
* }
* }
*
* @param Dimension[] $dimensions An array of dimensions
*/
Piwik::postEvent('Dimension.filterDimensions', array(&$instances));
$cache->save($cacheId, $instances);
}
return $cache->fetch($cacheId);
}
public static function getDimensions(Plugin $plugin)
{
$columns = $plugin->findMultipleComponents('Columns', '\\Piwik\\Columns\\Dimension');
$instances = array();
foreach ($columns as $colum) {
$instances[] = new $colum();
}
return $instances;
}
/**
* Creates a Dimension instance from a string ID (see {@link getId()}).
*
* @param string $dimensionId See {@link getId()}.
* @return Dimension|null The created instance or null if there is no Dimension for
* $dimensionId or if the plugin that contains the Dimension is
* not loaded.
* @api
* @deprecated Please use DimensionsProvider::factory instead
*/
public static function factory($dimensionId)
{
list($module, $dimension) = explode('.', $dimensionId);
return ComponentFactory::factory($module, $dimension, __CLASS__);
}
/**
* Returns the name of the plugin that contains this Dimension.
*
* @return string
* @throws Exception if the Dimension is not located within a Plugin module.
* @api
*/
public function getModule()
{
$id = $this->getId();
if (empty($id)) {
throw new Exception("Invalid dimension ID: '$id'.");
}
$parts = explode('.', $id);
return reset($parts);
}
/**
* Returns the type of the dimension which defines what kind of value this dimension stores.
* @return string
* @api since Piwik 3.2.0
*/
public function getType()
{
if (!empty($this->type)) {
return $this->type;
}
if ($this->getDbColumnJoin()) {
// best guess
return self::TYPE_TEXT;
}
if ($this->getEnumColumnValues()) {
// best guess
return self::TYPE_ENUM;
}
if (!empty($this->columnType)) {
// best guess
$type = strtolower($this->columnType);
if (strpos($type, 'datetime') !== false) {
return self::TYPE_DATETIME;
} elseif (strpos($type, 'timestamp') !== false) {
return self::TYPE_TIMESTAMP;
} elseif (strpos($type, 'date') !== false) {
return self::TYPE_DATE;
} elseif (strpos($type, 'time') !== false) {
return self::TYPE_TIME;
} elseif (strpos($type, 'float') !== false) {
return self::TYPE_FLOAT;
} elseif (strpos($type, 'decimal') !== false) {
return self::TYPE_FLOAT;
} elseif (strpos($type, 'int') !== false) {
return self::TYPE_NUMBER;
} elseif (strpos($type, 'binary') !== false) {
return self::TYPE_BINARY;
}
}
return self::TYPE_TEXT;
}
/**
* Get the version of the dimension which is used for update checks.
* @return string
* @ignore
*/
public function getVersion()
{
return $this->columnType;
}
}

View File

@@ -0,0 +1,122 @@
<?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\Columns;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\ComputedMetric;
use Piwik\Plugin\Report;
/**
* A factory to create metrics from a dimension.
*
* @api since Piwik 3.2.0
*/
class DimensionMetricFactory
{
/**
* @var Dimension
*/
private $dimension = null;
/**
* Generates a new dimension metric factory.
* @param Dimension $dimension A dimension instance the created metrics should be based on.
*/
public function __construct(Dimension $dimension)
{
$this->dimension = $dimension;
}
/**
* @return ArchivedMetric
*/
public function createCustomMetric($metricName, $readableName, $aggregation, $documentation = '')
{
if (!$this->dimension->getDbTableName() || !$this->dimension->getColumnName()) {
throw new \Exception(sprintf('Cannot make metric from dimension %s because DB table or column missing', $this->dimension->getId()));
}
$metric = new ArchivedMetric($this->dimension, $aggregation);
$metric->setType($this->dimension->getType());
$metric->setName($metricName);
$metric->setTranslatedName($readableName);
$metric->setDocumentation($documentation);
$metric->setCategory($this->dimension->getCategoryId());
return $metric;
}
/**
* @return \Piwik\Plugin\ComputedMetric
*/
public function createComputedMetric($metricName1, $metricName2, $aggregation)
{
// We cannot use reuse ComputedMetricFactory here as it would result in an endless loop since ComputedMetricFactory
// requires a MetricsList which is just being built here...
$metric = new ComputedMetric($metricName1, $metricName2, $aggregation);
$metric->setCategory($this->dimension->getCategoryId());
return $metric;
}
/**
* @return ArchivedMetric
*/
public function createMetric($aggregation)
{
$dimension = $this->dimension;
if (!$dimension->getNamePlural()) {
throw new \Exception(sprintf('No metric can be created for this dimension %s automatically because no $namePlural is set.', $dimension->getId()));
}
$prefix = '';
$translatedName = $dimension->getNamePlural();
$documentation = '';
switch ($aggregation) {
case ArchivedMetric::AGGREGATION_COUNT;
$prefix = ArchivedMetric::AGGREGATION_COUNT_PREFIX;
$translatedName = $dimension->getNamePlural();
$documentation = Piwik::translate('General_ComputedMetricCountDocumentation', $dimension->getNamePlural());
break;
case ArchivedMetric::AGGREGATION_SUM;
$prefix = ArchivedMetric::AGGREGATION_SUM_PREFIX;
$translatedName = Piwik::translate('General_ComputedMetricSum', $dimension->getNamePlural());
$documentation = Piwik::translate('General_ComputedMetricSumDocumentation', $dimension->getNamePlural());
break;
case ArchivedMetric::AGGREGATION_MAX;
$prefix = ArchivedMetric::AGGREGATION_MAX_PREFIX;
$translatedName = Piwik::translate('General_ComputedMetricMax', $dimension->getNamePlural());
$documentation = Piwik::translate('General_ComputedMetricMaxDocumentation', $dimension->getNamePlural());
break;
case ArchivedMetric::AGGREGATION_MIN;
$prefix = ArchivedMetric::AGGREGATION_MIN_PREFIX;
$translatedName = Piwik::translate('General_ComputedMetricMin', $dimension->getNamePlural());
$documentation = Piwik::translate('General_ComputedMetricMinDocumentation', $dimension->getNamePlural());
break;
case ArchivedMetric::AGGREGATION_UNIQUE;
$prefix = ArchivedMetric::AGGREGATION_UNIQUE_PREFIX;
$translatedName = Piwik::translate('General_ComputedMetricUniqueCount', $dimension->getNamePlural());
$documentation = Piwik::translate('General_ComputedMetricUniqueCountDocumentation', $dimension->getNamePlural());
break;
case ArchivedMetric::AGGREGATION_COUNT_WITH_NUMERIC_VALUE;
$prefix = ArchivedMetric::AGGREGATION_COUNT_WITH_NUMERIC_VALUE_PREFIX;
$translatedName = Piwik::translate('General_ComputedMetricCountWithValue', $dimension->getName());
$documentation = Piwik::translate('General_ComputedMetricCountWithValueDocumentation', $dimension->getName());
break;
}
$metricId = strtolower($dimension->getMetricId());
return $this->createCustomMetric($prefix . $metricId, $translatedName, $aggregation, $documentation);
}
}

View File

@@ -0,0 +1,62 @@
<?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\Columns;
use Piwik\CacheId;
use Piwik\Cache as PiwikCache;
class DimensionsProvider
{
/**
* @param $dimensionId
* @return Dimension
*/
public function factory($dimensionId)
{
$listDimensions = self::getMapOfNameToDimension();
if (empty($listDimensions) || !is_array($listDimensions) || !$dimensionId || !array_key_exists($dimensionId, $listDimensions)) {
return null;
}
return $listDimensions[$dimensionId];
}
private static function getMapOfNameToDimension()
{
$cacheId = CacheId::siteAware(CacheId::pluginAware('DimensionFactoryMap'));
$cache = PiwikCache::getTransientCache();
if ($cache->contains($cacheId)) {
$mapIdToDimension = $cache->fetch($cacheId);
} else {
$dimensions = new static();
$dimensions = $dimensions->getAllDimensions();
$mapIdToDimension = array();
foreach ($dimensions as $dimension) {
$mapIdToDimension[$dimension->getId()] = $dimension;
}
$cache->save($cacheId, $mapIdToDimension);
}
return $mapIdToDimension;
}
/**
* Returns a list of all available dimensions.
* @return Dimension[]
*/
public function getAllDimensions()
{
return Dimension::getAllDimensions();
}
}

View File

@@ -0,0 +1,75 @@
<?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\Columns;
use Exception;
use Piwik\Plugins\Actions\Actions\ActionSiteSearch;
/**
* @api
* @since 3.1.0
*/
class Discriminator
{
private $table;
private $discriminatorColumn;
private $discriminatorValue;
/**
* Join constructor.
* @param string $table unprefixed table name
* @param null|string $discriminatorColumn
* @param null|int $discriminatorValue should be only hard coded, safe values.
* @throws Exception
*/
public function __construct($table, $discriminatorColumn = null, $discriminatorValue = null)
{
if (empty($discriminatorColumn) || !isset($discriminatorValue)) {
throw new Exception('Both discriminatorColumn and discriminatorValue need to be defined');
}
$this->table = $table;
$this->discriminatorColumn = $discriminatorColumn;
$this->discriminatorValue = $discriminatorValue;
if (!$this->isValid()) {
// if adding another string value please post an event instead to get a list of allowed values
throw new Exception('$discriminatorValue needs to be null or numeric');
}
}
public function isValid()
{
return isset($this->discriminatorColumn)
&& (is_numeric($this->discriminatorValue) || $this->discriminatorValue == ActionSiteSearch::CVAR_KEY_SEARCH_CATEGORY);
}
/**
* @return string
*/
public function getTable()
{
return $this->table;
}
/**
* @return string
*/
public function getColumn()
{
return $this->discriminatorColumn;
}
/**
* @return int|null
*/
public function getValue()
{
return $this->discriminatorValue;
}
}

View File

@@ -0,0 +1,61 @@
<?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\Columns;
use Exception;
/**
* @api
* @since 3.1.0
*/
class Join
{
private $table;
private $column;
private $targetColumn;
/**
* Join constructor.
* @param $table
* @param $column
* @param $targetColumn
* @throws Exception
*/
public function __construct($table, $column, $targetColumn)
{
$this->table = $table;
$this->column = $column;
$this->targetColumn = $targetColumn;
}
/**
* @return string
*/
public function getTable()
{
return $this->table;
}
/**
* @return string
*/
public function getColumn()
{
return $this->column;
}
/**
* @return string
*/
public function getTargetColumn()
{
return $this->targetColumn;
}
}

View File

@@ -0,0 +1,24 @@
<?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\Columns\Join;
use Piwik\Columns;
/**
* @api
* @since 3.1.0
*/
class ActionNameJoin extends Columns\Join
{
public function __construct()
{
return parent::__construct('log_action', 'idaction', 'name');
}
}

View File

@@ -0,0 +1,24 @@
<?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\Columns\Join;
use Piwik\Columns;
/**
* @api
* @since 3.1.0
*/
class GoalNameJoin extends Columns\Join
{
public function __construct()
{
return parent::__construct('goal', 'idgoal', 'name');
}
}

View File

@@ -0,0 +1,24 @@
<?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\Columns\Join;
use Piwik\Columns;
/**
* @api
* @since 3.1.0
*/
class SiteNameJoin extends Columns\Join
{
public function __construct()
{
return parent::__construct('site', 'idsite', 'name');
}
}

View File

@@ -0,0 +1,191 @@
<?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\Columns;
use Piwik\Cache;
use Piwik\CacheId;
use Piwik\Piwik;
use Piwik\Plugin\ArchivedMetric;
use Piwik\Plugin\Metric;
use Piwik\Plugin\ProcessedMetric;
/**
* Manages the global list of metrics that can be used in reports.
*
* Metrics are added automatically by dimensions as well as through the {@hook Metric.addMetrics} and
* {@hook Metric.addComputedMetrics} and filtered through the {@hook Metric.filterMetrics} event.
* Observers for this event should call the {@link addMetric()} method to add metrics or use any of the other
* methods to remove metrics.
*
* @api since Piwik 3.2.0
*/
class MetricsList
{
/**
* List of metrics
*
* @var Metric[]
*/
private $metrics = array();
private $metricsByNameCache = array();
/**
* @param Metric $metric
*/
public function addMetric(Metric $metric)
{
$this->metrics[] = $metric;
$this->metricsByNameCache = array();
}
/**
* Get all available metrics.
*
* @return Metric[]
*/
public function getMetrics()
{
return $this->metrics;
}
/**
* Removes one or more metrics from the metrics list.
*
* @param string $metricCategory The metric category id. Can be a translation token eg 'General_Visits'
* see {@link Metric::getCategory()}.
* @param string|false $metricName The name of the metric to remove eg 'nb_visits'.
* If not supplied, all metrics within that category will be removed.
*/
public function remove($metricCategory, $metricName = false)
{
foreach ($this->metrics as $index => $metric) {
if ($metric->getCategoryId() === $metricCategory) {
if (!$metricName || $metric->getName() === $metricName) {
unset($this->metrics[$index]);
$this->metricsByNameCache = array();
}
}
}
}
/**
* @param string $metricName
* @return Metric|ArchivedMetric|null
*/
public function getMetric($metricName)
{
if (empty($this->metricsByNameCache)) {
// this method might be called quite often... eg when having heaps of goals... need to cache it
foreach ($this->metrics as $index => $metric) {
$this->metricsByNameCache[$metric->getName()] = $metric;
}
}
if (!empty($this->metricsByNameCache[$metricName])) {
return $this->metricsByNameCache[$metricName];
}
return null;
}
/**
* Get all metrics defined in the Piwik platform.
* @ignore
* @return static
*/
public static function get()
{
$cache = Cache::getTransientCache();
$cacheKey = CacheId::siteAware('MetricsList');
if ($cache->contains($cacheKey)) {
return $cache->fetch($cacheKey);
}
$list = new static;
/**
* Triggered to add new metrics that cannot be picked up automatically by the platform.
* This is useful if the plugin allows a user to create metrics dynamically. For example
* CustomDimensions or CustomVariables.
*
* **Example**
*
* public function addMetric(&$list)
* {
* $list->addMetric(new MyCustomMetric());
* }
*
* @param MetricsList $list An instance of the MetricsList. You can add metrics to the list this way.
*/
Piwik::postEvent('Metric.addMetrics', array($list));
$dimensions = Dimension::getAllDimensions();
foreach ($dimensions as $dimension) {
$factory = new DimensionMetricFactory($dimension);
$dimension->configureMetrics($list, $factory);
}
$computedFactory = new ComputedMetricFactory($list);
/**
* Triggered to add new metrics that cannot be picked up automatically by the platform.
* This is useful if the plugin allows a user to create metrics dynamically. For example
* CustomDimensions or CustomVariables.
*
* **Example**
*
* public function addMetric(&$list)
* {
* $list->addMetric(new MyCustomMetric());
* }
*
* @param MetricsList $list An instance of the MetricsList. You can add metrics to the list this way.
*/
Piwik::postEvent('Metric.addComputedMetrics', array($list, $computedFactory));
/**
* Triggered to filter metrics.
*
* **Example**
*
* public function removeMetrics(Piwik\Columns\MetricsList $list)
* {
* $list->remove($category='General_Visits'); // remove all metrics having this category
* }
*
* @param MetricsList $list An instance of the MetricsList. You can change the list of metrics this way.
*/
Piwik::postEvent('Metric.filterMetrics', array($list));
$availableMetrics = array();
foreach ($list->getMetrics() as $metric) {
$availableMetrics[] = $metric->getName();
}
foreach ($list->metrics as $index => $metric) {
if ($metric instanceof ProcessedMetric) {
$depMetrics = $metric->getDependentMetrics();
if (is_array($depMetrics)) {
foreach ($depMetrics as $depMetric) {
if (!in_array($depMetric, $availableMetrics, $strict = true)) {
unset($list->metrics[$index]); // not resolvable metric
}
}
}
}
}
$cache->save($cacheKey, $list);
return $list;
}
}

View File

@@ -0,0 +1,380 @@
<?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\Columns;
use Piwik\Common;
use Piwik\DbHelper;
use Piwik\Plugin\Dimension\ActionDimension;
use Piwik\Plugin\Dimension\VisitDimension;
use Piwik\Plugin\Dimension\ConversionDimension;
use Piwik\Db;
use Piwik\Plugin\Manager;
use Piwik\Updater as PiwikUpdater;
use Piwik\Filesystem;
use Piwik\Cache as PiwikCache;
use Piwik\Updater\Migration;
/**
* Class that handles dimension updates
*/
class Updater extends \Piwik\Updates
{
private static $cacheId = 'AllDimensionModifyTime';
/**
* @var VisitDimension[]
*/
public $visitDimensions;
/**
* @var ActionDimension[]
*/
private $actionDimensions;
/**
* @var ConversionDimension[]
*/
private $conversionDimensions;
/**
* @param VisitDimension[]|null $visitDimensions
* @param ActionDimension[]|null $actionDimensions
* @param ConversionDimension[]|null $conversionDimensions
*/
public function __construct(array $visitDimensions = null, array $actionDimensions = null, array $conversionDimensions = null)
{
$this->visitDimensions = $visitDimensions;
$this->actionDimensions = $actionDimensions;
$this->conversionDimensions = $conversionDimensions;
}
/**
* @param PiwikUpdater $updater
* @return Migration\Db[]
*/
public function getMigrationQueries(PiwikUpdater $updater)
{
$sqls = array();
$changingColumns = $this->getUpdates($updater);
$errorCodes = array(
Migration\Db\Sql::ERROR_CODE_COLUMN_NOT_EXISTS,
Migration\Db\Sql::ERROR_CODE_DUPLICATE_COLUMN
);
foreach ($changingColumns as $table => $columns) {
if (empty($columns) || !is_array($columns)) {
continue;
}
$sql = "ALTER TABLE `" . Common::prefixTable($table) . "` " . implode(', ', $columns);
$sqls[] = new Migration\Db\Sql($sql, $errorCodes);
}
return $sqls;
}
public function doUpdate(PiwikUpdater $updater)
{
$updater->executeMigrationQueries(__FILE__, $this->getMigrationQueries($updater));
}
private function getVisitDimensions()
{
// see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
if (!isset($this->visitDimensions)) {
$this->visitDimensions = VisitDimension::getAllDimensions();
}
return $this->visitDimensions;
}
private function getActionDimensions()
{
// see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
if (!isset($this->actionDimensions)) {
$this->actionDimensions = ActionDimension::getAllDimensions();
}
return $this->actionDimensions;
}
private function getConversionDimensions()
{
// see eg https://github.com/piwik/piwik/issues/8399 we fetch them only on demand to improve performance
if (!isset($this->conversionDimensions)) {
$this->conversionDimensions = ConversionDimension::getAllDimensions();
}
return $this->conversionDimensions;
}
private function getUpdates(PiwikUpdater $updater)
{
$visitColumns = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
$actionColumns = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
$conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion'));
$allUpdatesToRun = array();
foreach ($this->getVisitDimensions() as $dimension) {
$updates = $this->getUpdatesForDimension($updater, $dimension, 'log_visit.', $visitColumns);
$allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
}
foreach ($this->getActionDimensions() as $dimension) {
$updates = $this->getUpdatesForDimension($updater, $dimension, 'log_link_visit_action.', $actionColumns);
$allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
}
foreach ($this->getConversionDimensions() as $dimension) {
$updates = $this->getUpdatesForDimension($updater, $dimension, 'log_conversion.', $conversionColumns);
$allUpdatesToRun = $this->mixinUpdates($allUpdatesToRun, $updates);
}
return $allUpdatesToRun;
}
/**
* @param ActionDimension|ConversionDimension|VisitDimension $dimension
* @param string $componentPrefix
* @return array
*/
private function getUpdatesForDimension(PiwikUpdater $updater, $dimension, $componentPrefix, $existingColumnsInDb)
{
$column = $dimension->getColumnName();
$componentName = $componentPrefix . $column;
if (!$updater->hasNewVersion($componentName)) {
return array();
}
if (array_key_exists($column, $existingColumnsInDb)) {
$sqlUpdates = $dimension->update();
} else {
$sqlUpdates = $dimension->install();
}
return $sqlUpdates;
}
private function mixinUpdates($allUpdatesToRun, $updatesFromDimension)
{
if (!empty($updatesFromDimension)) {
foreach ($updatesFromDimension as $table => $col) {
if (empty($allUpdatesToRun[$table])) {
$allUpdatesToRun[$table] = $col;
} else {
$allUpdatesToRun[$table] = array_merge($allUpdatesToRun[$table], $col);
}
}
}
return $allUpdatesToRun;
}
public function getAllVersions(PiwikUpdater $updater)
{
// to avoid having to load all dimensions on each request we check if there were any changes on the file system
// can easily save > 100ms for each request
$cachedTimes = self::getCachedDimensionFileChanges();
$currentTimes = self::getCurrentDimensionFileChanges();
$diff = array_diff_assoc($currentTimes, $cachedTimes);
if (empty($diff)) {
return array();
}
$versions = array();
$visitColumns = DbHelper::getTableColumns(Common::prefixTable('log_visit'));
$actionColumns = DbHelper::getTableColumns(Common::prefixTable('log_link_visit_action'));
$conversionColumns = DbHelper::getTableColumns(Common::prefixTable('log_conversion'));
foreach ($this->getVisitDimensions() as $dimension) {
$versions = $this->mixinVersions($updater, $dimension, VisitDimension::INSTALLER_PREFIX, $visitColumns, $versions);
}
foreach ($this->getActionDimensions() as $dimension) {
$versions = $this->mixinVersions($updater, $dimension, ActionDimension::INSTALLER_PREFIX, $actionColumns, $versions);
}
foreach ($this->getConversionDimensions() as $dimension) {
$versions = $this->mixinVersions($updater, $dimension, ConversionDimension::INSTALLER_PREFIX, $conversionColumns, $versions);
}
return $versions;
}
/**
* @param PiwikUpdater $updater
* @param Dimension $dimension
* @param string $componentPrefix
* @param array $columns
* @param array $versions
* @return array The modified versions array
*/
private function mixinVersions(PiwikUpdater $updater, $dimension, $componentPrefix, $columns, $versions)
{
$columnName = $dimension->getColumnName();
// dimensions w/o columns do not need DB updates
if (!$columnName || !$dimension->hasColumnType()) {
return $versions;
}
$component = $componentPrefix . $columnName;
$version = $dimension->getVersion();
// if the column exists in the table, but has no associated version, and was one of the core columns
// that was moved when the dimension refactor took place, then:
// - set the installed version in the DB to the current code version
// - and do not check for updates since we just set the version to the latest
if (array_key_exists($columnName, $columns)
&& false === $updater->getCurrentComponentVersion($component)
&& self::wasDimensionMovedFromCoreToPlugin($component, $version)
) {
$updater->markComponentSuccessfullyUpdated($component, $version);
return $versions;
}
$versions[$component] = $version;
return $versions;
}
public static function isDimensionComponent($name)
{
return 0 === strpos($name, 'log_visit.')
|| 0 === strpos($name, 'log_conversion.')
|| 0 === strpos($name, 'log_conversion_item.')
|| 0 === strpos($name, 'log_link_visit_action.');
}
public static function wasDimensionMovedFromCoreToPlugin($name, $version)
{
// maps names of core dimension columns that were part of the original dimension refactor with their
// initial "version" strings. The '1' that is sometimes appended to the end of the string (sometimes seen as
// NULL1) is from individual dimension "versioning" logic (eg, see VisitDimension::getVersion())
$initialCoreDimensionVersions = array(
'log_visit.config_resolution' => 'VARCHAR(9) NOT NULL',
'log_visit.config_device_brand' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
'log_visit.config_device_model' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
'log_visit.config_windowsmedia' => 'TINYINT(1) NOT NULL',
'log_visit.config_silverlight' => 'TINYINT(1) NOT NULL',
'log_visit.config_java' => 'TINYINT(1) NOT NULL',
'log_visit.config_gears' => 'TINYINT(1) NOT NULL',
'log_visit.config_pdf' => 'TINYINT(1) NOT NULL',
'log_visit.config_quicktime' => 'TINYINT(1) NOT NULL',
'log_visit.config_realplayer' => 'TINYINT(1) NOT NULL',
'log_visit.config_device_type' => 'TINYINT( 100 ) NULL DEFAULT NULL',
'log_visit.visitor_localtime' => 'TIME NOT NULL',
'log_visit.location_region' => 'char(2) DEFAULT NULL1',
'log_visit.visitor_days_since_last' => 'SMALLINT(5) UNSIGNED NOT NULL',
'log_visit.location_longitude' => 'float(10, 6) DEFAULT NULL1',
'log_visit.visit_total_events' => 'SMALLINT(5) UNSIGNED NOT NULL',
'log_visit.config_os_version' => 'VARCHAR( 100 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL',
'log_visit.location_city' => 'varchar(255) DEFAULT NULL1',
'log_visit.location_country' => 'CHAR(3) NOT NULL1',
'log_visit.location_latitude' => 'float(10, 6) DEFAULT NULL1',
'log_visit.config_flash' => 'TINYINT(1) NOT NULL',
'log_visit.config_director' => 'TINYINT(1) NOT NULL',
'log_visit.visit_total_time' => 'SMALLINT(5) UNSIGNED NOT NULL',
'log_visit.visitor_count_visits' => 'SMALLINT(5) UNSIGNED NOT NULL1',
'log_visit.visit_entry_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL',
'log_visit.visit_entry_idaction_url' => 'INTEGER(11) UNSIGNED NOT NULL',
'log_visit.visitor_returning' => 'TINYINT(1) NOT NULL1',
'log_visit.visitor_days_since_order' => 'SMALLINT(5) UNSIGNED NOT NULL1',
'log_visit.visit_goal_buyer' => 'TINYINT(1) NOT NULL',
'log_visit.visit_first_action_time' => 'DATETIME NOT NULL',
'log_visit.visit_goal_converted' => 'TINYINT(1) NOT NULL',
'log_visit.visitor_days_since_first' => 'SMALLINT(5) UNSIGNED NOT NULL1',
'log_visit.visit_exit_idaction_name' => 'INTEGER(11) UNSIGNED NOT NULL',
'log_visit.visit_exit_idaction_url' => 'INTEGER(11) UNSIGNED NULL DEFAULT 0',
'log_visit.config_browser_version' => 'VARCHAR(20) NOT NULL',
'log_visit.config_browser_name' => 'VARCHAR(10) NOT NULL',
'log_visit.config_browser_engine' => 'VARCHAR(10) NOT NULL',
'log_visit.location_browser_lang' => 'VARCHAR(20) NOT NULL',
'log_visit.config_os' => 'CHAR(3) NOT NULL',
'log_visit.config_cookie' => 'TINYINT(1) NOT NULL',
'log_visit.referer_url' => 'TEXT NOT NULL',
'log_visit.visit_total_searches' => 'SMALLINT(5) UNSIGNED NOT NULL',
'log_visit.visit_total_actions' => 'SMALLINT(5) UNSIGNED NOT NULL',
'log_visit.referer_keyword' => 'VARCHAR(255) NULL1',
'log_visit.referer_name' => 'VARCHAR(70) NULL1',
'log_visit.referer_type' => 'TINYINT(1) UNSIGNED NULL1',
'log_visit.user_id' => 'VARCHAR(200) NULL',
'log_link_visit_action.idaction_name' => 'INTEGER(10) UNSIGNED',
'log_link_visit_action.idaction_url' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
'log_link_visit_action.server_time' => 'DATETIME NOT NULL',
'log_link_visit_action.time_spent_ref_action' => 'INTEGER(10) UNSIGNED NOT NULL',
'log_link_visit_action.idaction_event_action' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
'log_link_visit_action.idaction_event_category' => 'INTEGER(10) UNSIGNED DEFAULT NULL',
'log_conversion.revenue_discount' => 'float default NULL',
'log_conversion.revenue' => 'float default NULL',
'log_conversion.revenue_shipping' => 'float default NULL',
'log_conversion.revenue_subtotal' => 'float default NULL',
'log_conversion.revenue_tax' => 'float default NULL',
);
if (!array_key_exists($name, $initialCoreDimensionVersions)) {
return false;
}
return strtolower($initialCoreDimensionVersions[$name]) === strtolower($version);
}
public function onNoUpdateAvailable($versionsThatWereChecked)
{
if (!empty($versionsThatWereChecked)) {
// invalidate cache only if there were actually file changes before, otherwise we write the cache on each
// request. There were versions checked only if there was a file change but no update, meaning we can
// set the cache and declare this state as "no update available".
self::cacheCurrentDimensionFileChanges();
}
}
private static function getCurrentDimensionFileChanges()
{
$times = array();
foreach (Manager::getPluginsDirectories() as $pluginsDir) {
$files = Filesystem::globr($pluginsDir . '*/Columns', '*.php');
foreach ($files as $file) {
$times[$file] = filemtime($file);
}
}
return $times;
}
private static function cacheCurrentDimensionFileChanges()
{
$changes = self::getCurrentDimensionFileChanges();
$cache = self::buildCache();
$cache->save(self::$cacheId, $changes);
}
private static function buildCache()
{
return PiwikCache::getEagerCache();
}
private static function getCachedDimensionFileChanges()
{
$cache = self::buildCache();
if ($cache->contains(self::$cacheId)) {
return $cache->fetch(self::$cacheId);
}
return array();
}
}