first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Handles AJAX processing (convert date to timestamp using current calendar).
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('AJAX_SCRIPT', true);
require(__DIR__ . '/../../../config.php');
// Action verb.
$action = required_param('action', PARAM_ALPHA);
switch ($action) {
case 'totime':
// Converts from time fields to timestamp using current user's calendar and time zone.
echo \availability_date\frontend::get_time_from_fields(
required_param('year', PARAM_INT),
required_param('month', PARAM_INT),
required_param('day', PARAM_INT),
required_param('hour', PARAM_INT),
required_param('minute', PARAM_INT));
exit;
case 'fromtime' :
// Converts from timestamp to time fields.
echo json_encode(\availability_date\frontend::get_fields_from_time(
required_param('time', PARAM_INT)));
exit;
}
// Unexpected actions throw coding_exception (this error should not occur
// unless there is a code bug).
throw new coding_exception('Unexpected action parameter');
@@ -0,0 +1,307 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Date condition.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace availability_date;
defined('MOODLE_INTERNAL') || die();
/**
* Date condition.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class condition extends \core_availability\condition {
/** @var string Availabile only from specified date. */
const DIRECTION_FROM = '>=';
/** @var string Availabile only until specified date. */
const DIRECTION_UNTIL = '<';
/** @var string One of the DIRECTION_xx constants. */
private $direction;
/** @var int Time (Unix epoch seconds) for condition. */
private $time;
/** @var int Forced current time (for unit tests) or 0 for normal. */
private static $forcecurrenttime = 0;
/**
* Constructor.
*
* @param \stdClass $structure Data structure from JSON decode
* @throws \coding_exception If invalid data structure.
*/
public function __construct($structure) {
// Get direction.
if (isset($structure->d) && in_array($structure->d,
array(self::DIRECTION_FROM, self::DIRECTION_UNTIL))) {
$this->direction = $structure->d;
} else {
throw new \coding_exception('Missing or invalid ->d for date condition');
}
// Get time.
if (isset($structure->t) && is_int($structure->t)) {
$this->time = $structure->t;
} else {
throw new \coding_exception('Missing or invalid ->t for date condition');
}
}
public function save() {
return (object)array('type' => 'date',
'd' => $this->direction, 't' => $this->time);
}
/**
* Returns a JSON object which corresponds to a condition of this type.
*
* Intended for unit testing, as normally the JSON values are constructed
* by JavaScript code.
*
* @param string $direction DIRECTION_xx constant
* @param int $time Time in epoch seconds
* @return stdClass Object representing condition
*/
public static function get_json($direction, $time) {
return (object)array('type' => 'date', 'd' => $direction, 't' => (int)$time);
}
public function is_available($not, \core_availability\info $info, $grabthelot, $userid) {
return $this->is_available_for_all($not);
}
public function is_available_for_all($not = false) {
// Check condition.
$now = self::get_time();
switch ($this->direction) {
case self::DIRECTION_FROM:
$allow = $now >= $this->time;
break;
case self::DIRECTION_UNTIL:
$allow = $now < $this->time;
break;
default:
throw new \coding_exception('Unexpected direction');
}
if ($not) {
$allow = !$allow;
}
return $allow;
}
/**
* Obtains the actual direction of checking based on the $not value.
*
* @param bool $not True if condition is negated
* @return string Direction constant
* @throws \coding_exception
*/
protected function get_logical_direction($not) {
switch ($this->direction) {
case self::DIRECTION_FROM:
return $not ? self::DIRECTION_UNTIL : self::DIRECTION_FROM;
case self::DIRECTION_UNTIL:
return $not ? self::DIRECTION_FROM : self::DIRECTION_UNTIL;
default:
throw new \coding_exception('Unexpected direction');
}
}
public function get_description($full, $not, \core_availability\info $info) {
return $this->get_either_description($not, false);
}
public function get_standalone_description(
$full, $not, \core_availability\info $info) {
return $this->get_either_description($not, true);
}
/**
* Shows the description using the different lang strings for the standalone
* version or the full one.
*
* @param bool $not True if NOT is in force
* @param bool $standalone True to use standalone lang strings
*/
protected function get_either_description($not, $standalone) {
$direction = $this->get_logical_direction($not);
$midnight = self::is_midnight($this->time);
$midnighttag = $midnight ? '_date' : '';
$satag = $standalone ? 'short_' : 'full_';
switch ($direction) {
case self::DIRECTION_FROM:
return get_string($satag . 'from' . $midnighttag, 'availability_date',
self::show_time($this->time, $midnight, false));
case self::DIRECTION_UNTIL:
return get_string($satag . 'until' . $midnighttag, 'availability_date',
self::show_time($this->time, $midnight, true));
}
}
protected function get_debug_string() {
return $this->direction . ' ' . gmdate('Y-m-d H:i:s', $this->time);
}
/**
* Gets time. This function is implemented here rather than calling time()
* so that it can be overridden in unit tests. (Would really be nice if
* Moodle had a generic way of doing that, but it doesn't.)
*
* @return int Current time (seconds since epoch)
*/
protected static function get_time() {
if (self::$forcecurrenttime) {
return self::$forcecurrenttime;
} else {
return time();
}
}
/**
* Forces the current time for unit tests.
*
* @param int $forcetime Time to return from the get_time function
*/
public static function set_current_time_for_test($forcetime = 0) {
self::$forcecurrenttime = $forcetime;
}
/**
* Shows a time either as a date or a full date and time, according to
* user's timezone.
*
* @param int $time Time
* @param bool $dateonly If true, uses date only
* @param bool $until If true, and if using date only, shows previous date
* @return string Date
*/
protected function show_time($time, $dateonly, $until = false) {
// For 'until' dates that are at midnight, e.g. midnight 5 March, it
// is better to word the text as 'until end 4 March'.
$daybefore = false;
if ($until && $dateonly) {
$daybefore = true;
$time = strtotime('-1 day', $time);
}
return userdate($time,
get_string($dateonly ? 'strftimedate' : 'strftimedatetime', 'langconfig'));
}
/**
* 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 otherwise
*/
protected static function is_midnight($time) {
return usergetmidnight($time) == $time;
}
public function update_after_restore(
$restoreid, $courseid, \base_logger $logger, $name) {
// Update the date, if restoring with changed date.
$dateoffset = \core_availability\info::get_restore_date_offset($restoreid);
if ($dateoffset) {
$this->time += $dateoffset;
return true;
}
return false;
}
/**
* Changes all date restrictions on a course by the specified shift amount.
* Used by the course reset feature.
*
* @param int $courseid Course id
* @param int $timeshift Offset in seconds
*/
public static function update_all_dates($courseid, $timeshift) {
global $DB;
$modinfo = get_fast_modinfo($courseid);
$anychanged = false;
// Adjust dates from all course modules.
foreach ($modinfo->cms as $cm) {
if (!$cm->availability) {
continue;
}
$info = new \core_availability\info_module($cm);
$tree = $info->get_availability_tree();
$dates = $tree->get_all_children('availability_date\condition');
$changed = false;
foreach ($dates as $date) {
$date->time += $timeshift;
$changed = true;
}
// Save the updated course module.
if ($changed) {
$DB->set_field('course_modules', 'availability', json_encode($tree->save()),
array('id' => $cm->id));
$anychanged = true;
}
}
// Adjust dates from all course sections.
foreach ($modinfo->get_section_info_all() as $section) {
if (!$section->availability) {
continue;
}
$info = new \core_availability\info_section($section);
$tree = $info->get_availability_tree();
$dates = $tree->get_all_children('availability_date\condition');
$changed = false;
foreach ($dates as $date) {
$date->time += $timeshift;
$changed = true;
}
// Save the updated course module.
if ($changed) {
$updatesection = new \stdClass();
$updatesection->id = $section->id;
$updatesection->availability = json_encode($tree->save());
$updatesection->timemodified = time();
$DB->update_record('course_sections', $updatesection);
// Invalidate the section cache by given section id.
\course_modinfo::purge_course_section_cache_by_id($courseid, $section->id);
$anychanged = true;
}
}
if ($anychanged) {
// Partial rebuild the sections which have been invalidated.
rebuild_course_cache($courseid, true, true);
}
}
}
@@ -0,0 +1,174 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Front-end class.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace availability_date;
defined('MOODLE_INTERNAL') || die();
/**
* Front-end class.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class frontend extends \core_availability\frontend {
/**
* The date selector popup is not currently supported because the date
* selector is in a messy state (about to be replaced with a new YUI3
* implementation) and MDL-44814 was rejected. I have left the code in
* place, but disabled. When the date selector situation is finalised,
* then this constant should be removed (either applying MDL-44814 if old
* selector is still in use, or modifying the JavaScript code to support the
* new date selector if it has landed).
*
* @var bool
*/
const DATE_SELECTOR_SUPPORTED = false;
protected function get_javascript_strings() {
return array('ajaxerror', 'direction_before', 'direction_from', 'direction_until',
'direction_label', 'error_dateconflict');
}
/**
* Given field values, obtains the corresponding timestamp.
*
* @param int $year Year
* @param int $month Month
* @param int $day Day
* @param int $hour Hour
* @param int $minute Minute
* @return int Timestamp
*/
public static function get_time_from_fields($year, $month, $day, $hour, $minute) {
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$gregoriandate = $calendartype->convert_to_gregorian(
$year, $month, $day, $hour, $minute);
return make_timestamp($gregoriandate['year'], $gregoriandate['month'],
$gregoriandate['day'], $gregoriandate['hour'], $gregoriandate['minute'], 0);
}
/**
* Given a timestamp, obtains corresponding field values.
*
* @param int $time Timestamp
* @return \stdClass Object with fields for year, month, day, hour, minute
*/
public static function get_fields_from_time($time) {
$calendartype = \core_calendar\type_factory::get_calendar_instance();
$wrongfields = $calendartype->timestamp_to_date_array($time);
return array('day' => $wrongfields['mday'],
'month' => $wrongfields['mon'], 'year' => $wrongfields['year'],
'hour' => $wrongfields['hours'], 'minute' => $wrongfields['minutes']);
}
protected function get_javascript_init_params($course, \cm_info $cm = null,
\section_info $section = null) {
global $CFG, $OUTPUT;
require_once($CFG->libdir . '/formslib.php');
// Support internationalised calendars.
$calendartype = \core_calendar\type_factory::get_calendar_instance();
// Get current date, but set time to 00:00 (to make it easier to
// specify whole days) and change name of mday field to match below.
$wrongfields = $calendartype->timestamp_to_date_array(time());
$current = array('day' => $wrongfields['mday'],
'month' => $wrongfields['mon'], 'year' => $wrongfields['year'],
'hour' => 0, 'minute' => 0);
// Time part is handled the same everywhere.
$hours = array();
for ($i = 0; $i <= 23; $i++) {
$hours[$i] = sprintf("%02d", $i);
}
$minutes = array();
for ($i = 0; $i < 60; $i += 5) {
$minutes[$i] = sprintf("%02d", $i);
}
// List date fields.
$fields = $calendartype->get_date_order(
$calendartype->get_min_year(), $calendartype->get_max_year());
// Add time fields - in RTL mode these are switched.
$fields['split'] = '/';
if (right_to_left()) {
$fields['minute'] = $minutes;
$fields['colon'] = ':';
$fields['hour'] = $hours;
} else {
$fields['hour'] = $hours;
$fields['colon'] = ':';
$fields['minute'] = $minutes;
}
// Output all date fields.
$html = '<span class="availability-group">';
foreach ($fields as $field => $options) {
if ($options === '/') {
$html = rtrim($html);
// In Gregorian calendar mode only, we support a date selector popup, reusing
// code from form to ensure consistency.
if ($calendartype->get_name() === 'gregorian' && self::DATE_SELECTOR_SUPPORTED) {
$image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
$html .= ' ' . \html_writer::link('#', $image, array('name' => 'x[calendar]'));
form_init_date_js();
}
$html .= '</span> <span class="availability-group">';
continue;
}
if ($options === ':') {
$html .= ': ';
continue;
}
$html .= \html_writer::start_tag('label');
$html .= \html_writer::span(get_string($field) . ' ', 'accesshide');
// NOTE: The fields need to have these weird names in order that they
// match the standard Moodle form control, otherwise the date selector
// won't find them.
$html .= \html_writer::start_tag('select', array('name' => 'x[' . $field . ']', 'class' => 'custom-select'));
foreach ($options as $key => $value) {
$params = array('value' => $key);
if ($current[$field] == $key) {
$params['selected'] = 'selected';
}
$html .= \html_writer::tag('option', s($value), $params);
}
$html .= \html_writer::end_tag('select');
$html .= \html_writer::end_tag('label');
$html .= ' ';
}
$html = rtrim($html) . '</span>';
// Also get the time that corresponds to this default date.
$time = self::get_time_from_fields($current['year'], $current['month'],
$current['day'], $current['hour'], $current['minute']);
return array($html, $time);
}
}
@@ -0,0 +1,46 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy Subsystem implementation for availability_date.
*
* @package availability_date
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace availability_date\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for availability_date implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
@@ -0,0 +1,42 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Language strings.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['ajaxerror'] = 'Error contacting server to convert times';
$string['direction_before'] = 'Date';
$string['direction_from'] = 'from';
$string['direction_label'] = 'Direction';
$string['direction_until'] = 'until';
$string['description'] = 'Prevent access until (or from) a specified date and time.';
$string['error_dateconflict'] = 'Conflicts with other date restrictions';
$string['full_from'] = 'It is after <strong>{$a}</strong>';
$string['full_from_date'] = 'It is on or after <strong>{$a}</strong>';
$string['full_until'] = 'It is before <strong>{$a}</strong>';
$string['full_until_date'] = 'It is before end of <strong>{$a}</strong>';
$string['pluginname'] = 'Restriction by date';
$string['short_from'] = 'Available from <strong>{$a}</strong>';
$string['short_from_date'] = 'Available from <strong>{$a}</strong>';
$string['short_until'] = 'Available until <strong>{$a}</strong>';
$string['short_until_date'] = 'Available until end of <strong>{$a}</strong>';
$string['title'] = 'Date';
$string['privacy:metadata'] = 'The Restriction by date plugin does not store any personal data.';
@@ -0,0 +1,50 @@
@availability @availability_date
Feature: availability_date
In order to control student access to activities
As a teacher
I need to set date conditions which prevent student access
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
And the following "users" exist:
| username |
| teacher1 |
| student1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | course | name |
| page | C1 | Page 1 |
| page | C1 | Page 2 |
@javascript
Scenario: Test condition
# Add a Page with a date condition that does match (from the past).
Given I am on the "Page 1" "page activity editing" page logged in as "teacher1"
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I click on ".availability-item .availability-eye img" "css_element"
And I set the field "year" to "2013"
And I press "Save and return to course"
# Add a Page with a date condition that doesn't match (until the past).
And I am on the "Page 2" "page activity editing" page
And I expand all fieldsets
And I click on "Add restriction..." "button"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I click on ".availability-item .availability-eye img" "css_element"
And I set the field "Direction" to "until"
And I set the field "year" to "2013"
And I press "Save and return to course"
# Log back in as student.
When I am on the "Course 1" "course" page logged in as "student1"
# Page 1 should appear, but page 2 does not.
Then I should see "Page 1" in the "region-main" "region"
And I should not see "Page 2" in the "region-main" "region"
@@ -0,0 +1,105 @@
@availability @availability_date @javascript
Feature: As a teacher I can set availability dates restriction to an activity and see a warning when conflicting dates are set
Background:
Given the following "courses" exist:
| fullname | shortname | format | enablecompletion |
| Course 1 | C1 | topics | 1 |
And the following "users" exist:
| username |
| teacher1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | intro | introformat | course | content | contentformat | idnumber |
| page | PageName1 | PageDesc1 | 1 | C1 | Page 1 | 1 | 1 |
Scenario: When I set dates to potential conflicting dates in the same subset, I should see a warning.
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "2" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "2" "availability_date > Date Restriction" to "until"
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "3" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "3" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "3" "availability_date > Date Restriction" to "6"
When I set the field "Direction" in the "3" "availability_date > Date Restriction" to "from"
Then I should see "Conflicts with other date restrictions"
Scenario: If there are conflicting dates in the same subset, I should not see a warning if condition are separated by "any".
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "1.2" "availability_date > Date Restriction" to "until"
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.3" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.3" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.3" "availability_date > Date Restriction" to "6"
And I set the field "Direction" in the "1.3" "availability_date > Date Restriction" to "from"
When I set the field "Required restrictions" in the "1" "core_availability > Set Of Restrictions" to "any"
Then I should not see "Conflicts with other date restrictions"
Scenario: There should a conflicting availability dates are in the same subset separated by "all".
Given I am on the PageName1 "page activity editing" page logged in as teacher1
And I expand all fieldsets
# Root level: Student "must" match the following.
And I click on "Add restriction..." "button" in the "root" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
# This is the second level: Student "must" match any of the following.
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
# And now the third and final level.
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1.1" "availability_date > Date Restriction" to "2"
And I set the field "Direction" in the "1.1.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1.1" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.1.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.1.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.1.2" "availability_date > Date Restriction" to "3"
And I set the field "Direction" in the "1.1.2" "availability_date > Date Restriction" to "until"
# Then add a restriction to the second level.
And I click on "Add restriction..." "button" in the "1" "core_availability > Availability Button Area"
And I click on "Restriction set" "button" in the "Add restriction..." "dialogue"
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2.1" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2.1" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2.1" "availability_date > Date Restriction" to "4"
And I set the field "Direction" in the "1.2.1" "availability_date > Date Restriction" to "from"
And I click on "Add restriction..." "button" in the "1.2" "core_availability > Availability Button Area"
And I click on "Date" "button" in the "Add restriction..." "dialogue"
And I set the field "year" in the "1.2.2" "availability_date > Date Restriction" to "2023"
And I set the field "Month" in the "1.2.2" "availability_date > Date Restriction" to "April"
And I set the field "day" in the "1.2.2" "availability_date > Date Restriction" to "3"
When I set the field "Direction" in the "1.2.2" "availability_date > Date Restriction" to "until"
# Same subset, we can detect conflicts.
Then I should see "Conflicts with other date restrictions"
@@ -0,0 +1,39 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use Behat\Mink\Element\NodeElement;
/**
* Behat availabilty-related steps definitions.
*
* @package availability_date
* @category test
* @copyright 2023 Laurent David <laurent.david@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_availability_date extends behat_base {
/**
* Return the list of partial named selectors.
*
* @return array
*/
public static function get_partial_named_selectors(): array {
return [
new behat_component_named_selector(
'Date Restriction', ["//div[h3[@data-restriction-order=%locator% and contains(text(), 'Date restriction')]]"]
),
];
}
}
@@ -0,0 +1,280 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace availability_date;
use core_availability\tree;
/**
* Unit tests for the date condition.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class condition_test extends \advanced_testcase {
/**
* Load required classes.
*/
public function setUp(): void {
// Load the mock info class so that it can be used.
global $CFG;
require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php');
}
/**
* Tests constructing and using date condition as part of tree.
*/
public function test_in_tree(): void {
global $SITE, $USER, $CFG;
$this->resetAfterTest();
$this->setAdminUser();
// Set server timezone for test. (Important as otherwise the timezone
// could be anything - this is modified by other unit tests, too.)
$this->setTimezone('UTC');
// SEt user to GMT+5.
$USER->timezone = 5;
// Construct tree with date condition.
$time = strtotime('2014-02-18 14:20:00 GMT');
$structure = (object)array('op' => '|', 'show' => true, 'c' => array(
(object)array('type' => 'date', 'd' => '>=', 't' => $time)));
$tree = new \core_availability\tree($structure);
$info = new \core_availability\mock_info();
// Check if available (when not available).
condition::set_current_time_for_test($time - 1);
$information = '';
$result = $tree->check_available(false, $info, true, $USER->id);
$this->assertFalse($result->is_available());
$information = $tree->get_result_information($info, $result);
// Note: PM is normally upper-case, but an issue with PHP on Mac means
// that on that platform, it is reported lower-case.
$this->assertMatchesRegularExpression('~from.*18 February 2014, 7:20 (PM|pm)~', $information);
// Check if available (when available).
condition::set_current_time_for_test($time);
$result = $tree->check_available(false, $info, true, $USER->id);
$this->assertTrue($result->is_available());
$information = $tree->get_result_information($info, $result);
$this->assertEquals('', $information);
}
/**
* Tests the constructor including error conditions. Also tests the
* string conversion feature (intended for debugging only).
*/
public function test_constructor(): void {
// No parameters.
$structure = (object)array();
try {
$date = new condition($structure);
$this->fail();
} catch (\coding_exception $e) {
$this->assertStringContainsString('Missing or invalid ->d', $e->getMessage());
}
// Invalid ->d.
$structure->d = 'woo hah!!';
try {
$date = new condition($structure);
$this->fail();
} catch (\coding_exception $e) {
$this->assertStringContainsString('Missing or invalid ->d', $e->getMessage());
}
// Missing ->t.
$structure->d = '>=';
try {
$date = new condition($structure);
$this->fail();
} catch (\coding_exception $e) {
$this->assertStringContainsString('Missing or invalid ->t', $e->getMessage());
}
// Invalid ->t.
$structure->t = 'got you all in check';
try {
$date = new condition($structure);
$this->fail();
} catch (\coding_exception $e) {
$this->assertStringContainsString('Missing or invalid ->t', $e->getMessage());
}
// Valid conditions of both types.
$structure = (object)array('d' => '>=', 't' => strtotime('2014-02-18 14:43:17 GMT'));
$date = new condition($structure);
$this->assertEquals('{date:>= 2014-02-18 14:43:17}', (string)$date);
$structure->d = '<';
$date = new condition($structure);
$this->assertEquals('{date:< 2014-02-18 14:43:17}', (string)$date);
}
/**
* Tests the save() function.
*/
public function test_save(): void {
$structure = (object)array('d' => '>=', 't' => 12345);
$cond = new condition($structure);
$structure->type = 'date';
$this->assertEquals($structure, $cond->save());
}
/**
* Tests the is_available() and is_available_to_all() functions.
*/
public function test_is_available(): void {
global $SITE, $USER;
$time = strtotime('2014-02-18 14:50:10 GMT');
$info = new \core_availability\mock_info();
// Test with >=.
$date = new condition((object)array('d' => '>=', 't' => $time));
condition::set_current_time_for_test($time - 1);
$this->assertFalse($date->is_available(false, $info, true, $USER->id));
condition::set_current_time_for_test($time);
$this->assertTrue($date->is_available(false, $info, true, $USER->id));
// Test with <.
$date = new condition((object)array('d' => '<', 't' => $time));
condition::set_current_time_for_test($time);
$this->assertFalse($date->is_available(false, $info, true, $USER->id));
condition::set_current_time_for_test($time - 1);
$this->assertTrue($date->is_available(false, $info, true, $USER->id));
// Repeat this test with is_available_to_all() - it should be the same.
$date = new condition((object)array('d' => '<', 't' => $time));
condition::set_current_time_for_test($time);
$this->assertFalse($date->is_available_for_all(false));
condition::set_current_time_for_test($time - 1);
$this->assertTrue($date->is_available_for_all(false));
}
/**
* Tests the get_description and get_standalone_description functions.
*/
public function test_get_description(): void {
global $SITE, $CFG;
$this->resetAfterTest();
$this->setTimezone('UTC');
$modinfo = get_fast_modinfo($SITE);
$info = new \core_availability\mock_info();
$time = strtotime('2014-02-18 14:55:01 GMT');
// Test with >=.
$date = new condition((object)array('d' => '>=', 't' => $time));
$information = $date->get_description(true, false, $info);
$this->assertMatchesRegularExpression('~after.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_description(true, true, $info);
$this->assertMatchesRegularExpression('~before.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_standalone_description(true, false, $info);
$this->assertMatchesRegularExpression('~from.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_standalone_description(true, true, $info);
$this->assertMatchesRegularExpression('~until.*18 February 2014, 2:55 (PM|pm)~', $information);
// Test with <.
$date = new condition((object)array('d' => '<', 't' => $time));
$information = $date->get_description(true, false, $info);
$this->assertMatchesRegularExpression('~before.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_description(true, true, $info);
$this->assertMatchesRegularExpression('~after.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_standalone_description(true, false, $info);
$this->assertMatchesRegularExpression('~until.*18 February 2014, 2:55 (PM|pm)~', $information);
$information = $date->get_standalone_description(true, true, $info);
$this->assertMatchesRegularExpression('~from.*18 February 2014, 2:55 (PM|pm)~', $information);
// Test special case for dates that are midnight.
$date = new condition((object)array('d' => '>=',
't' => strtotime('2014-03-05 00:00 GMT')));
$information = $date->get_description(true, false, $info);
$this->assertMatchesRegularExpression('~on or after.*5 March 2014([^0-9]*)$~', $information);
$information = $date->get_description(true, true, $info);
$this->assertMatchesRegularExpression('~before.*end of.*4 March 2014([^0-9]*)$~', $information);
$information = $date->get_standalone_description(true, false, $info);
$this->assertMatchesRegularExpression('~from.*5 March 2014([^0-9]*)$~', $information);
$information = $date->get_standalone_description(true, true, $info);
$this->assertMatchesRegularExpression('~until end of.*4 March 2014([^0-9]*)$~', $information);
// In the 'until' case for midnight, it shows the previous day. (I.e.
// if the date is 5 March 00:00, then we show it as available until 4
// March, implying 'the end of'.)
$date = new condition((object)array('d' => '<',
't' => strtotime('2014-03-05 00:00 GMT')));
$information = $date->get_description(true, false, $info);
$this->assertMatchesRegularExpression('~before end of.*4 March 2014([^0-9]*)$~', $information);
$information = $date->get_description(true, true, $info);
$this->assertMatchesRegularExpression('~on or after.*5 March 2014([^0-9]*)$~', $information);
$information = $date->get_standalone_description(true, false, $info);
$this->assertMatchesRegularExpression('~until end of.*4 March 2014([^0-9]*)$~', $information);
$information = $date->get_standalone_description(true, true, $info);
$this->assertMatchesRegularExpression('~from.*5 March 2014([^0-9]*)$~', $information);
}
/**
* Tests the update_all_dates function.
*/
public function test_update_all_dates(): void {
global $DB;
$this->resetAfterTest();
// Create a course with 3 pages.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$rec = array('course' => $course);
$page1 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
$page2 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
$page3 = $generator->get_plugin_generator('mod_page')->create_instance($rec);
// Set the availability page 2 to a simple date condition. You can access
// it from 1337 onwards.
$simplecondition = tree::get_root_json(array(
condition::get_json(condition::DIRECTION_FROM, 1337)));
$DB->set_field('course_modules', 'availability',
json_encode($simplecondition), array('id' => $page2->cmid));
// Set page 3 to a complex set of conditions including a nested date condition.
// You can access it until 1459, *or* after 2810 if you belong to a group.
$complexcondition = tree::get_root_json(array(
condition::get_json(condition::DIRECTION_UNTIL, 1459),
tree::get_nested_json(array(
condition::get_json(condition::DIRECTION_FROM, 2810),
\availability_group\condition::get_json()))),
tree::OP_OR);
$DB->set_field('course_modules', 'availability',
json_encode($complexcondition), array('id' => $page3->cmid));
// Now use the update_all_dates function to move date forward 100000.
condition::update_all_dates($course->id, 100000);
// Get the expected conditions after adjusting time, and compare to database.
$simplecondition->c[0]->t = 101337;
$complexcondition->c[0]->t = 101459;
$complexcondition->c[1]->c[0]->t = 102810;
$this->assertEquals($simplecondition, json_decode(
$DB->get_field('course_modules', 'availability', array('id' => $page2->cmid))));
$this->assertEquals($complexcondition, json_decode(
$DB->get_field('course_modules', 'availability', array('id' => $page3->cmid))));
// The one without availability conditions should still be null.
$this->assertNull($DB->get_field('course_modules', 'availability', array('id' => $page1->cmid)));
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Version info.
*
* @package availability_date
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->component = 'availability_date';
@@ -0,0 +1,241 @@
YUI.add('moodle-availability_date-form', function (Y, NAME) {
/**
* JavaScript for form editing date conditions.
*
* @module moodle-availability_date-form
*/
M.availability_date = M.availability_date || {};
/**
* @class M.availability_date.form
* @extends M.core_availability.plugin
*/
M.availability_date.form = Y.Object(M.core_availability.plugin);
/**
* Initialises this plugin.
*
* Because the date fields are complex depending on Moodle calendar settings,
* we create the HTML for these fields in PHP and pass it to this method.
*
* @method initInner
* @param {String} html HTML to use for date fields
* @param {Number} defaultTime Time value that corresponds to initial fields
*/
M.availability_date.form.initInner = function(html, defaultTime) {
this.html = html;
this.defaultTime = defaultTime;
};
M.availability_date.form.getNode = function(json) {
var html = '<span class="col-form-label pr-3">' +
M.util.get_string('direction_before', 'availability_date') + '</span> <span class="availability-group">' +
'<label><span class="accesshide">' + M.util.get_string('direction_label', 'availability_date') + ' </span>' +
'<select name="direction" class="custom-select">' +
'<option value="&gt;=">' + M.util.get_string('direction_from', 'availability_date') + '</option>' +
'<option value="&lt;">' + M.util.get_string('direction_until', 'availability_date') + '</option>' +
'</select></label></span> ' + this.html;
var node = Y.Node.create('<span>' + html + '</span>');
// Set initial value if non-default.
if (json.t !== undefined) {
node.setData('time', json.t);
// Disable everything.
node.all('select:not([name=direction])').each(function(select) {
select.set('disabled', true);
});
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=fromtime' +
'&time=' + json.t;
Y.io(url, {on: {
success: function(id, response) {
var fields = Y.JSON.parse(response.responseText);
for (var field in fields) {
var select = node.one('select[name=x\\[' + field + '\\]]');
select.set('value', '' + fields[field]);
select.set('disabled', false);
}
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
} else {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
// Add event handlers (first time only).
if (!M.availability_date.form.addedEvents) {
M.availability_date.form.addedEvents = true;
var root = Y.one('.availability-field');
root.delegate('change', function() {
// For the direction, just update the form fields.
M.core_availability.form.update();
}, '.availability_date select[name=direction]');
root.delegate('change', function() {
// Update time using AJAX call from root node.
M.availability_date.form.updateTime(this.ancestor('span.availability_date'));
}, '.availability_date select:not([name=direction])');
}
if (node.one('a[href=#]')) {
// Add the date selector magic.
M.form.dateselector.init_single_date_selector(node);
// This special handler detects when the date selector changes the year.
var yearSelect = node.one('select[name=x\\[year\\]]');
var oldSet = yearSelect.set;
yearSelect.set = function(name, value) {
oldSet.call(yearSelect, name, value);
if (name === 'selectedIndex') {
// Do this after timeout or the other fields haven't been set yet.
setTimeout(function() {
M.availability_date.form.updateTime(node);
}, 0);
}
};
}
return node;
};
/**
* Updates time from AJAX. Whenever the field values change, we recompute the
* actual time via an AJAX request to Moodle.
*
* This will set the 'time' data on the node and then update the form, once it
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
// actual time using AJAX because it depends on the user's
// time zone and calendar options.
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=totime' +
'&year=' + node.one('select[name=x\\[year\\]]').get('value') +
'&month=' + node.one('select[name=x\\[month\\]]').get('value') +
'&day=' + node.one('select[name=x\\[day\\]]').get('value') +
'&hour=' + node.one('select[name=x\\[hour\\]]').get('value') +
'&minute=' + node.one('select[name=x\\[minute\\]]').get('value');
Y.io(url, {on: {
success: function(id, response) {
node.setData('time', response.responseText);
M.core_availability.form.update();
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
};
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in the same branch.
*
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
}
return null;
};
return siblingsFinderRecursive(tree);
};
/**
* Check current node.
*
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
var error = M.availability_date.form.checkConditionDate(node);
if (error) {
errors.push('availability_date:error_dateconflict');
}
};
}, '@VERSION@', {"requires": ["base", "node", "event", "io", "moodle-core_availability-form"]});
@@ -0,0 +1 @@
YUI.add("moodle-availability_date-form",function(o,e){M.availability_date=M.availability_date||{},M.availability_date.form=o.Object(M.core_availability.plugin),M.availability_date.form.initInner=function(e,a){this.html=e,this.defaultTime=a},M.availability_date.form.getNode=function(e){var t,i,a='<span class="col-form-label pr-3">'+M.util.get_string("direction_before","availability_date")+'</span> <span class="availability-group"><label><span class="accesshide">'+M.util.get_string("direction_label","availability_date")+' </span><select name="direction" class="custom-select"><option value="&gt;=">'+M.util.get_string("direction_from","availability_date")+'</option><option value="&lt;">'+M.util.get_string("direction_until","availability_date")+"</option></select></label></span> "+this.html,l=o.Node.create("<span>"+a+"</span>");return e.t!==undefined?(l.setData("time",e.t),l.all("select:not([name=direction])").each(function(e){e.set("disabled",!0)}),a=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=fromtime&time="+e.t,o.io(a,{on:{success:function(e,a){var t,i,n=o.JSON.parse(a.responseText);for(t in n)(i=l.one("select[name=x\\["+t+"\\]]")).set("value",""+n[t]),i.set("disabled",!1)},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})):l.setData("time",this.defaultTime),e.nodeUID===undefined&&(a=new Date,e.nodeUID=a.getTime()),l.setData("nodeUID",e.nodeUID),e.d!==undefined&&l.one("select[name=direction]").set("value",e.d),M.availability_date.form.addedEvents||(M.availability_date.form.addedEvents=!0,(a=o.one(".availability-field")).delegate("change",function(){M.core_availability.form.update()},".availability_date select[name=direction]"),a.delegate("change",function(){M.availability_date.form.updateTime(this.ancestor("span.availability_date"))},".availability_date select:not([name=direction])")),l.one("a[href=#]")&&(M.form.dateselector.init_single_date_selector(l),t=l.one("select[name=x\\[year\\]]"),i=t.set,t.set=function(e,a){i.call(t,e,a),"selectedIndex"===e&&setTimeout(function(){M.availability_date.form.updateTime(l)},0)}),l},M.availability_date.form.updateTime=function(t){var e=M.cfg.wwwroot+"/availability/condition/date/ajax.php?action=totime&year="+t.one("select[name=x\\[year\\]]").get("value")+"&month="+t.one("select[name=x\\[month\\]]").get("value")+"&day="+t.one("select[name=x\\[day\\]]").get("value")+"&hour="+t.one("select[name=x\\[hour\\]]").get("value")+"&minute="+t.one("select[name=x\\[minute\\]]").get("value");o.io(e,{on:{success:function(e,a){t.setData("time",a.responseText),M.core_availability.form.update()},failure:function(){window.alert(M.util.get_string("ajaxerror","availability_date"))}}})},M.availability_date.form.fillValue=function(e,a){e.d=a.one("select[name=direction]").get("value"),e.t=parseInt(a.getData("time"),10),e.nodeUID=a.getData("nodeUID")},M.availability_date.form.findAllDateSiblings=function(e,d){var r,c=function(e){var a,t,i,n=[],l=!1,o=e.op!==undefined?e.op:null;if(e.c!==undefined){for(i=e.c,a=0;a<i.length;a++){if((r=i.at(a)).type===undefined&&(t=c(r)))return t;"date"===r.type&&(d===r.nodeUID?l=!0:"&"===o&&n.push(r))}if(l)return n}return null};return c(e)},M.availability_date.form.checkConditionDate=function(e){var a=!1,t=e.getData("nodeUID"),i=e.one("select[name=direction]").get("value"),n=parseInt(e.getData("time"),10),e=M.availability_date.form.findAllDateSiblings(M.core_availability.form.rootList.getValue(),t);return e&&e.forEach(function(e){return"<"===e.d?">="===i&&n>=e.t&&(a=!0):"<"===i&&n<=e.t&&(a=!0),a}),a},M.availability_date.form.fillErrors=function(e,a){M.availability_date.form.checkConditionDate(a)&&e.push("availability_date:error_dateconflict")}},"@VERSION@",{requires:["base","node","event","io","moodle-core_availability-form"]});
@@ -0,0 +1,241 @@
YUI.add('moodle-availability_date-form', function (Y, NAME) {
/**
* JavaScript for form editing date conditions.
*
* @module moodle-availability_date-form
*/
M.availability_date = M.availability_date || {};
/**
* @class M.availability_date.form
* @extends M.core_availability.plugin
*/
M.availability_date.form = Y.Object(M.core_availability.plugin);
/**
* Initialises this plugin.
*
* Because the date fields are complex depending on Moodle calendar settings,
* we create the HTML for these fields in PHP and pass it to this method.
*
* @method initInner
* @param {String} html HTML to use for date fields
* @param {Number} defaultTime Time value that corresponds to initial fields
*/
M.availability_date.form.initInner = function(html, defaultTime) {
this.html = html;
this.defaultTime = defaultTime;
};
M.availability_date.form.getNode = function(json) {
var html = '<span class="col-form-label pr-3">' +
M.util.get_string('direction_before', 'availability_date') + '</span> <span class="availability-group">' +
'<label><span class="accesshide">' + M.util.get_string('direction_label', 'availability_date') + ' </span>' +
'<select name="direction" class="custom-select">' +
'<option value="&gt;=">' + M.util.get_string('direction_from', 'availability_date') + '</option>' +
'<option value="&lt;">' + M.util.get_string('direction_until', 'availability_date') + '</option>' +
'</select></label></span> ' + this.html;
var node = Y.Node.create('<span>' + html + '</span>');
// Set initial value if non-default.
if (json.t !== undefined) {
node.setData('time', json.t);
// Disable everything.
node.all('select:not([name=direction])').each(function(select) {
select.set('disabled', true);
});
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=fromtime' +
'&time=' + json.t;
Y.io(url, {on: {
success: function(id, response) {
var fields = Y.JSON.parse(response.responseText);
for (var field in fields) {
var select = node.one('select[name=x\\[' + field + '\\]]');
select.set('value', '' + fields[field]);
select.set('disabled', false);
}
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
} else {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
// Add event handlers (first time only).
if (!M.availability_date.form.addedEvents) {
M.availability_date.form.addedEvents = true;
var root = Y.one('.availability-field');
root.delegate('change', function() {
// For the direction, just update the form fields.
M.core_availability.form.update();
}, '.availability_date select[name=direction]');
root.delegate('change', function() {
// Update time using AJAX call from root node.
M.availability_date.form.updateTime(this.ancestor('span.availability_date'));
}, '.availability_date select:not([name=direction])');
}
if (node.one('a[href=#]')) {
// Add the date selector magic.
M.form.dateselector.init_single_date_selector(node);
// This special handler detects when the date selector changes the year.
var yearSelect = node.one('select[name=x\\[year\\]]');
var oldSet = yearSelect.set;
yearSelect.set = function(name, value) {
oldSet.call(yearSelect, name, value);
if (name === 'selectedIndex') {
// Do this after timeout or the other fields haven't been set yet.
setTimeout(function() {
M.availability_date.form.updateTime(node);
}, 0);
}
};
}
return node;
};
/**
* Updates time from AJAX. Whenever the field values change, we recompute the
* actual time via an AJAX request to Moodle.
*
* This will set the 'time' data on the node and then update the form, once it
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
// actual time using AJAX because it depends on the user's
// time zone and calendar options.
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=totime' +
'&year=' + node.one('select[name=x\\[year\\]]').get('value') +
'&month=' + node.one('select[name=x\\[month\\]]').get('value') +
'&day=' + node.one('select[name=x\\[day\\]]').get('value') +
'&hour=' + node.one('select[name=x\\[hour\\]]').get('value') +
'&minute=' + node.one('select[name=x\\[minute\\]]').get('value');
Y.io(url, {on: {
success: function(id, response) {
node.setData('time', response.responseText);
M.core_availability.form.update();
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
};
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in the same branch.
*
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
}
return null;
};
return siblingsFinderRecursive(tree);
};
/**
* Check current node.
*
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
var error = M.availability_date.form.checkConditionDate(node);
if (error) {
errors.push('availability_date:error_dateconflict');
}
};
}, '@VERSION@', {"requires": ["base", "node", "event", "io", "moodle-core_availability-form"]});
@@ -0,0 +1,10 @@
{
"name": "moodle-availability_date-form",
"builds": {
"moodle-availability_date-form": {
"jsfiles": [
"form.js"
]
}
}
}
+236
View File
@@ -0,0 +1,236 @@
/**
* JavaScript for form editing date conditions.
*
* @module moodle-availability_date-form
*/
M.availability_date = M.availability_date || {};
/**
* @class M.availability_date.form
* @extends M.core_availability.plugin
*/
M.availability_date.form = Y.Object(M.core_availability.plugin);
/**
* Initialises this plugin.
*
* Because the date fields are complex depending on Moodle calendar settings,
* we create the HTML for these fields in PHP and pass it to this method.
*
* @method initInner
* @param {String} html HTML to use for date fields
* @param {Number} defaultTime Time value that corresponds to initial fields
*/
M.availability_date.form.initInner = function(html, defaultTime) {
this.html = html;
this.defaultTime = defaultTime;
};
M.availability_date.form.getNode = function(json) {
var html = '<span class="col-form-label pr-3">' +
M.util.get_string('direction_before', 'availability_date') + '</span> <span class="availability-group">' +
'<label><span class="accesshide">' + M.util.get_string('direction_label', 'availability_date') + ' </span>' +
'<select name="direction" class="custom-select">' +
'<option value="&gt;=">' + M.util.get_string('direction_from', 'availability_date') + '</option>' +
'<option value="&lt;">' + M.util.get_string('direction_until', 'availability_date') + '</option>' +
'</select></label></span> ' + this.html;
var node = Y.Node.create('<span>' + html + '</span>');
// Set initial value if non-default.
if (json.t !== undefined) {
node.setData('time', json.t);
// Disable everything.
node.all('select:not([name=direction])').each(function(select) {
select.set('disabled', true);
});
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=fromtime' +
'&time=' + json.t;
Y.io(url, {on: {
success: function(id, response) {
var fields = Y.JSON.parse(response.responseText);
for (var field in fields) {
var select = node.one('select[name=x\\[' + field + '\\]]');
select.set('value', '' + fields[field]);
select.set('disabled', false);
}
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
} else {
// Set default time that corresponds to the HTML selectors.
node.setData('time', this.defaultTime);
}
if (json.nodeUID === undefined) {
var miliTime = new Date();
json.nodeUID = miliTime.getTime();
}
node.setData('nodeUID', json.nodeUID);
if (json.d !== undefined) {
node.one('select[name=direction]').set('value', json.d);
}
// Add event handlers (first time only).
if (!M.availability_date.form.addedEvents) {
M.availability_date.form.addedEvents = true;
var root = Y.one('.availability-field');
root.delegate('change', function() {
// For the direction, just update the form fields.
M.core_availability.form.update();
}, '.availability_date select[name=direction]');
root.delegate('change', function() {
// Update time using AJAX call from root node.
M.availability_date.form.updateTime(this.ancestor('span.availability_date'));
}, '.availability_date select:not([name=direction])');
}
if (node.one('a[href=#]')) {
// Add the date selector magic.
M.form.dateselector.init_single_date_selector(node);
// This special handler detects when the date selector changes the year.
var yearSelect = node.one('select[name=x\\[year\\]]');
var oldSet = yearSelect.set;
yearSelect.set = function(name, value) {
oldSet.call(yearSelect, name, value);
if (name === 'selectedIndex') {
// Do this after timeout or the other fields haven't been set yet.
setTimeout(function() {
M.availability_date.form.updateTime(node);
}, 0);
}
};
}
return node;
};
/**
* Updates time from AJAX. Whenever the field values change, we recompute the
* actual time via an AJAX request to Moodle.
*
* This will set the 'time' data on the node and then update the form, once it
* gets an AJAX response.
*
* @method updateTime
* @param {Y.Node} node Node for plugin controls
*/
M.availability_date.form.updateTime = function(node) {
// After a change to the date/time we need to recompute the
// actual time using AJAX because it depends on the user's
// time zone and calendar options.
var url = M.cfg.wwwroot + '/availability/condition/date/ajax.php?action=totime' +
'&year=' + node.one('select[name=x\\[year\\]]').get('value') +
'&month=' + node.one('select[name=x\\[month\\]]').get('value') +
'&day=' + node.one('select[name=x\\[day\\]]').get('value') +
'&hour=' + node.one('select[name=x\\[hour\\]]').get('value') +
'&minute=' + node.one('select[name=x\\[minute\\]]').get('value');
Y.io(url, {on: {
success: function(id, response) {
node.setData('time', response.responseText);
M.core_availability.form.update();
},
failure: function() {
window.alert(M.util.get_string('ajaxerror', 'availability_date'));
}
}});
};
M.availability_date.form.fillValue = function(value, node) {
value.d = node.one('select[name=direction]').get('value');
value.t = parseInt(node.getData('time'), 10);
value.nodeUID = node.getData('nodeUID');
};
/**
* List out Date node value in the same branch.
*
* This will go through all array node and list nodes that are sibling of the current node.
*
* @method findAllDateSiblings
* @param {Array} tree Tree items to convert
* @param {Number} nodeUIDToFind node UID to find.
* @return {Array|null} array of surrounding date avaiability values
*/
M.availability_date.form.findAllDateSiblings = function(tree, nodeUIDToFind) {
var itemValue = null;
var siblingsFinderRecursive = function(itemsTree) {
var dateSiblings = [];
var nodeFound = false;
var index;
var childDates;
var currentOp = itemsTree.op !== undefined ? itemsTree.op : null;
if (itemsTree.c !== undefined) {
var children = itemsTree.c;
for (index = 0; index < children.length; index++) {
itemValue = children.at(index);
if (itemValue.type === undefined) {
childDates = siblingsFinderRecursive(itemValue);
if (childDates) {
return childDates;
}
}
if (itemValue.type === 'date') {
// We go through all tree node, if we meet the current node then we add all nodes in the current branch.
if (nodeUIDToFind === itemValue.nodeUID) {
nodeFound = true;
} else if (currentOp === '&') {
dateSiblings.push(itemValue);
}
}
}
if (nodeFound) {
return dateSiblings;
}
}
return null;
};
return siblingsFinderRecursive(tree);
};
/**
* Check current node.
*
* This will check current date node with all date node in tree node.
*
* @method checkConditionDate
* @param {Y.Node} currentNode The curent node.
*
* @return {boolean} error Return true if the date is conflict.
*/
M.availability_date.form.checkConditionDate = function(currentNode) {
var error = false;
var currentNodeUID = currentNode.getData('nodeUID');
var currentNodeDirection = currentNode.one('select[name=direction]').get('value');
var currentNodeTime = parseInt(currentNode.getData('time'), 10);
var dateSiblings = M.availability_date.form.findAllDateSiblings(
M.core_availability.form.rootList.getValue(),
currentNodeUID);
if (dateSiblings) {
dateSiblings.forEach(function(dateSibling) {
// Validate if the date is conflict.
if (dateSibling.d === '<') {
if (currentNodeDirection === '>=' && currentNodeTime >= dateSibling.t) {
error = true;
}
} else {
if (currentNodeDirection === '<' && currentNodeTime <= dateSibling.t) {
error = true;
}
}
return error;
});
}
return error;
};
M.availability_date.form.fillErrors = function(errors, node) {
var error = M.availability_date.form.checkConditionDate(node);
if (error) {
errors.push('availability_date:error_dateconflict');
}
};
@@ -0,0 +1,11 @@
{
"moodle-availability_date-form": {
"requires": [
"base",
"node",
"event",
"io",
"moodle-core_availability-form"
]
}
}