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