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