PluginProbe ʕ •ᴥ•ʔ
Matomo Analytics – Powerful, Privacy-First Insights for WordPress / 5.0.3
Matomo Analytics – Powerful, Privacy-First Insights for WordPress v5.0.3
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
468 lines
1 <?php
2
3 /**
4 * Matomo - free/libre analytics platform
5 *
6 * @link https://matomo.org
7 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
8 *
9 */
10 namespace Piwik\Period;
11
12 use Exception;
13 use Piwik\Cache;
14 use Piwik\Common;
15 use Piwik\Container\StaticContainer;
16 use Piwik\Date;
17 use Piwik\Period;
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 protected $label = 'range';
35 protected $today;
36 /**
37 * @var null|Date
38 */
39 protected $defaultEndDate;
40 protected $strPeriod;
41 protected $strDate;
42 protected $timezone;
43 /**
44 * Constructor.
45 *
46 * @param string $strPeriod The type of period each subperiod is. Either `'day'`, `'week'`,
47 * `'month'` or `'year'`.
48 * @param string $strDate The date range, eg, `'2007-07-24,2013-11-15'`.
49 * @param string $timezone The timezone to use, eg, `'UTC'`.
50 * @param bool|Date $today The date to use as _today_. Defaults to `Date::factory('today', $timzeone)`.
51 * @api
52 */
53 public function __construct($strPeriod, $strDate, $timezone = 'UTC', $today = false)
54 {
55 $this->strPeriod = $strPeriod;
56 $this->strDate = $strDate;
57 $this->timezone = $timezone;
58 $this->defaultEndDate = null;
59 if ($today === false) {
60 $today = Date::factory('now', $this->timezone);
61 }
62 $this->today = $today;
63 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
64 }
65 public function __sleep()
66 {
67 return ['strPeriod', 'strDate', 'timezone', 'defaultEndDate', 'today'];
68 }
69 public function __wakeup()
70 {
71 $this->translator = StaticContainer::get('Piwik\\Translation\\Translator');
72 }
73 private function getCache()
74 {
75 return Cache::getTransientCache();
76 }
77 private function getCacheId()
78 {
79 $end = '';
80 if ($this->defaultEndDate) {
81 $end = $this->defaultEndDate->getTimestamp();
82 }
83 $today = $this->today->getTimestamp();
84 return 'range' . $this->strPeriod . $this->strDate . $this->timezone . $end . $today;
85 }
86 private function loadAllFromCache()
87 {
88 $range = $this->getCache()->fetch($this->getCacheId());
89 if (!empty($range)) {
90 foreach ($range as $key => $val) {
91 $this->{$key} = $val;
92 }
93 }
94 }
95 private function cacheAll()
96 {
97 $props = get_object_vars($this);
98 $this->getCache()->save($this->getCacheId(), $props);
99 }
100 /**
101 * Returns the current period as a localized short string.
102 *
103 * @return string
104 */
105 public function getLocalizedShortString()
106 {
107 return $this->getTranslatedRange($this->getRangeFormat(true));
108 }
109 /**
110 * Returns the current period as a localized long string.
111 *
112 * @return string
113 */
114 public function getLocalizedLongString()
115 {
116 return $this->getTranslatedRange($this->getRangeFormat());
117 }
118 /**
119 * Returns the start date of the period.
120 *
121 * @return Date
122 * @throws Exception
123 */
124 public function getDateStart()
125 {
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 * @param Date $oDate
164 */
165 public function setDefaultEndDate(Date $oDate)
166 {
167 $this->defaultEndDate = $oDate;
168 }
169 /**
170 * Generates the subperiods
171 *
172 * @throws Exception
173 */
174 protected function generate()
175 {
176 if ($this->subperiodsProcessed) {
177 return;
178 }
179 $this->loadAllFromCache();
180 if ($this->subperiodsProcessed) {
181 return;
182 }
183 parent::generate();
184 if (preg_match('/^(last|previous)([0-9]*)$/', $this->strDate, $regs)) {
185 $lastN = $regs[2];
186 $lastOrPrevious = $regs[1];
187 if (!is_null($this->defaultEndDate)) {
188 $defaultEndDate = $this->defaultEndDate;
189 } else {
190 $defaultEndDate = $this->today;
191 }
192 $period = $this->strPeriod;
193 if ($period == 'range') {
194 $period = 'day';
195 }
196 if ($lastOrPrevious == 'last') {
197 $endDate = $defaultEndDate;
198 } elseif ($lastOrPrevious == 'previous') {
199 if ('month' == $period) {
200 $endDate = $defaultEndDate->subMonth(1);
201 } else {
202 $endDate = $defaultEndDate->subPeriod(1, $period);
203 }
204 }
205 $lastN = $this->getMaxN((int) $lastN);
206 // last1 means only one result ; last2 means 2 results so we remove only 1 to the days/weeks/etc
207 $lastN--;
208 if ($lastN < 0) {
209 $lastN = 0;
210 }
211 $startDate = $endDate->addPeriod(-1 * $lastN, $period);
212 } elseif ($dateRange = \Piwik\Period\Range::parseDateRange($this->strDate)) {
213 $strDateStart = $dateRange[1];
214 $strDateEnd = $dateRange[2];
215 $startDate = Date::factory($strDateStart);
216 // we set the timezone in the Date object only if the date is relative eg. 'today', 'yesterday', 'now'
217 $timezone = null;
218 if (strpos($strDateEnd, '-') === false) {
219 $timezone = $this->timezone;
220 }
221 $endDate = Date::factory($strDateEnd, $timezone)->setTime("00:00:00");
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