PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.1.0
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.1.0
5.13.0 5.12.1 5.12.0 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 2 years ago Month.php 2 years ago PeriodValidator.php 2 years ago Range.php 2 years ago Week.php 2 years ago Year.php 2 years ago
Range.php
467 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 $dateStart = parent::getDateStart();
126 if (empty($dateStart)) {
127 throw new Exception("Specified date range is invalid.");
128 }
129 return $dateStart;
130 }
131 /**
132 * Returns the current period as a string.
133 *
134 * @return string
135 */
136 public function getPrettyString()
137 {
138 $out = $this->translator->translate('General_DateRangeFromTo', array($this->getDateStart()->toString(), $this->getDateEnd()->toString()));
139 return $out;
140 }
141 protected function getMaxN(int $lastN) : int
142 {
143 switch ($this->strPeriod) {
144 case 'day':
145 $lastN = min($lastN, 5 * 365);
146 break;
147 case 'week':
148 $lastN = min($lastN, 10 * 52);
149 break;
150 case 'month':
151 $lastN = min($lastN, 10 * 12);
152 break;
153 case 'year':
154 $lastN = min($lastN, 10);
155 break;
156 }
157 return $lastN;
158 }
159 /**
160 * Sets the default end date of the period.
161 *
162 * @param Date $oDate
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 } else {
222 throw new Exception($this->translator->translate('General_ExceptionInvalidDateRange', array($this->strDate, ' \'lastN\', \'previousN\', \'YYYY-MM-DD,YYYY-MM-DD\'')));
223 }
224 if ($this->strPeriod != 'range') {
225 $this->fillArraySubPeriods($startDate, $endDate, $this->strPeriod);
226 $this->cacheAll();
227 return;
228 }
229 $this->processOptimalSubperiods($startDate, $endDate);
230 // When period=range, we want End Date to be the actual specified end date,
231 // rather than the end of the month / week / whatever is used for processing this range
232 $this->endDate = $endDate;
233 $this->cacheAll();
234 }
235 /**
236 * Given a date string, returns `false` if not a date range,
237 * or returns the array containing start and end dates.
238 *
239 * @param string $dateString
240 * @return mixed array(1 => dateStartString, 2 => dateEndString) or `false` if the input was not a date range.
241 */
242 public static function parseDateRange($dateString)
243 {
244 $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);
245 if (empty($matched)) {
246 return false;
247 }
248 return $regs;
249 }
250 protected $endDate = null;
251 /**
252 * Returns the end date of the period.
253 *
254 * @return null|Date
255 */
256 public function getDateEnd()
257 {
258 if (!is_null($this->endDate)) {
259 return $this->endDate;
260 }
261 return parent::getDateEnd();
262 }
263 /**
264 * Determine which kind of period is best to use.
265 * See Range.test.php
266 *
267 * @param Date $startDate
268 * @param Date $endDate
269 */
270 protected function processOptimalSubperiods($startDate, $endDate)
271 {
272 while ($startDate->isEarlier($endDate) || $startDate == $endDate) {
273 $endOfPeriod = null;
274 $month = new \Piwik\Period\Month($startDate);
275 $endOfMonth = $month->getDateEnd();
276 $startOfMonth = $month->getDateStart();
277 $year = new \Piwik\Period\Year($startDate);
278 $endOfYear = $year->getDateEnd();
279 $startOfYear = $year->getDateStart();
280 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)) {
281 $this->addSubperiod($year);
282 $endOfPeriod = $endOfYear;
283 } 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)) {
284 $this->addSubperiod($month);
285 $endOfPeriod = $endOfMonth;
286 } else {
287 // From start date,
288 // Process end of week
289 $week = new \Piwik\Period\Week($startDate);
290 $startOfWeek = $week->getDateStart();
291 $endOfWeek = $week->getDateEnd();
292 $firstDayNextMonth = $startDate->addPeriod(2, 'month')->setDay(1);
293 $useMonthsNextIteration = $firstDayNextMonth->isEarlier($endDate);
294 if ($useMonthsNextIteration && $endOfWeek->isLater($endOfMonth)) {
295 $this->fillArraySubPeriods($startDate, $endOfMonth, 'day');
296 $endOfPeriod = $endOfMonth;
297 } elseif ($this->isEndOfWeekLaterThanEndDate($endDate, $endOfWeek) && ($endOfWeek->isEarlier($this->today) || $startOfWeek->toString() != $startDate->toString() || $endDate->isEarlier($this->today))) {
298 // If end of this week is later than end date, we use days
299 $this->fillArraySubPeriods($startDate, $endDate, 'day');
300 break 1;
301 } elseif ($startOfWeek->isEarlier($startDate) && $endOfWeek->isEarlier($this->today)) {
302 $this->fillArraySubPeriods($startDate, $endOfWeek, 'day');
303 $endOfPeriod = $endOfWeek;
304 } else {
305 $this->addSubperiod($week);
306 $endOfPeriod = $endOfWeek;
307 }
308 }
309 $startDate = $endOfPeriod->addDay(1);
310 }
311 }
312 /**
313 * Adds new subperiods
314 *
315 * @param Date $startDate
316 * @param Date $endDate
317 * @param string $period
318 */
319 protected function fillArraySubPeriods($startDate, $endDate, $period)
320 {
321 $arrayPeriods = array();
322 $endSubperiod = Period\Factory::build($period, $endDate);
323 $arrayPeriods[] = $endSubperiod;
324 // set end date to start of end period since we're comparing against start date.
325 $endDate = $endSubperiod->getDateStart();
326 while ($endDate->isLater($startDate)) {
327 $endDate = $endDate->addPeriod(-1, $period);
328 $subPeriod = Period\Factory::build($period, $endDate);
329 $arrayPeriods[] = $subPeriod;
330 }
331 $arrayPeriods = array_reverse($arrayPeriods);
332 foreach ($arrayPeriods as $period) {
333 $this->addSubperiod($period);
334 }
335 }
336 /**
337 * Returns the date that is one period before the supplied date.
338 *
339 * @param bool|string $date The date to get the last date of.
340 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
341 *
342 * @return array An array with two elements, a string for the date before $date and
343 * a Period instance for the period before $date.
344 * @api
345 */
346 public static function getLastDate($date = false, $period = false)
347 {
348 return self::getDateXPeriodsAgo(1, $date, $period);
349 }
350 /**
351 * Returns the date that is X periods before the supplied date.
352 *
353 * @param bool|string $date The date to get the last date of.
354 * @param bool|string $period The period to use (either 'day', 'week', 'month', 'year');
355 * @param int $subXPeriods How many periods in the past the date should be, for instance 1 or 7.
356 * If sub period is 365 days and the current year is a leap year we assume you want to get the
357 * day one year ago and change the value to 366 days therefore.
358 *
359 * @return array An array with two elements, a string for the date before $date and
360 * a Period instance for the period before $date.
361 * @api
362 */
363 public static function getDateXPeriodsAgo($subXPeriods, $date = false, $period = false)
364 {
365 if ($date === false) {
366 $date = Common::getRequestVar('date');
367 }
368 if ($period === false) {
369 $period = Common::getRequestVar('period');
370 }
371 if (365 == $subXPeriods && 'day' == $period && Date::factory($date)->isLeapYear()) {
372 $subXPeriods = 366;
373 }
374 if ($period === 'range') {
375 $rangePeriod = new \Piwik\Period\Range($period, $date);
376 $daysDifference = self::getNumDaysDifference($rangePeriod->getDateStart(), $rangePeriod->getDateEnd());
377 $end = $rangePeriod->getDateStart()->subDay(1);
378 $from = $end->subDay($daysDifference);
379 return array("{$from},{$end}", false);
380 }
381 // can't get the last date for range periods & dates that use lastN/previousN
382 $strLastDate = false;
383 $lastPeriod = false;
384 if (!preg_match('/(last|previous)([0-9]*)/', $date, $regs)) {
385 if (strpos($date, ',')) {
386 // date in the form of 2011-01-01,2011-02-02
387 $rangePeriod = new \Piwik\Period\Range($period, $date);
388 $lastStartDate = $rangePeriod->getDateStart()->subPeriod($subXPeriods, $period);
389 $lastEndDate = $rangePeriod->getDateEnd()->subPeriod($subXPeriods, $period);
390 $strLastDate = "{$lastStartDate},{$lastEndDate}";
391 } else {
392 $lastPeriod = Date::factory($date)->subPeriod($subXPeriods, $period);
393 $strLastDate = $lastPeriod->toString();
394 }
395 }
396 return array($strLastDate, $lastPeriod);
397 }
398 /**
399 * Return the number of days contained in this range
400 *
401 * @return int
402 * @throws Exception
403 */
404 public function getDayCount()
405 {
406 return self::getNumDaysDifference($this->getDateStart(), $this->getDateEnd()) + 1;
407 }
408 private static function getNumDaysDifference(Date $date1, Date $date2)
409 {
410 $days = abs($date1->getTimestamp() - $date2->getTimestamp()) / 60 / 60 / 24;
411 return (int) round($days);
412 }
413 /**
414 * Returns a date range string given a period type, end date and number of periods
415 * the range spans over.
416 *
417 * @param string $period The sub period type, `'day'`, `'week'`, `'month'` and `'year'`.
418 * @param int $lastN The number of periods of type `$period` that the result range should
419 * span.
420 * @param string $endDate The desired end date of the range.
421 * @param \Piwik\Site $site The site whose timezone should be used.
422 * @return string The date range string, eg, `'2012-01-02,2013-01-02'`.
423 * @api
424 */
425 public static function getRelativeToEndDate($period, $lastN, $endDate, $site)
426 {
427 $timezone = $site->getTimezone();
428 $last30Relative = new \Piwik\Period\Range($period, $lastN, $timezone);
429 if (strpos($endDate, '-') === false) {
430 // eg today, yesterday, ... needs the timezone
431 $endDate = Date::factoryInTimezone($endDate, $timezone);
432 } else {
433 $endDate = Date::factory($endDate);
434 }
435 $last30Relative->setDefaultEndDate($endDate);
436 $date = $last30Relative->getDateStart()->toString() . "," . $last30Relative->getDateEnd()->toString();
437 return $date;
438 }
439 private function isEndOfWeekLaterThanEndDate($endDate, $endOfWeek)
440 {
441 $isEndOfWeekLaterThanEndDate = $endOfWeek->isLater($endDate);
442 $isEndDateAlsoEndOfWeek = $endOfWeek->toString() == $endDate->toString();
443 $isEndOfWeekLaterThanEndDate = $isEndOfWeekLaterThanEndDate || $isEndDateAlsoEndOfWeek && $endDate->isLater($this->today);
444 return $isEndOfWeekLaterThanEndDate;
445 }
446 /**
447 * Returns the date range string comprising two dates
448 *
449 * @return string eg, `'2012-01-01,2012-01-31'`.
450 */
451 public function getRangeString()
452 {
453 $dateStart = $this->getDateStart();
454 $dateEnd = $this->getDateEnd();
455 return $dateStart->toString("Y-m-d") . "," . $dateEnd->toString("Y-m-d");
456 }
457 public function getImmediateChildPeriodLabel()
458 {
459 $subperiods = $this->getSubperiods();
460 return reset($subperiods)->getImmediateChildPeriodLabel();
461 }
462 public function getParentPeriodLabel()
463 {
464 return null;
465 }
466 }
467