From 6282381d05a62b032c20d25b7b29d8ead41cfbbd Mon Sep 17 00:00:00 2001 From: sam marshall Date: Tue, 11 Oct 2011 14:50:01 +0100 Subject: [PATCH] MDL-27242 Conditional availability dates should support time as well as day Change also adds minor feature to date-time selector so you can specify the time it uses as default when the value is 0 (disabled). Credit: This feature was developed collaboratively by Charles Fulton, Neill Magill, and me. --- course/modedit.php | 12 ---- course/moodleform_mod.php | 21 +++++-- lang/en/condition.php | 1 + lib/conditionlib.php | 103 ++++++++++++++++++++++++---------- lib/db/upgrade.php | 18 ++++++ lib/form/datetimeselector.php | 8 ++- version.php | 2 +- 7 files changed, 117 insertions(+), 48 deletions(-) diff --git a/course/modedit.php b/course/modedit.php index a68ddcca529..111e655acf1 100644 --- a/course/modedit.php +++ b/course/modedit.php @@ -302,12 +302,6 @@ if ($mform->is_cancelled()) { if (!empty($CFG->enableavailability)) { $cm->availablefrom = $fromform->availablefrom; $cm->availableuntil = $fromform->availableuntil; - // The form time is midnight, but because we want it to be - // inclusive, set it to 23:59:59 on that day. - if ($cm->availableuntil) { - $cm->availableuntil = strtotime('23:59:59', - $cm->availableuntil); - } $cm->showavailability = $fromform->showavailability; condition_info::update_cm_from_form($cm,$fromform,true); } @@ -393,12 +387,6 @@ if ($mform->is_cancelled()) { if(!empty($CFG->enableavailability)) { $newcm->availablefrom = $fromform->availablefrom; $newcm->availableuntil = $fromform->availableuntil; - // The form time is midnight, but because we want it to be - // inclusive, set it to 23:59:59 on that day. - if ($newcm->availableuntil) { - $newcm->availableuntil = strtotime('23:59:59', - $newcm->availableuntil); - } $newcm->showavailability = $fromform->showavailability; } if (isset($fromform->showdescription)) { diff --git a/course/moodleform_mod.php b/course/moodleform_mod.php index ce5666bbba7..b4fbb2fabfd 100644 --- a/course/moodleform_mod.php +++ b/course/moodleform_mod.php @@ -308,7 +308,7 @@ abstract class moodleform_mod extends moodleform { // Conditions: Don't let them set dates which make no sense if (array_key_exists('availablefrom', $data) && $data['availablefrom'] && $data['availableuntil'] && - $data['availablefrom'] > $data['availableuntil']) { + $data['availablefrom'] >= $data['availableuntil']) { $errors['availablefrom'] = get_string('badavailabledates', 'condition'); } @@ -429,10 +429,23 @@ abstract class moodleform_mod extends moodleform { if (!empty($CFG->enableavailability)) { // Conditional availability - $mform->addElement('header', 'availabilityconditionsheader', get_string('availabilityconditions', 'condition')); - $mform->addElement('date_selector', 'availablefrom', get_string('availablefrom', 'condition'), array('optional'=>true)); + + // Available from/to defaults to midnight because then the display + // will be nicer where it tells users when they can access it (it + // shows only the date and not time). + $date = usergetdate(time()); + $midnight = make_timestamp($date['year'], $date['mon'], $date['mday']); + + // From/until controls + $mform->addElement('header', 'availabilityconditionsheader', + get_string('availabilityconditions', 'condition')); + $mform->addElement('date_time_selector', 'availablefrom', + get_string('availablefrom', 'condition'), + array('optional' => true, 'defaulttime' => $midnight)); $mform->addHelpButton('availablefrom', 'availablefrom', 'condition'); - $mform->addElement('date_selector', 'availableuntil', get_string('availableuntil', 'condition'), array('optional'=>true)); + $mform->addElement('date_time_selector', 'availableuntil', + get_string('availableuntil', 'condition'), + array('optional' => true, 'defaulttime' => $midnight)); // Conditions based on grades $gradeoptions = array(); diff --git a/lang/en/condition.php b/lang/en/condition.php index 5c0fd778446..ed05e324f2b 100644 --- a/lang/en/condition.php +++ b/lang/en/condition.php @@ -57,6 +57,7 @@ $string['requires_completion_3'] = 'Not available unless the activity {$ $string['requires_date'] = 'Available from {$a}.'; $string['requires_date_before'] = 'Available until {$a}.'; $string['requires_date_both'] = 'Available from {$a->from} to {$a->until}.'; +$string['requires_date_both_single_day'] = 'Available on {$a}.'; $string['requires_grade_any'] = 'Not available until you have a grade in {$a}.'; $string['requires_grade_max'] = 'Not available unless you get an appropriate score in {$a}.'; $string['requires_grade_min'] = 'Not available until you achieve a required score in {$a}.'; diff --git a/lib/conditionlib.php b/lib/conditionlib.php index 83b79d343d2..f3279a19dbc 100644 --- a/lib/conditionlib.php +++ b/lib/conditionlib.php @@ -331,24 +331,83 @@ WHERE } } - // Dates + // The date logic is complicated. The intention of this logic is: + // 1) display date without time where possible (whenever the date is + // midnight) + // 2) when the 'until' date is e.g. 00:00 on the 14th, we display it as + // 'until the 13th' (experience at the OU showed that students are + // likely to interpret 'until ' as 'until the end of '). + // 3) This behaviour becomes confusing for 'same-day' dates where there + // are some exceptions. + // Users in different time zones will typically not get the 'abbreviated' + // behaviour but it should work OK for them aside from that. + + // The following cases are possible: + // a) From 13:05 on 14 Oct until 12:10 on 17 Oct (exact, exact) + // b) From 14 Oct until 12:11 on 17 Oct (midnight, exact) + // c) From 13:05 on 14 Oct until 17 Oct (exact, midnight 18 Oct) + // d) From 14 Oct until 17 Oct (midnight 14 Oct, midnight 18 Oct) + // e) On 14 Oct (midnight 14 Oct, midnight 15 Oct) + // f) From 13:05 on 14 Oct until 0:00 on 15 Oct (exact, midnight, same day) + // g) From 0:00 on 14 Oct until 12:05 on 14 Oct (midnight, exact, same day) + // h) From 13:05 on 14 Oct (exact) + // i) From 14 Oct (midnight) + // j) Until 13:05 on 14 Oct (exact) + // k) Until 14 Oct (midnight 15 Oct) + + // Check if start and end dates are 'midnights', if so we show in short form + $shortfrom = self::is_midnight($this->cm->availablefrom); + $shortuntil = self::is_midnight($this->cm->availableuntil); + + // For some checks and for display, we need the previous day for the 'until' + // value, if we are going to display it in short form + if ($this->cm->availableuntil) { + $daybeforeuntil = strtotime("-1 day", usergetmidnight($this->cm->availableuntil)); + } + + // Special case for if one but not both are exact and they are within a day + if ($this->cm->availablefrom && $this->cm->availableuntil && + $shortfrom != $shortuntil && $daybeforeuntil < $this->cm->availablefrom) { + // Don't use abbreviated version (see examples f, g above) + $shortfrom = false; + $shortuntil = false; + } + + // When showing short end date, the display time is the 'day before' one + $displayuntil = $shortuntil ? $daybeforeuntil : $this->cm->availableuntil; + if ($this->cm->availablefrom && $this->cm->availableuntil) { - $information .= get_string('requires_date_both', 'condition', - (object)array( - 'from' => self::show_time($this->cm->availablefrom, false), - 'until' => self::show_time($this->cm->availableuntil, true))); + if ($shortfrom && $shortuntil && $daybeforeuntil == $this->cm->availablefrom) { + $information .= get_string('requires_date_both_single_day', 'condition', + self::show_time($this->cm->availablefrom, true)); + } else { + $information .= get_string('requires_date_both', 'condition', (object)array( + 'from' => self::show_time($this->cm->availablefrom, $shortfrom), + 'until' => self::show_time($displayuntil, $shortuntil))); + } } else if ($this->cm->availablefrom) { $information .= get_string('requires_date', 'condition', - self::show_time($this->cm->availablefrom, false)); + self::show_time($this->cm->availablefrom, $shortfrom)); } else if ($this->cm->availableuntil) { $information .= get_string('requires_date_before', 'condition', - self::show_time($this->cm->availableuntil, true)); + self::show_time($displayuntil, $shortuntil)); } $information = trim($information); return $information; } + /** + * Checks whether a given time refers exactly to midnight (in current user + * timezone). + * @param int $time Time + * @return bool True if time refers to midnight, false if it's some other + * time or if it is set to zero + */ + private static function is_midnight($time) { + return $time && usergetmidnight($time) == $time; + } + /** * Determines whether this particular course-module is currently available * according to these criteria. @@ -467,7 +526,8 @@ WHERE $available = false; $information .= get_string('requires_date', 'condition', - self::show_time($this->cm->availablefrom, false)); + self::show_time($this->cm->availablefrom, + self::is_midnight($this->cm->availablefrom))); } } @@ -491,30 +551,15 @@ WHERE } /** - * Shows a time either as a date (if it falls exactly on the day) or - * a full date and time, according to user's timezone. - * + * Shows a time either as a date or a full date and time, according to + * user's timezone. * @param int $time Time - * @param bool $until True if this date should be treated as the second of - * an inclusive pair - if so the time will be shown unless date is 23:59:59. - * Without this the date shows for 0:00:00. + * @param bool $dateonly If true, uses date only * @return string Date */ - private function show_time($time, $until) { - // Break down the time into fields - $userdate = usergetdate($time); - - // Handle the 'inclusive' second date - if($until) { - $dateonly = $userdate['hours']==23 && $userdate['minutes']==59 && - $userdate['seconds']==59; - } else { - $dateonly = $userdate['hours']==0 && $userdate['minutes']==0 && - $userdate['seconds']==0; - } - - return userdate($time, get_string( - $dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig')); + private function show_time($time, $dateonly) { + return userdate($time, + get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig')); } /** diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index ad4056b047d..cea5a0f9e90 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -6792,6 +6792,24 @@ FROM upgrade_main_savepoint(true, 2011100700.02); } + if ($oldversion < 2011101200.01) { + // The conditional availability date system used to rely on dates being + // set to 23:59:59 for the end date, but now that exact times are + // supported, it uses midnight on the following day. + + // The query is restricted on 'time mod 10 = 9' in order that + // it is safe to run this upgrade twice if something goes wrong. + $DB->execute('UPDATE {course_modules} SET availableuntil = availableuntil + 1 ' . + 'WHERE availableuntil > 0 AND ' . $DB->sql_modulo('availableuntil', 10) . ' = 9'); + + // Because availableuntil is stored in modinfo, we need to clear modinfo + // for all courses. + rebuild_course_cache(0, true); + + // Main savepoint reached + upgrade_main_savepoint(true, 2011101200.01); + } + return true; } diff --git a/lib/form/datetimeselector.php b/lib/form/datetimeselector.php index 94b7dfeb11f..957d7a39a62 100644 --- a/lib/form/datetimeselector.php +++ b/lib/form/datetimeselector.php @@ -40,12 +40,13 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group{ * * startyear => integer start of range of years that can be selected * stopyear => integer last year that can be selected + * defaulttime => default time value if the field is currently not set * timezone => float/string timezone * applydst => apply users daylight savings adjustment? * step => step to increment minutes by * optional => if true, show a checkbox beside the date to turn it on (or off) */ - var $_options = array('startyear' => 1970, 'stopyear' => 2020, + var $_options = array('startyear' => 1970, 'stopyear' => 2020, 'defaulttime' => 0, 'timezone' => 99, 'applydst' => true, 'step' => 5, 'optional' => false); /** @@ -159,7 +160,10 @@ class MoodleQuickForm_date_time_selector extends MoodleQuickForm_group{ } $requestvalue=$value; if ($value == 0) { - $value = time(); + $value = $this->_options['defaulttime']; + if (!$value) { + $value = time(); + } } if (!is_array($value)) { $currentdate = usergetdate($value); diff --git a/version.php b/version.php index 0c2e50847e3..91e35361a12 100644 --- a/version.php +++ b/version.php @@ -31,7 +31,7 @@ defined('MOODLE_INTERNAL') || die(); -$version = 2011101200.00; // YYYYMMDD = weekly release date of this DEV branch +$version = 2011101200.01; // YYYYMMDD = weekly release date of this DEV branch // RR = release increments - 00 in DEV branches // .XX = incremental changes