PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.8.2
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.8.2
5.11.1 5.11.0 5.10.2 5.10.1 trunk 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.3.2 4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.1.0 4.1.1 4.1.2 4.1.3 4.10.0 4.11.0 4.12.0 4.13.0 4.13.2 4.13.3 4.13.4 4.13.5 4.14.0 4.14.1 4.14.2 4.15.0 4.15.1 4.15.2 4.15.3 4.2.0 4.3.0 4.3.1 4.4.1 4.4.2 4.5.0 4.6.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 5.0.8 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.10.0 5.2.0 5.2.1 5.2.2 5.3.0 5.3.1 5.3.2 5.3.3 5.6.0 5.6.1 5.7.0 5.7.1 5.8.0 5.8.1 5.8.2
matomo / app / core / Period / Range.php
matomo / app / core / Period Last commit date
Day.php 2 years ago Factory.php 3 months ago Month.php 2 years ago PeriodValidator.php 2 years ago Range.php 3 months ago Week.php 1 year ago Year.php 2 years ago
Range.php
482 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license https://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 */
9 namespace Piwik\Period;
10
11 use Exception;
12 use Piwik\Cache;
13 use Piwik\Common;
14 use Piwik\Container\StaticContainer;
15 use Piwik\Date;
16 use Piwik\Period;
17 /**
18 * Arbitrary date range representation.
19 *
20 * This class represents a period that contains a list of consecutive days as subperiods
21 * It is created when the **period** query parameter is set to **range** and is used
22 * to calculate the subperiods of multiple period requests (eg, when period=day and
23 * date=2007-07-24,2013-11-15).
24 *
25 * The range period differs from other periods mainly in that since it is arbitrary,
26 * range periods are not pre-archived by the cron core:archive command.
27 *
28 * @api
29 */
30 class Range extends Period
31 {
32 public const PERIOD_ID = 5;
33 protected $label = 'range';
34 protected $today;
35 /**
36 * @var null|Date
37 */
38 protected $defaultEndDate;
39 protected $strPeriod;
40 protected $strDate;
41 protected $timezone;
42 /**
43 * Constructor.
44 *
45 * @param string $strPeriod The type of period each subperiod is. Either `'day'`, `'week'`,
46 * `'month'` or `'year'`.
47 * @param string $strDate The date range, eg, `'2007-07-24,2013-11-15'`.
48 * @param string $timezone The timezone to use, eg, `'UTC'`.
49 * @param bool|Date $today The date to use as _today_. Defaults to `Date::factory('today', $timzeone)`.
50 * @api
51 */
52 public function __construct($strPeriod, $strDate, $timezone = 'UTC', $today = \false)
53 {
54 $this->strPeriod = $strPeriod;
55 $this->strDate = $strDate;
56 $this->timezone = $timezone;
57 $this->defaultEndDate = null;
58 if ($today === \false) {
59 $today = Date::factory('now', $this->timezone);
60 }
61 $this->today = $today;
62 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
63 }
64 public function __sleep()
65 {
66 return ['strPeriod', 'strDate', 'timezone', 'defaultEndDate', 'today'];
67 }
68 public function __wakeup()
69 {
70 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
71 }
72 private function getCache()
73 {
74 return Cache::getTransientCache();
75 }
76 private function getCacheId()
77 {
78 $end = '';
79 if ($this->defaultEndDate) {
80 $end = $this->defaultEndDate->getTimestamp();
81 }
82 $today = $this->today->getTimestamp();
83 return 'range' . $this->strPeriod . $this->strDate . $this->timezone . $end . $today;
84 }
85 private function loadAllFromCache()
86 {
87 $range = $this->getCache()->fetch($this->getCacheId());
88 if (!empty($range)) {
89 foreach ($range as $key => $val) {
90 $this->{$key} = $val;
91 }
92 }
93 }
94 private function cacheAll()
95 {
96 $props = get_object_vars($this);
97 $this->getCache()->save($this->getCacheId(), $props);
98 }
99 /**
100 * Returns the current period as a localized short string.
101 *
102 * @return string
103 */
104 public function getLocalizedShortString()
105 {
106 return $this->getTranslatedRange($this->getRangeFormat(\true));
107 }
108 /**
109 * Returns the current period as a localized long string.
110 *
111 * @return string
112 */
113 public function getLocalizedLongString()
114 {
115 return $this->getTranslatedRange($this->getRangeFormat());
116 }
117 /**
118 * Returns the start date of the period.
119 *
120 * @return Date
121 * @throws Exception
122 */
123 public function getDateStart()
124 {
125 /** @var Date|null $dateStart */
126 $dateStart = parent::getDateStart();
127 if (empty($dateStart)) {
128 throw new Exception("Specified date range is invalid.");
129 }
130 return $dateStart;
131 }
132 /**
133 * Returns the current period as a string.
134 *
135 * @return string
136 */
137 public function getPrettyString()
138 {
139 $out = $this->translator->translate('General_DateRangeFromTo', array($this->getDateStart()->toString(), $this->getDateEnd()->toString()));
140 return $out;
141 }
142 protected function getMaxN(int $lastN) : int
143 {
144 switch ($this->strPeriod) {
145 case 'day':
146 $lastN = min($lastN, 5 * 365);
147 break;
148 case 'week':
149 $lastN = min($lastN, 10 * 52);
150 break;
151 case 'month':
152 $lastN = min($lastN, 10 * 12);
153 break;
154 case 'year':
155 $lastN = min($lastN, 10);
156 break;
157 }
158 return $lastN;
159 }
160 /**
161 * Sets the default end date of the period.
162 *
163 */
164 public function setDefaultEndDate(Date $oDate)
165 {
166 $this->defaultEndDate = $oDate;
167 }
168 /**
169 * Generates the subperiods
170 *
171 * @throws Exception
172 */
173 protected function generate()
174 {
175 if ($this->subperiodsProcessed) {
176 return;
177 }
178 $this->loadAllFromCache();
179 if ($this->subperiodsProcessed) {
180 return;
181 }
182 parent::generate();
183 if (preg_match('/^(last|previous)([0-9]*)$/', $this->strDate, $regs)) {
184 $lastN = $regs[2];
185 $lastOrPrevious = $regs[1];
186 if (!is_null($this->defaultEndDate)) {
187 $defaultEndDate = $this->defaultEndDate;
188 } else {
189 $defaultEndDate = $this->today;
190 }
191 $period = $this->strPeriod;
192 if ($period == 'range') {
193 $period = 'day';
194 }
195 if ($lastOrPrevious == 'last') {
196 $endDate = $defaultEndDate;
197 } elseif ($lastOrPrevious == 'previous') {
198 if ('month' == $period) {
199 $endDate = $defaultEndDate->subMonth(1);
200 } else {
201 $endDate = $defaultEndDate->subPeriod(1, $period);
202 }
203 }
204 $lastN = $this->getMaxN((int) $lastN);
205 // last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc
206 $lastN--;
207 if ($lastN < 0) {
208 $lastN = 0;
209 }
210 $startDate = $endDate->addPeriod(-1 * $lastN, $period);
211 } elseif ($dateRange = \Piwik\Period\Range::parseDateRange($this->strDate)) {
212 $strDateStart = $dateRange[1];
213 $strDateEnd = $dateRange[2];
214 $startDate = Date::factory($strDateStart);
215 // we set the timezone in the Date object only if the date is relative eg. 'today', 'yesterday', 'now'
216 $timezone = null;
217 if (strpos($strDateEnd, '-') === \false) {
218 $timezone = $this->timezone;
219 }
220 $endDate = Date::factory($strDateEnd, $timezone)->setTime("00:00:00");
221 $maxAllowedEndDate = Date::factory(self::getMaxAllowedEndTimestamp());
222 if ($endDate->isLater($maxAllowedEndDate)) {
223 $endDate = $maxAllowedEndDate;
224 }
225 } else {
226 throw new Exception($this->translator->translate('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\'')));
227 }
228 if ($this->strPeriod != 'range') {
229 $this->fillArraySubPeriods($startDate, $endDate, $this->strPeriod);
230 $this->cacheAll();
231 return;
232 }
233 $this->processOptimalSubperiods($startDate, $endDate);
234 // When period=range, we want End Date to be the actual specified end date,
235 // rather than the end of the month / week / whatever is used for processing this range
236 $this->endDate = $endDate;
237 $this->cacheAll();
238 }
239 /**
240 * Given a date string, returns `false` if not a date range,
241 * or returns the array containing start and end dates.
242 *
243 * @param string $dateString
244 * @return mixed array(1 => dateStartString, 2 => dateEndString) or `false` if the input was not a date range.
245 */
246 public static function parseDateRange($dateString)
247 {
248 $matched = preg_match('/^((?:[0-9]{4}-[0-9]{1,2}-[0-9]{1,2})|last[ -]?(?:week|month|year)),((?:[0-9]{4}-[0-9]{1,2}-[0-9]{1,2})|today|now|yesterday|last[ -]?(?:week|month|year))$/Di', trim($dateString), $regs);
249 if (empty($matched)) {
250 return \false;
251 }
252 return $regs;
253 }
254 protected $endDate = null;
255 /**
256 * Returns the end date of the period.
257 *
258 * @return null|Date
259 */
260 public function getDateEnd()
261 {
262 if (!is_null($this->endDate)) {
263 return $this->endDate;
264 }
265 return parent::getDateEnd();
266 }
267 /**
268 * Determine which kind of period is best to use.
269 * See Range.test.php
270 *
271 * @param Date $startDate
272 * @param Date $endDate
273 */
274 protected function processOptimalSubperiods($startDate, $endDate)
275 {
276 while ($startDate->isEarlier($endDate) || $startDate == $endDate) {
277 $endOfPeriod = null;
278 $month = new \Piwik\Period\Month($startDate);
279 $endOfMonth = $month->getDateEnd();
280 $startOfMonth = $month->getDateStart();
281 $year = new \Piwik\Period\Year($startDate);
282 $endOfYear = $year->getDateEnd();
283 $startOfYear = $year->getDateStart();
284 if ($startDate == $startOfYear && ($endOfYear->isEarlier($endDate) || $endOfYear == $endDate || $endOfYear->isLater($this->today)) && !($endDate->isEarlier($this->today) && $this->today->toString('Y') == $endOfYear->toString('Y') && $this->today->compareYear($endOfYear) == 0)) {
285 $this->addSubperiod($year);
286 $endOfPeriod = $endOfYear;
287 } elseif ($startDate == $startOfMonth && ($endOfMonth->isEarlier($endDate) || $endOfMonth == $endDate || $endOfMonth->isLater($this->today)) && !($endDate->isEarlier($this->today) && $this->today->toString('Y') == $endOfMonth->toString('Y') && $this->today->compareMonth($endOfMonth) == 0)) {
288 $this->addSubperiod($month);
289 $endOfPeriod = $endOfMonth;
290 } else {
291 // From start date,
292 // Process end of week
293 $week = new \Piwik\Period\Week($startDate);
294 $startOfWeek = $week->getDateStart();
295 $endOfWeek = $week->getDateEnd();
296 $firstDayNextMonth = $startDate->addPeriod(2, 'month')->setDay(1);
297 $useMonthsNextIteration = $firstDayNextMonth->isEarlier($endDate);
298 if ($useMonthsNextIteration && $endOfWeek->isLater($endOfMonth)) {
299 $this->fillArraySubPeriods($startDate, $endOfMonth, 'day');
300 $endOfPeriod = $endOfMonth;
301 } elseif ($this->isEndOfWeekLaterThanEndDate($endDate, $endOfWeek) && ($endOfWeek->isEarlier($this->today) || $startOfWeek->toString() != $startDate->toString() || $endDate->isEarlier($this->today))) {
302 // If end of this week is later than end date, we use days
303 $this->fillArraySubPeriods($startDate, $endDate, 'day');
304 break 1;
305 } elseif ($startOfWeek->isEarlier($startDate) && $endOfWeek->isEarlier($this->today)) {
306 $this->fillArraySubPeriods($startDate, $endOfWeek, 'day');
307 $endOfPeriod = $endOfWeek;
308 } else {
309 $this->addSubperiod($week);
310 $endOfPeriod = $endOfWeek;
311 }
312 }
313 $startDate = $endOfPeriod->addDay(1);
314 }
315 }
316 /**
317 * Adds new subperiods
318 *
319 * @param Date $startDate
320 * @param Date $endDate
321 * @param string $period
322 */
323 protected function fillArraySubPeriods($startDate, $endDate, $period)
324 {
325 $arrayPeriods = array();
326 $endSubperiod = Period\Factory::build($period, $endDate);
327 $arrayPeriods[] = $endSubperiod;
328 // set end date to start of end period since we're comparing against start date.
329 $endDate = $endSubperiod->getDateStart();
330 while ($endDate->isLater($startDate)) {
331 $endDate = $endDate->addPeriod(-1, $period);
332 $subPeriod = Period\Factory::build($period, $endDate);
333 $arrayPeriods[] = $subPeriod;
334 }
335 $arrayPeriods = array_reverse($arrayPeriods);
336 foreach ($arrayPeriods as $period) {
337 $this->addSubperiod($period);
338 }
339 }
340 /**
341 * Returns the date that is one period before the supplied date.
342 *
343 * @param bool|string $date The date to get the last date of.
344 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
345 *
346 * @return array An array with two elements, a string for the date before $date and
347 * a Period instance for the period before $date.
348 * @api
349 */
350 public static function getLastDate($date = \false, $period = \false)
351 {
352 return self::getDateXPeriodsAgo(1, $date, $period);
353 }
354 /**
355 * Returns the date that is X periods before the supplied date.
356 *
357 * @param bool|string $date The date to get the last date of.
358 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
359 * @param int $subXPeriods How many periods in the past the date should be, for instance 1 or 7.
360 * If sub period is 365 days and the current year is a leap year we assume you want to get the
361 * day one year ago and change the value to 366 days therefore.
362 *
363 * @return array An array with two elements, a string for the date before $date and
364 * a Period instance for the period before $date.
365 * @api
366 */
367 public static function getDateXPeriodsAgo($subXPeriods, $date = \false, $period = \false)
368 {
369 if ($date === \false) {
370 $date = Common::getRequestVar('date');
371 }
372 if ($period === \false) {
373 $period = Common::getRequestVar('period');
374 }
375 if (365 == $subXPeriods && 'day' == $period && Date::factory($date)->isLeapYear()) {
376 $subXPeriods = 366;
377 }
378 if ($period === 'range') {
379 $rangePeriod = new \Piwik\Period\Range($period, $date);
380 $daysDifference = self::getNumDaysDifference($rangePeriod->getDateStart(), $rangePeriod->getDateEnd());
381 $end = $rangePeriod->getDateStart()->subDay(1);
382 $from = $end->subDay($daysDifference);
383 return array("{$from},{$end}", \false);
384 }
385 // can't get the last date for range periods & dates that use lastN/previousN
386 $strLastDate = \false;
387 $lastPeriod = \false;
388 if (!preg_match('/(last|previous)([0-9]*)/', $date, $regs)) {
389 if (strpos($date, ',')) {
390 // date in the form of 2011-01-01,2011-02-02
391 $rangePeriod = new \Piwik\Period\Range($period, $date);
392 $lastStartDate = $rangePeriod->getDateStart()->subPeriod($subXPeriods, $period);
393 $lastEndDate = $rangePeriod->getDateEnd()->subPeriod($subXPeriods, $period);
394 $strLastDate = "{$lastStartDate},{$lastEndDate}";
395 } else {
396 $lastPeriod = Date::factory($date)->subPeriod($subXPeriods, $period);
397 $strLastDate = $lastPeriod->toString();
398 }
399 }
400 return array($strLastDate, $lastPeriod);
401 }
402 /**
403 * Return the number of days contained in this range
404 *
405 * @return int
406 * @throws Exception
407 */
408 public function getDayCount()
409 {
410 return self::getNumDaysDifference($this->getDateStart(), $this->getDateEnd()) + 1;
411 }
412 private static function getNumDaysDifference(Date $date1, Date $date2)
413 {
414 $days = abs($date1->getTimestamp() - $date2->getTimestamp()) / 60 / 60 / 24;
415 return (int) round($days);
416 }
417 /**
418 * Returns a date range string given a period type, end date and number of periods
419 * the range spans over.
420 *
421 * @param string $period The sub period type, `'day'`, `'week'`, `'month'` and `'year'`.
422 * @param int $lastN The number of periods of type `$period` that the result range should
423 * span.
424 * @param string $endDate The desired end date of the range.
425 * @param \Piwik\Site $site The site whose timezone should be used.
426 * @return string The date range string, eg, `'2012-01-02,2013-01-02'`.
427 * @api
428 */
429 public static function getRelativeToEndDate($period, $lastN, $endDate, $site)
430 {
431 $timezone = $site->getTimezone();
432 $last30Relative = new \Piwik\Period\Range($period, $lastN, $timezone);
433 if (strpos($endDate, '-') === \false) {
434 // eg today, yesterday, ... needs the timezone
435 $endDate = Date::factoryInTimezone($endDate, $timezone);
436 } else {
437 $endDate = Date::factory($endDate);
438 }
439 $last30Relative->setDefaultEndDate($endDate);
440 $date = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString();
441 return $date;
442 }
443 private function isEndOfWeekLaterThanEndDate($endDate, $endOfWeek)
444 {
445 $isEndOfWeekLaterThanEndDate = $endOfWeek->isLater($endDate);
446 $isEndDateAlsoEndOfWeek = $endOfWeek->toString() == $endDate->toString();
447 $isEndOfWeekLaterThanEndDate = $isEndOfWeekLaterThanEndDate || $isEndDateAlsoEndOfWeek && $endDate->isLater($this->today);
448 return $isEndOfWeekLaterThanEndDate;
449 }
450 /**
451 * Returns the date range string comprising two dates
452 *
453 * @return string eg, `'2012-01-01,2012-01-31'`.
454 */
455 public function getRangeString()
456 {
457 $dateStart = $this->getDateStart();
458 $dateEnd = $this->getDateEnd();
459 return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d");
460 }
461 public function getImmediateChildPeriodLabel()
462 {
463 $subperiods = $this->getSubperiods();
464 return reset($subperiods)->getImmediateChildPeriodLabel();
465 }
466 public function getParentPeriodLabel()
467 {
468 return null;
469 }
470 /**
471 * Returns the max allowed end timestamp for a range. If an enddate after this timestamp is provided, Matomo will
472 * automatically lower the end date to the date returned by this method.
473 * The max supported timestamp is always set to end of the current year plus 10 years.
474 *
475 * @api
476 */
477 public static function getMaxAllowedEndTimestamp() : int
478 {
479 return strtotime(date('Y-12-31 00:00:00', strtotime('+10 year', Date::getNowTimestamp())));
480 }
481 }
482