From 6e091e9fd6f295ac733423d702e3de123dbe8a6d Mon Sep 17 00:00:00 2001 From: Dmitrii Metelkin Date: Thu, 16 Aug 2018 11:36:30 +1000 Subject: [PATCH] Improve the logic of recurring ranges (#185) --- element/daterange/classes/element.php | 178 ++++++++++++++++-- .../lang/en/customcertelement_daterange.php | 2 +- 2 files changed, 159 insertions(+), 21 deletions(-) diff --git a/element/daterange/classes/element.php b/element/daterange/classes/element.php index 06ebf5d..413811a 100644 --- a/element/daterange/classes/element.php +++ b/element/daterange/classes/element.php @@ -59,6 +59,11 @@ class element extends \mod_customcert\element { */ const LAST_YEAR_PLACEHOLDER = '{{last_year}}'; + /** + * A year in the user's date. + */ + const DATE_YEAR_PLACEHOLDER = '{{date_year}}'; + /** * Default max number of dateranges per element. */ @@ -428,19 +433,19 @@ class element extends \mod_customcert\element { * @return string */ protected function get_daterange_string($date) { - $rangematched = null; + $matchedrange = null; $outputstring = ''; foreach ($this->get_decoded_data()->dateranges as $key => $range) { - if ($this->is_date_in_range($date, $range)) { - $rangematched = $range; - $outputstring = $range->datestring; - break; - } - - if (!empty($range->recurring)) { - if ($rangematched = $this->get_matched_recurring_range($date, $range)) { - $outputstring = $rangematched->datestring; + if ($this->is_recurring_range($range)) { + if ($matchedrange = $this->get_matched_recurring_range($date, $range)) { + $outputstring = $matchedrange->datestring; + break; + } + } else { + if ($this->is_date_in_range($date, $range)) { + $matchedrange = $range; + $outputstring = $range->datestring; break; } } @@ -450,7 +455,16 @@ class element extends \mod_customcert\element { $outputstring = $this->get_decoded_data()->fallbackstring; } - return $this->format_date_string($outputstring, $rangematched); + return $this->format_date_string($outputstring, $date, $matchedrange); + } + + /** + * @param \stdClass $range Range object. + * + * @return bool + */ + protected function is_recurring_range(\stdClass $range) { + return !empty($range->recurring); } /** @@ -466,7 +480,84 @@ class element extends \mod_customcert\element { } /** - * Get recurring date range matched provided date. + * Check if provided date is in the recurring date range. + * + * @param int $date Unix timestamp date to check. + * @param \stdClass $range Range object. + * + * @return bool + */ + protected function is_date_in_recurring_range($date, \stdClass $range) { + $intdate = $this->build_number_from_date($date); + $intstart = $this->build_number_from_date($range->startdate); + $intend = $this->build_number_from_date($range->enddate); + + if (!$this->has_turn_of_the_year($range)) { + if ($intdate >= $intstart && $intdate <= $intend) { + return true; + } + } else { + if ($intdate >= $intstart && $intdate >= $intend) { + return true; + } + + if ($intdate <= $intstart && $intdate <= $intend) { + return true; + } + } + + return false; + } + + /** + * Check if provided recurring range has a turn of the year. + * + * @param \stdClass $reccurringrange Range object. + * + * @return bool + */ + protected function has_turn_of_the_year(\stdClass $reccurringrange) { + return date('Y', $reccurringrange->startdate) != date('Y', $reccurringrange->enddate); + } + + /** + * Check if provided date is in the start year of the recurring range with a turn of the year. + * + * @param int $date Unix timestamp date to check. + * @param \stdClass $range Range object. + * + * @return bool + */ + protected function in_start_year($date, \stdClass $range) { + $intdate = $this->build_number_from_date($date); + $intstart = $this->build_number_from_date($range->startdate); + $intend = $this->build_number_from_date($range->enddate); + + return $intdate >= $intstart && $intdate >= $intend; + } + + /** + * Check if provided date is in the end year of the recurring range with a turn of the year. + * + * @param int $date Unix timestamp date to check. + * @param \stdClass $range Range object. + * + * @return bool + */ + protected function in_end_year($date, \stdClass $range) { + $intdate = $this->build_number_from_date($date); + $intstart = $this->build_number_from_date($range->startdate); + $intend = $this->build_number_from_date($range->enddate); + + return $intdate <= $intstart && $intdate <= $intend; + } + + /** + * Return matched recurring date range. + * + * As recurring date ranges do not depend on the year, + * we will use a date's year to build a new matched recurring date range with + * start year and end year. This is required to replace placeholders like first_year and last_year. * * @param int $date Unix timestamp date to check. * @param \stdClass $range Range object. @@ -474,34 +565,68 @@ class element extends \mod_customcert\element { * @return \stdClass || null */ protected function get_matched_recurring_range($date, \stdClass $range) { - while ($range->enddate < time()) { - $startyear = date('Y', $range->startdate) + 1; // Max recurring period is 12 months. - $endyear = date('Y', $range->enddate) + 1; // Max recurring period is 12 months. + if (!$this->is_date_in_recurring_range($date, $range)) { + return null; + } - $range->startdate = strtotime(date('d.m.', $range->startdate) . $startyear); - $range->enddate = strtotime(date('d.m.', $range->enddate) . $endyear); + if ($this->has_turn_of_the_year($range)) { + + if ($this->in_start_year($date, $range)) { + $startyear = date('Y', $date); + $endyear = $startyear + 1; + $range->startdate = strtotime(date('d.m.', $range->startdate) . $startyear); + $range->enddate = strtotime(date('d.m.', $range->enddate) . $endyear); - if ($this->is_date_in_range($date, $range)) { return $range; } + + if ($this->in_end_year($date, $range)) { + $endyear = date('Y', $date); + $startyear = $endyear - 1; + $range->startdate = strtotime(date('d.m.', $range->startdate) . $startyear); + $range->enddate = strtotime(date('d.m.', $range->enddate) . $endyear); + + return $range; + } + } else { + $range->startdate = strtotime(date('d.m.', $range->startdate) . date('Y', $date)); + $range->enddate = strtotime(date('d.m.', $range->enddate) . date('Y', $date)); + + return $range; } return null; } /** - * Format date string. + * Build number representation of the provided date. + * + * @param int $date Unix timestamp date to check. + * + * @return int + */ + protected function build_number_from_date($date) { + return (int)date('md', $date); + } + + /** + * Format date string based on different types of placeholders. * * @param string $datestring Date string to format. + * @param int $date Unix timestamp date to check. * @param \stdClass $range Optional range element element to process range related placeholders. * * @return string */ - protected function format_date_string($datestring, $range = null) { + protected function format_date_string($datestring, $date, $range = null) { foreach ($this->get_placeholders() as $search => $replace) { $datestring = str_replace($search, $replace, $datestring); } + foreach ($this->get_date_placeholders($date) as $search => $replace) { + $datestring = str_replace($search, $replace, $datestring); + } + if ($range) { foreach ($this->get_range_placeholders($range) as $search => $replace) { $datestring = str_replace($search, $replace, $datestring); @@ -522,6 +647,19 @@ class element extends \mod_customcert\element { ]; } + /** + * Return a list of user's date related placeholders to replace in date string as search => $replace pairs. + + * @param int $date Unix timestamp date to check. + * + * @return array + */ + protected function get_date_placeholders($date) { + return [ + self::DATE_YEAR_PLACEHOLDER => date('Y', $date), + ]; + } + /** * Return a list of range related placeholders to replace in date string as search => $replace pairs. * diff --git a/element/daterange/lang/en/customcertelement_daterange.php b/element/daterange/lang/en/customcertelement_daterange.php index 81d9ddf..4307e87 100644 --- a/element/daterange/lang/en/customcertelement_daterange.php +++ b/element/daterange/lang/en/customcertelement_daterange.php @@ -33,7 +33,7 @@ $string['dateitem_help'] = 'This will be the date that is printed on the certifi $string['dateranges'] = 'Dateranges'; $string['fallbackstring'] = 'Fallback string'; $string['fallbackstring_help'] = 'This string will be displayed if no daterange applies to a date. If Fallback string is not set, then there will be no output at all.'; -$string['help'] = 'Configure a string representation for each daterange. Set start and end dates as well as a string you would like to transform each range to. Make sure your ranges do not overlap, otherwise the first detected daterange will be applied. If no daterange applied to a date, then Fallback string will be displayed. If Fallback string is not set, then there will be no output at all. If you mark a date range as Recurring, then the configured year will not be considered and the current year will be used. As the year of a recurring date range is not considered, you are not allowed to configure a recurring date range with more than 12 months as it would become ambiguous otherwise. Also there are {{first_year}} and {{last_year}} and {{current_year}} placeholders that could be used in the string representation. The placeholders will be replaced by first year or last year in the matched range or the current year.'; +$string['help'] = 'Configure a string representation for each daterange. Set start and end dates as well as a string you would like to transform each range to. Make sure your ranges do not overlap, otherwise the first detected daterange will be applied. If no daterange applied to a date, then Fallback string will be displayed. If Fallback string is not set, then there will be no output at all. If you mark a date range as Recurring, then the configured year will not be considered and the current year will be used. As the year of a recurring date range is not considered, you are not allowed to configure a recurring date range with more than 12 months as it would become ambiguous otherwise. Also there are {{first_year}}, {{last_year}}, {{current_year}} and {{date_year}} placeholders that could be used in the string representation. The placeholders will be replaced by first year or last year in the matched range or the current year or a year from the users\'s date.'; $string['issueddate'] = 'Issued date'; $string['maxranges'] = 'Maximum number ranges'; $string['maxranges_desc'] = 'Set a maximum number of date ranges per each element';