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:
authorBenaka <diosmosis@users.noreply.github.com>2017-09-11 16:08:42 +0300
committerMatthieu Aubry <mattab@users.noreply.github.com>2017-09-11 16:08:42 +0300
commitf0ddb70c85a6211540797665f17d36b6d79128a4 (patch)
tree605ef83a15a39b1d30d6641234b83aee58ec8b16 /core
parent1092e4d7290333591f0f9beead6580a1424bc99f (diff)
Changes to support custom periods (#11837)
* Separate Archive query creation responsibility from Archive class. * Add ability for plugins to define custom period types. * Make period responsible for determining start/end time of periods, not LogAggregator. * Allow specifying custom archive writer in PluginsArchiver.
Diffstat (limited to 'core')
-rw-r--r--core/Archive.php54
-rw-r--r--core/Archive/ArchiveQuery.php49
-rw-r--r--core/Archive/ArchiveQueryFactory.php127
-rw-r--r--core/ArchiveProcessor/Parameters.php26
-rw-r--r--core/ArchiveProcessor/PluginsArchiver.php4
-rw-r--r--core/DataAccess/LogAggregator.php8
-rw-r--r--core/Date.php34
-rw-r--r--core/Period.php20
-rw-r--r--core/Period/Factory.php76
9 files changed, 329 insertions, 69 deletions
diff --git a/core/Archive.php b/core/Archive.php
index 4fc7e219bc..3a598eae88 100644
--- a/core/Archive.php
+++ b/core/Archive.php
@@ -8,12 +8,13 @@
*/
namespace Piwik;
+use Piwik\Archive\ArchiveQuery;
+use Piwik\Archive\ArchiveQueryFactory;
use Piwik\Archive\Parameters;
use Piwik\ArchiveProcessor\Rules;
use Piwik\Archive\ArchiveInvalidator;
use Piwik\Container\StaticContainer;
use Piwik\DataAccess\ArchiveSelector;
-use Piwik\Period\Factory as PeriodFactory;
/**
* The **Archive** class is used to query cached analytics statistics
@@ -106,7 +107,7 @@ use Piwik\Period\Factory as PeriodFactory;
*
* @api
*/
-class Archive
+class Archive implements ArchiveQuery
{
const REQUEST_ALL_WEBSITES_FLAG = 'all';
const ARCHIVE_ALL_PLUGINS_FLAG = 'all';
@@ -176,7 +177,7 @@ class Archive
* @param bool $forceIndexedBySite Whether to force index the result of a query by site ID.
* @param bool $forceIndexedByDate Whether to force index the result of a query by period.
*/
- protected function __construct(Parameters $params, $forceIndexedBySite = false,
+ public function __construct(Parameters $params, $forceIndexedBySite = false,
$forceIndexedByDate = false)
{
$this->params = $params;
@@ -203,30 +204,12 @@ class Archive
* or date range (ie, 'YYYY-MM-DD,YYYY-MM-DD').
* @param bool|false|string $segment Segment definition or false if no segment should be used. {@link Piwik\Segment}
* @param bool|false|string $_restrictSitesToLogin Used only when running as a scheduled task.
- * @return static
+ * @return ArchiveQuery
*/
public static function build($idSites, $period, $strDate, $segment = false, $_restrictSitesToLogin = false)
{
- $websiteIds = Site::getIdSitesFromIdSitesString($idSites, $_restrictSitesToLogin);
-
- $timezone = false;
- if (count($websiteIds) == 1) {
- $timezone = Site::getTimezoneFor($websiteIds[0]);
- }
-
- if (Period::isMultiplePeriod($strDate, $period)) {
- $oPeriod = PeriodFactory::build($period, $strDate, $timezone);
- $allPeriods = $oPeriod->getSubperiods();
- } else {
- $oPeriod = PeriodFactory::makePeriodFromQueryParams($timezone, $period, $strDate);
- $allPeriods = array($oPeriod);
- }
-
- $segment = new Segment($segment, $websiteIds);
- $idSiteIsAll = $idSites == self::REQUEST_ALL_WEBSITES_FLAG;
- $isMultipleDate = Period::isMultiplePeriod($strDate, $period);
-
- return static::factory($segment, $allPeriods, $websiteIds, $idSiteIsAll, $isMultipleDate);
+ return StaticContainer::get(ArchiveQueryFactory::class)->build($idSites, $period, $strDate, $segment,
+ $_restrictSitesToLogin);
}
/**
@@ -249,24 +232,13 @@ class Archive
* the result of querying functions will be indexed by period,
* regardless of whether `count($periods) == 1`.
*
- * @return Archive
+ * @return ArchiveQuery
*/
- public static function factory(Segment $segment, array $periods, array $idSites, $idSiteIsAll = false, $isMultipleDate = false)
+ public static function factory(Segment $segment, array $periods, array $idSites, $idSiteIsAll = false,
+ $isMultipleDate = false)
{
- $forceIndexedBySite = false;
- $forceIndexedByDate = false;
-
- if ($idSiteIsAll || count($idSites) > 1) {
- $forceIndexedBySite = true;
- }
-
- if (count($periods) > 1 || $isMultipleDate) {
- $forceIndexedByDate = true;
- }
-
- $params = new Parameters($idSites, $periods, $segment);
-
- return new static($params, $forceIndexedBySite, $forceIndexedByDate);
+ return StaticContainer::get(ArchiveQueryFactory::class)->factory($segment, $periods, $idSites, $idSiteIsAll,
+ $isMultipleDate);
}
/**
@@ -838,7 +810,7 @@ class Archive
* @throws \Exception If a plugin cannot be found or if the plugin for the report isn't
* activated.
*/
- private static function getPluginForReport($report)
+ public static function getPluginForReport($report)
{
// Core metrics are always processed in Core, for the requested date/period/segment
if (in_array($report, Metrics::getVisitsMetricNames())) {
diff --git a/core/Archive/ArchiveQuery.php b/core/Archive/ArchiveQuery.php
new file mode 100644
index 0000000000..acd6bbf29e
--- /dev/null
+++ b/core/Archive/ArchiveQuery.php
@@ -0,0 +1,49 @@
+<?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\Archive;
+
+
+use Piwik\DataTable;
+
+interface ArchiveQuery
+{
+ /**
+ * @param string|string[] $names
+ * @return false|number|array
+ */
+ public function getNumeric($names);
+
+ /**
+ * @param string|string[] $names
+ * @return DataTable|DataTable\Map
+ */
+ public function getDataTableFromNumeric($names);
+
+ /**
+ * @param $names
+ * @return mixed
+ */
+ public function getDataTableFromNumericAndMergeChildren($names);
+
+ /**
+ * @param string $name
+ * @param int|string|null $idSubtable
+ * @return DataTable|DataTable\Map
+ */
+ public function getDataTable($name, $idSubtable = null);
+
+ /**
+ * @param string $name
+ * @param int|string|null $idSubtable
+ * @param int|null $depth
+ * @param bool $addMetadataSubtableId
+ * @return DataTable|DataTable\Map
+ */
+ public function getDataTableExpanded($name, $idSubtable = null, $depth = null, $addMetadataSubtableId = true);
+} \ No newline at end of file
diff --git a/core/Archive/ArchiveQueryFactory.php b/core/Archive/ArchiveQueryFactory.php
new file mode 100644
index 0000000000..ddf3db6b87
--- /dev/null
+++ b/core/Archive/ArchiveQueryFactory.php
@@ -0,0 +1,127 @@
+<?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\Archive;
+
+use Piwik\Archive;
+use Piwik\Period;
+use Piwik\Segment;
+use Piwik\Site;
+use Piwik\Period\Factory as PeriodFactory;
+
+class ArchiveQueryFactory
+{
+ public function __construct()
+ {
+ // empty
+ }
+
+ /**
+ * @see \Piwik\Archive::build()
+ */
+ public function build($idSites, $strPeriod, $strDate, $strSegment = false, $_restrictSitesToLogin = false)
+ {
+ list($websiteIds, $timezone, $idSiteIsAll) = $this->getSiteInfoFromQueryParam($idSites, $_restrictSitesToLogin);
+ list($allPeriods, $isMultipleDate) = $this->getPeriodInfoFromQueryParam($strDate, $strPeriod, $timezone);
+ $segment = $this->getSegmentFromQueryParam($strSegment, $websiteIds);
+
+ return $this->factory($segment, $allPeriods, $websiteIds, $idSiteIsAll, $isMultipleDate);
+ }
+
+ /**
+ * @see \Piwik\Archive::factory()
+ */
+ public function factory(Segment $segment, array $periods, array $idSites, $idSiteIsAll = false, $isMultipleDate = false)
+ {
+ $forceIndexedBySite = false;
+ $forceIndexedByDate = false;
+
+ if ($idSiteIsAll || count($idSites) > 1) {
+ $forceIndexedBySite = true;
+ }
+
+ if (count($periods) > 1 || $isMultipleDate) {
+ $forceIndexedByDate = true;
+ }
+
+ $params = new Parameters($idSites, $periods, $segment);
+
+ return $this->newInstance($params, $forceIndexedBySite, $forceIndexedByDate);
+ }
+
+ public function newInstance(Parameters $params, $forceIndexedBySite, $forceIndexedByDate)
+ {
+ return new Archive($params, $forceIndexedBySite, $forceIndexedByDate);
+ }
+
+ /**
+ * Parses the site ID string provided in the 'idSite' query parameter to a list of
+ * website IDs.
+ *
+ * @param string $idSites the value of the 'idSite' query parameter
+ * @param bool $_restrictSitesToLogin
+ * @return array an array containing three elements:
+ * - an array of website IDs
+ * - string timezone to use (or false to use no timezone) when creating periods.
+ * - true if the request was for all websites (this forces the archive result to
+ * be indexed by site, even if there is only one site in Piwik)
+ */
+ protected function getSiteInfoFromQueryParam($idSites, $_restrictSitesToLogin)
+ {
+ $websiteIds = Site::getIdSitesFromIdSitesString($idSites, $_restrictSitesToLogin);
+
+ $timezone = false;
+ if (count($websiteIds) == 1) {
+ $timezone = Site::getTimezoneFor($websiteIds[0]);
+ }
+
+ $idSiteIsAll = $idSites == Archive::REQUEST_ALL_WEBSITES_FLAG;
+
+ return [$websiteIds, $timezone, $idSiteIsAll];
+ }
+
+ /**
+ * Parses the date & period query parameters into a list of periods.
+ *
+ * @param string $strDate the value of the 'date' query parameter
+ * @param string $strPeriod the value of the 'period' query parameter
+ * @param string $timezone the timezone to use when constructing periods.
+ * @return array an array containing two elements:
+ * - the list of period objects to query archive data for
+ * - true if the request was for multiple periods (ie, two months, two weeks, etc.), false if otherwise.
+ * (this forces the archive result to be indexed by period, even if the list of periods
+ * has only one period).
+ */
+ protected function getPeriodInfoFromQueryParam($strDate, $strPeriod, $timezone)
+ {
+ if (Period::isMultiplePeriod($strDate, $strPeriod)) {
+ $oPeriod = PeriodFactory::build($strPeriod, $strDate, $timezone);
+ $allPeriods = $oPeriod->getSubperiods();
+ } else {
+ $oPeriod = PeriodFactory::makePeriodFromQueryParams($timezone, $strPeriod, $strDate);
+ $allPeriods = array($oPeriod);
+ }
+
+ $isMultipleDate = Period::isMultiplePeriod($strDate, $strPeriod);
+
+ return [$allPeriods, $isMultipleDate];
+ }
+
+ /**
+ * Parses the segment query parameter into a Segment object.
+ *
+ * @param string $strSegment the value of the 'segment' query parameter.
+ * @param int[] $websiteIds the list of sites being queried.
+ * @return Segment
+ */
+ protected function getSegmentFromQueryParam($strSegment, $websiteIds)
+ {
+ return new Segment($strSegment, $websiteIds);
+ }
+} \ No newline at end of file
diff --git a/core/ArchiveProcessor/Parameters.php b/core/ArchiveProcessor/Parameters.php
index 4edd1f4b58..752557a868 100644
--- a/core/ArchiveProcessor/Parameters.php
+++ b/core/ArchiveProcessor/Parameters.php
@@ -154,12 +154,36 @@ class Parameters
}
/**
+ * Returns the start day of the period in the site's timezone (includes the time of day).
+ *
+ * @return Date
+ */
+ public function getDateTimeStart()
+ {
+ return $this->getPeriod()->getDateTimeStart()->setTimezone($this->getSite()->getTimezone());
+ }
+
+ /**
+ * Returns the end day of the period in the site's timezone (includes the time of day).
+ *
+ * @return Date
+ */
+ public function getDateTimeEnd()
+ {
+ return $this->getPeriod()->getDateTimeEnd()->setTimezone($this->getSite()->getTimezone());
+ }
+
+ /**
* @return bool
*/
public function isSingleSiteDayArchive()
{
$oneSite = $this->isSingleSite();
- $oneDay = $this->getPeriod()->getLabel() == 'day';
+
+ $period = $this->getPeriod();
+ $secondsInPeriod = $period->getDateEnd()->getTimestampUTC() - $period->getDateStart()->getTimestampUTC();
+ $oneDay = $secondsInPeriod <= Date::NUM_SECONDS_IN_DAY;
+
return $oneDay && $oneSite;
}
diff --git a/core/ArchiveProcessor/PluginsArchiver.php b/core/ArchiveProcessor/PluginsArchiver.php
index f251551a6d..4c963e17bc 100644
--- a/core/ArchiveProcessor/PluginsArchiver.php
+++ b/core/ArchiveProcessor/PluginsArchiver.php
@@ -49,11 +49,11 @@ class PluginsArchiver
*/
public static $archivers = array();
- public function __construct(Parameters $params, $isTemporaryArchive)
+ public function __construct(Parameters $params, $isTemporaryArchive, ArchiveWriter $archiveWriter = null)
{
$this->params = $params;
$this->isTemporaryArchive = $isTemporaryArchive;
- $this->archiveWriter = new ArchiveWriter($this->params, $this->isTemporaryArchive);
+ $this->archiveWriter = $archiveWriter ?: new ArchiveWriter($this->params, $this->isTemporaryArchive);
$this->archiveWriter->initNewArchive();
$this->logAggregator = new LogAggregator($params);
diff --git a/core/DataAccess/LogAggregator.php b/core/DataAccess/LogAggregator.php
index 522a46fe95..c30132f41e 100644
--- a/core/DataAccess/LogAggregator.php
+++ b/core/DataAccess/LogAggregator.php
@@ -12,8 +12,10 @@ use Piwik\ArchiveProcessor\Parameters;
use Piwik\Common;
use Piwik\Container\StaticContainer;
use Piwik\DataArray;
+use Piwik\Date;
use Piwik\Db;
use Piwik\Metrics;
+use Piwik\Period;
use Piwik\Tracker\GoalManager;
use Psr\Log\LoggerInterface;
@@ -155,8 +157,8 @@ class LogAggregator
*/
public function __construct(Parameters $params, LoggerInterface $logger = null)
{
- $this->dateStart = $params->getDateStart();
- $this->dateEnd = $params->getDateEnd();
+ $this->dateStart = $params->getDateTimeStart();
+ $this->dateEnd = $params->getDateTimeEnd();
$this->segment = $params->getSegment();
$this->sites = $params->getIdSites();
$this->logger = $logger ?: StaticContainer::get('Psr\Log\LoggerInterface');
@@ -515,7 +517,7 @@ class LogAggregator
*/
protected function getGeneralQueryBindParams()
{
- $bind = array($this->dateStart->getDateStartUTC(), $this->dateEnd->getDateEndUTC());
+ $bind = array($this->dateStart->toString(Date::DATE_TIME_FORMAT), $this->dateEnd->toString(Date::DATE_TIME_FORMAT));
$bind = array_merge($bind, $this->sites);
return $bind;
diff --git a/core/Date.php b/core/Date.php
index 64583c9814..3415af6f95 100644
--- a/core/Date.php
+++ b/core/Date.php
@@ -178,17 +178,34 @@ class Date
}
/**
+ * @return string
+ * @deprecated
+ */
+ public function getDateStartUTC()
+ {
+ return $this->getStartOfDay()->toString(self::DATE_TIME_FORMAT);
+ }
+
+ /**
* Returns the start of the day of the current timestamp in UTC. For example,
* if the current timestamp is `'2007-07-24 14:04:24'` in UTC, the result will
- * be `'2007-07-24'`.
+ * be `'2007-07-24'` as a Date.
*
- * @return string
+ * @return Date
*/
- public function getDateStartUTC()
+ public function getStartOfDay()
{
$dateStartUTC = gmdate('Y-m-d', $this->timestamp);
- $date = Date::factory($dateStartUTC)->setTimezone($this->timezone);
- return $date->toString(self::DATE_TIME_FORMAT);
+ return Date::factory($dateStartUTC)->setTimezone($this->timezone);
+ }
+
+ /**
+ * @return string
+ * @deprecated
+ */
+ public function getDateEndUTC()
+ {
+ return $this->getEndOfDay()->toString(self::DATE_TIME_FORMAT);
}
/**
@@ -196,13 +213,12 @@ class Date
* if the current timestamp is `'2007-07-24 14:03:24'` in UTC, the result will
* be `'2007-07-24 23:59:59'`.
*
- * @return string
+ * @return Date
*/
- public function getDateEndUTC()
+ public function getEndOfDay()
{
$dateEndUTC = gmdate('Y-m-d 23:59:59', $this->timestamp);
- $date = Date::factory($dateEndUTC)->setTimezone($this->timezone);
- return $date->toString(self::DATE_TIME_FORMAT);
+ return Date::factory($dateEndUTC)->setTimezone($this->timezone);
}
/**
diff --git a/core/Period.php b/core/Period.php
index 0abd1f4ba3..ebcad4896d 100644
--- a/core/Period.php
+++ b/core/Period.php
@@ -135,6 +135,26 @@ abstract class Period
}
/**
+ * Returns the start date & time of this period.
+ *
+ * @return Date
+ */
+ public function getDateTimeStart()
+ {
+ return $this->getDateStart()->getStartOfDay();
+ }
+
+ /**
+ * Returns the end date & time of this period.
+ *
+ * @return Date
+ */
+ public function getDateTimeEnd()
+ {
+ return $this->getDateEnd()->getEndOfDay();
+ }
+
+ /**
* Returns the last day of the period.
*
* @return Date
diff --git a/core/Period/Factory.php b/core/Period/Factory.php
index 88c29a92e2..60e5bc9b07 100644
--- a/core/Period/Factory.php
+++ b/core/Period/Factory.php
@@ -9,12 +9,55 @@
namespace Piwik\Period;
use Exception;
+use Piwik\Container\StaticContainer;
use Piwik\Date;
use Piwik\Period;
use Piwik\Piwik;
+use Piwik\Plugin;
-class Factory
+/**
+ * Creates Period instances using the values used for the 'period' and 'date'
+ * query parameters.
+ *
+ * ## Custom Periods
+ *
+ * Plugins can define their own period factories all plugins to define new period types, in addition
+ * to "day", "week", "month", "year" and "range".
+ *
+ * To define a new period type:
+ *
+ * 1. create a new period class that derives from {@see \Piwik\Period}.
+ * 2. extend this class in a new PeriodFactory class and put it in /path/to/piwik/plugins/MyPlugin/PeriodFactory.php
+ *
+ * Period name collisions:
+ *
+ * If two plugins try to handle the same period label, the first one encountered will
+ * be used. In other words, avoid using another plugin's period label.
+ */
+abstract class Factory
{
+ public function __construct()
+ {
+ // empty
+ }
+
+ /**
+ * Returns true if this factory should handle the period/date string combination.
+ *
+ * @return bool
+ */
+ public abstract function shouldHandle($strPeriod, $strDate);
+
+ /**
+ * Creates a period using the value of the 'date' query parameter.
+ *
+ * @param string $strPeriod
+ * @param string|Date $date
+ * @param string $timezone
+ * @return Period
+ */
+ public abstract function make($strPeriod, $date, $timezone);
+
/**
* Creates a new Period instance with a period ID and {@link Date} instance.
*
@@ -35,26 +78,33 @@ class Factory
|| $period == 'range') {
return new Range($period, $date, $timezone);
}
- $date = Date::factory($date);
+
+ $dateObject = Date::factory($date);
+ } else {
+ $dateObject = $date;
}
switch ($period) {
case 'day':
- return new Day($date);
- break;
-
+ return new Day($dateObject);
case 'week':
- return new Week($date);
- break;
-
+ return new Week($dateObject);
case 'month':
- return new Month($date);
- break;
-
+ return new Month($dateObject);
case 'year':
- return new Year($date);
- break;
+ return new Year($dateObject);
}
+
+ /** @var string[] $customPeriodFactories */
+ $customPeriodFactories = Plugin\Manager::getInstance()->findComponents('PeriodFactory', self::class);
+ foreach ($customPeriodFactories as $customPeriodFactoryClass) {
+ $customPeriodFactory = StaticContainer::get($customPeriodFactoryClass);
+ if ($customPeriodFactory->shouldHandle($period, $date)) {
+ return $customPeriodFactory->make($period, $date, $timezone);
+ }
+ }
+
+ throw new \Exception("Don't know how to create a '$period' period!");
}
public static function checkPeriodIsEnabled($period)