diff options
author | Benaka <diosmosis@users.noreply.github.com> | 2017-09-11 16:08:42 +0300 |
---|---|---|
committer | Matthieu Aubry <mattab@users.noreply.github.com> | 2017-09-11 16:08:42 +0300 |
commit | f0ddb70c85a6211540797665f17d36b6d79128a4 (patch) | |
tree | 605ef83a15a39b1d30d6641234b83aee58ec8b16 /core | |
parent | 1092e4d7290333591f0f9beead6580a1424bc99f (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.php | 54 | ||||
-rw-r--r-- | core/Archive/ArchiveQuery.php | 49 | ||||
-rw-r--r-- | core/Archive/ArchiveQueryFactory.php | 127 | ||||
-rw-r--r-- | core/ArchiveProcessor/Parameters.php | 26 | ||||
-rw-r--r-- | core/ArchiveProcessor/PluginsArchiver.php | 4 | ||||
-rw-r--r-- | core/DataAccess/LogAggregator.php | 8 | ||||
-rw-r--r-- | core/Date.php | 34 | ||||
-rw-r--r-- | core/Period.php | 20 | ||||
-rw-r--r-- | core/Period/Factory.php | 76 |
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) |