Commit 8643c576 authored by David Monllaó's avatar David Monllaó
Browse files

MDL-22078 course: Complete proposed patch

- New site setting to define the default course duration (used to set
  the default end date for some course formats)
- End date setting out of restore
- Fix tool_uploadcourse
- Other fixes here and there
parent 9b7f058c
......@@ -92,6 +92,9 @@ if ($hassiteconfig or has_any_capability($capabilities, $systemcontext)) {
$temp->add(new admin_setting_configselect('moodlecourse/coursedisplay', new lang_string('coursedisplay'),
new lang_string('coursedisplay_help'), COURSE_DISPLAY_SINGLEPAGE, $choices));
$temp->add(new admin_setting_configduration('moodlecourse/courseduration', get_string('courseduration'),
get_string('courseduration_desc'), YEARSECS));
// Appearance.
$temp->add(new admin_setting_heading('appearancehdr', new lang_string('appearance'), ''));
......
......@@ -641,6 +641,22 @@ class tool_uploadcourse_course {
$this->do = self::DO_CREATE;
}
// Validate course start and end dates.
if ($exists) {
// We also check existing start and end dates if we are updating an existing course.
$existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
if (empty($coursedata['startdate'])) {
$coursedata['startdate'] = $existingdata->startdate;
}
if (empty($coursedata['enddate'])) {
$coursedata['enddate'] = $existingdata->enddate;
}
}
if ($errorcode = course_validate_dates($coursedata)) {
$this->error($errorcode, new lang_string($errorcode, 'error'));
return false;
}
// Add role renaming.
$errors = array();
$rolenames = tool_uploadcourse_helper::get_role_names($this->rawdata, $errors);
......@@ -905,6 +921,7 @@ class tool_uploadcourse_course {
if (empty($course->enddate)) {
$course->enddate = $DB->get_field_select('course', 'enddate', 'id = :id', array('id' => $course->id));
}
$resetdata->reset_end_date_old = $course->enddate;
// Add roles.
$roles = tool_uploadcourse_helper::get_role_ids();
......
......@@ -24,6 +24,8 @@
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/course/lib.php');
/**
* Specify course upload details.
*
......@@ -95,9 +97,8 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form {
$mform->addHelpButton('defaults[startdate]', 'startdate');
$mform->setDefault('defaults[startdate]', time() + 3600 * 24);
$mform->addElement('date_selector', 'defaults[enddate]', get_string('enddate'));
$mform->addElement('date_selector', 'defaults[enddate]', get_string('enddate'), array('optional' => true));
$mform->addHelpButton('defaults[enddate]', 'enddate');
$mform->setDefault('defaults[enddate]', 0);
$courseformats = get_sorted_course_formats(true);
$formcourseformats = array();
......@@ -194,4 +195,37 @@ class tool_uploadcourse_step2_form extends tool_uploadcourse_base_form {
$mform->closeHeaderBefore('buttonar');
}
/**
* Sets the enddate default after set_data is called.
*/
public function definition_after_data() {
$mform = $this->_form;
// The default end date depends on the course format.
$format = course_get_format((object)array('format' => get_config('moodlecourse', 'format')));
$enddate = $format->get_default_course_enddate($mform, array('startdate' => 'defaults[startdate]'));
// We add 1 day like we do above in startdate.
$mform->setDefault('defaults[enddate]', $enddate + 3600 * 24);
}
/**
* Validation.
*
* @param array $data
* @param array $files
* @return array the errors that were found
*/
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
if ($errorcode = course_validate_dates($data['defaults'])) {
$errors['defaults[enddate]'] = get_string($errorcode, 'error');
}
return $errors;
}
}
......@@ -156,7 +156,7 @@ if (!isset($encodings[$options['encoding']])) {
$defaults = array();
$defaults['category'] = $options['category'];
$defaults['startdate'] = time() + 3600 * 24;
$defaults['enddate'] = 0;
$defaults['enddate'] = $defaults['startdate'] + intval(get_config('moodlecourse', 'courseduration'));
$defaults['newsitems'] = $courseconfig->newsitems;
$defaults['showgrades'] = $courseconfig->showgrades;
$defaults['showreports'] = $courseconfig->showreports;
......
......@@ -104,7 +104,15 @@ if ($form2data = $mform2->is_cancelled()) {
}
} else {
$processor = new tool_uploadcourse_processor($cir, $form1data->options, array());
if (!empty($form1data)) {
$options = $form1data->options;
} else if ($submitteddata = $mform2->get_submitted_data()) {
$options = (array)$submitteddata->options;
} else {
// Weird but we still need to provide a value, setting the default step1_form one.
$options = array('mode' => tool_uploadcourse_processor::MODE_CREATE_NEW);
}
$processor = new tool_uploadcourse_processor($cir, $options, array());
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('uploadcoursespreview', 'tool_uploadcourse'));
$processor->preview($previewrows, new tool_uploadcourse_tracker(tool_uploadcourse_tracker::OUTPUT_HTML));
......
......@@ -172,17 +172,6 @@ class restore_course_task extends restore_task {
$startdate->set_ui(new backup_setting_ui_dateselector($startdate, get_string('setting_course_startdate', 'backup')));
$this->add_setting($startdate);
// Old versions may not have this data.
if (isset($this->get_info()->original_course_enddate)) {
$courseenddate = $this->get_info()->original_course_enddate;
} else {
$courseenddate = 0;
}
$enddate = new restore_course_generic_text_setting('course_enddate',
base_setting::IS_INTEGER, $courseenddate);
$enddate->set_ui(new backup_setting_ui_dateselector($enddate, get_string('setting_course_enddate', 'backup')));
$this->add_setting($enddate);
$keep_enrols = new restore_course_generic_setting('keep_roles_and_enrolments', base_setting::IS_BOOLEAN, false);
$keep_enrols->set_ui(new backup_setting_ui_select($keep_enrols, $keep_enrols->get_name(), array(1=>get_string('yes'), 0=>get_string('no'))));
$keep_enrols->get_ui()->set_label(get_string('setting_keep_roles_and_enrolments', 'backup'));
......
......@@ -1824,7 +1824,6 @@ class restore_course_structure_step extends restore_structure_step {
$fullname = $this->get_setting_value('course_fullname');
$shortname = $this->get_setting_value('course_shortname');
$startdate = $this->get_setting_value('course_startdate');
$enddate = $this->get_setting_value('course_enddate');
// Calculate final course names, to avoid dupes.
list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
......@@ -1871,6 +1870,10 @@ class restore_course_structure_step extends restore_structure_step {
$this->legacyrestrictmodules = !empty($data->restrictmodules);
$data->startdate= $this->apply_date_offset($data->startdate);
if (isset($data->enddate)) {
$data->enddate = $this->apply_date_offset($data->enddate);
}
if ($data->defaultgroupingid) {
$data->defaultgroupingid = $this->get_mappingid('grouping', $data->defaultgroupingid);
}
......@@ -1894,9 +1897,6 @@ class restore_course_structure_step extends restore_structure_step {
$data->format = 'singleactivity';
$data->activitytype = 'scorm';
}
if (isset($data->enddate)) {
$data->enddate = $this->apply_date_offset($data->enddate);
}
// Course record ready, update it
$DB->update_record('course', $data);
......
......@@ -174,9 +174,9 @@ abstract class restore_check {
// settings so that they cannot change it.
$hasrolldatescap = has_capability('moodle/restore:rolldates', $coursectx, $userid);
if (!$hasrolldatescap) {
$datesetting = $restore_controller->get_plan()->get_setting('course_startdate');
if ($datesetting) {
$datesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
$startdatesetting = $restore_controller->get_plan()->get_setting('course_startdate');
if ($startdatesetting) {
$startdatesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
}
}
......@@ -203,10 +203,6 @@ abstract class restore_check {
$overwritesetting = $restore_controller->get_plan()->get_setting('overwrite_conf');
$overwritesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
}
$datesetting = $restore_controller->get_plan()->get_setting('course_enddate');
if ($datesetting) {
$datesetting->set_status(base_setting::LOCKED_BY_PERMISSION);
}
}
return true;
......
......@@ -342,7 +342,8 @@ class course_edit_form extends moodleform {
$mform = $this->_form;
// add available groupings
if ($courseid = $mform->getElementValue('id') and $mform->elementExists('defaultgroupingid')) {
$courseid = $mform->getElementValue('id');
if ($courseid and $mform->elementExists('defaultgroupingid')) {
$options = array();
if ($groupings = $DB->get_records('groupings', array('courseid'=>$courseid))) {
foreach ($groupings as $grouping) {
......@@ -357,7 +358,14 @@ class course_edit_form extends moodleform {
// add course format options
$formatvalue = $mform->getElementValue('format');
if (is_array($formatvalue) && !empty($formatvalue)) {
$courseformat = course_get_format((object)array('format' => $formatvalue[0]));
$params = array('format' => $formatvalue[0]);
// Load the course as well if it is available, course formats may need it to work out
// they preferred course end date.
if ($courseid) {
$params['id'] = $courseid;
}
$courseformat = course_get_format((object)$params);
$elements = $courseformat->create_edit_form_elements($mform);
for ($i = 0; $i < count($elements); $i++) {
......@@ -395,9 +403,8 @@ class course_edit_form extends moodleform {
}
}
// Add field validation check for end date must be after start date.
if (($data['enddate'] > 0) && ($data['enddate'] < $data['startdate'])) {
$errors['enddate'] = get_string('enddatebeforstartdate', 'error');
if ($errorcode = course_validate_dates($data)) {
$errors['enddate'] = get_string($errorcode, 'error');
}
$errors = array_merge($errors, enrol_course_edit_validation($data, $this->context));
......
......@@ -679,6 +679,13 @@ abstract class format_base {
$mform->setDefault($optionname, $option['default']);
}
}
if (!$forsection && empty($this->courseid)) {
// At this stage (this is called from definition_after_data) course data is already set as default.
// We can not overwrite what is in the database.
$mform->setDefault('enddate', $this->get_default_course_enddate($mform));
}
return $elements;
}
......@@ -1100,6 +1107,46 @@ abstract class format_base {
return $this->inplace_editable_render_section_name($section, ($itemtype === 'sectionname'), true);
}
}
/**
* Returns the default end date value based on the start date.
*
* This is the default implementation for course formats, it is based on
* moodlecourse/courseduration setting. Course formats like format_weeks for
* example can overwrite this method and return a value based on their internal options.
*
* @param moodleform $mform
* @param array $fieldnames The form - field names mapping.
* @return int
*/
public function get_default_course_enddate($mform, $fieldnames = array()) {
if (empty($fieldnames)) {
$fieldnames = array('startdate' => 'startdate');
}
$startdate = $this->get_form_start_date($mform, $fieldnames);
$courseduration = intval(get_config('moodlecourse', 'courseduration'));
if (!$courseduration) {
// Default, it should be already set during upgrade though.
$courseduration = YEARSECS;
}
return $startdate + $courseduration;
}
/**
* Get the start date value from the course settings page form.
*
* @param moodleform $mform
* @param array $fieldnames The form - field names mapping.
* @return int
*/
protected function get_form_start_date($mform, $fieldnames) {
$startdate = $mform->getElementValue($fieldnames['startdate']);
return $mform->getElement($fieldnames['startdate'])->exportValue($startdate);
}
}
/**
......
......@@ -4,6 +4,9 @@ Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
=== 3.2 ===
* Callback delete_course is deprecated and should be replaced with observer for event \core\event\course_content_deleted
* Course formats can overwrite get_default_course_enddate function to set the default course end date for new courses.
format_base::get_default_course_enddate uses the new "Course duration" site setting to calculate the default course end date
from the default course start date.
=== 3.1 ===
* Course format may use the inplace_editable template to allow quick editing of section names, see
......
......@@ -380,10 +380,16 @@ class format_weeks extends format_base {
* Return the start and end date of the passed section
*
* @param int|stdClass|section_info $section section to get the dates for
* @param int $startdate Force course start date, useful when the course is not yet created
* @return stdClass property start for startdate, property end for enddate
*/
public function get_section_dates($section) {
$course = $this->get_course();
public function get_section_dates($section, $startdate = false) {
if ($startdate === false) {
$course = $this->get_course();
$startdate = $course->startdate;
}
if (is_object($section)) {
$sectionnum = $section->section;
} else {
......@@ -392,7 +398,7 @@ class format_weeks extends format_base {
$oneweekseconds = 604800;
// Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
// savings and the date changes.
$startdate = $course->startdate + 7200;
$startdate = $startdate + 7200;
$dates = new stdClass();
$dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1));
......@@ -454,6 +460,37 @@ class format_weeks extends format_base {
}
return parent::inplace_editable_render_section_name($section, $linkifneeded, $editable, $edithint, $editlabel);
}
/**
* Returns the default end date for weeks course format.
*
* @param moodleform $mform
* @param array $fieldnames The form - field names mapping.
* @return int
*/
public function get_default_course_enddate($mform, $fieldnames = array()) {
if (empty($fieldnames['startdate'])) {
$fieldnames['startdate'] = 'startdate';
}
if (empty($fieldnames['numsections'])) {
$fieldnames['numsections'] = 'numsections';
}
$startdate = $this->get_form_start_date($mform, $fieldnames);
if ($mform->elementExists($fieldnames['numsections'])) {
$numsections = $mform->getElementValue($fieldnames['numsections']);
$numsections = $mform->getElement($fieldnames['numsections'])->exportValue($numsections);
} else {
// Fallback to the default value for new courses.
$numsections = get_config('moodlecourse', $fieldnames['numsections']);
}
// Final week's last day.
$dates = $this->get_section_dates(intval($numsections), $startdate);
return $dates->end;
}
}
/**
......
......@@ -2687,6 +2687,10 @@ function create_course($data, $editoroptions = NULL) {
}
}
if ($errorcode = course_validate_dates((array)$data)) {
throw new moodle_exception($errorcode);
}
// Check if timecreated is given.
$data->timecreated = !empty($data->timecreated) ? $data->timecreated : time();
$data->timemodified = $data->timecreated;
......@@ -2805,6 +2809,10 @@ function update_course($data, $editoroptions = NULL) {
}
}
if ($errorcode = course_validate_dates((array)$data)) {
throw new moodle_exception($errorcode);
}
if (!isset($data->category) or empty($data->category)) {
// prevent nulls and 0 in category field
unset($data->category);
......@@ -4190,3 +4198,29 @@ function course_get_user_administration_options($course, $context) {
return $options;
}
/**
* Validates course start and end dates.
*
* Checks that the end course date is not greater than the start course date.
*
* $coursedata['startdate'] or $coursedata['enddate'] may not be set, it depends on the form and user input.
*
* @param array $coursedata May contain startdate and enddate timestamps, depends on the user input.
* @return mixed False if everything alright, error codes otherwise.
*/
function course_validate_dates($coursedata) {
// If both start and end dates are set end date should be later than the start date.
if (!empty($coursedata['startdate']) && !empty($coursedata['enddate']) &&
($coursedata['enddate'] < $coursedata['startdate'])) {
return 'enddatebeforestartdate';
}
// If start date is not set end date can not be set.
if (empty($coursedata['startdate']) && !empty($coursedata['enddate'])) {
return 'nostartdatenoenddate';
}
return false;
}
......@@ -71,6 +71,7 @@ if ($mform->is_cancelled()) {
echo $OUTPUT->heading($strresetcourse);
$data->reset_start_date_old = $course->startdate;
$data->reset_end_date_old = $course->enddate;
$status = reset_course_userdata($data);
$data = array();
......
......@@ -25,6 +25,7 @@
defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
require_once($CFG->dirroot . '/course/lib.php');
/**
* Defines the course reset settings form.
......@@ -42,6 +43,8 @@ class course_reset_form extends moodleform {
$mform->addElement('date_selector', 'reset_start_date', get_string('startdate'), array('optional'=>true));
$mform->addHelpButton('reset_start_date', 'startdate');
$mform->addElement('date_selector', 'reset_end_date', get_string('enddate'), array('optional' => true));
$mform->addHelpButton('reset_end_date', 'enddate');
$mform->addElement('checkbox', 'reset_events', get_string('deleteevents', 'calendar'));
$mform->addElement('checkbox', 'reset_notes', get_string('deletenotes', 'notes'));
$mform->addElement('checkbox', 'reset_comments', get_string('deleteallcomments', 'moodle'));
......@@ -160,4 +163,44 @@ class course_reset_form extends moodleform {
$mform->setDefault($element, $default);
}
}
/**
* Validation.
*
* @param array $data
* @param array $files
* @return array the errors that were found
*/
public function validation($data, $files) {
global $DB;
$course = get_course($data['id']);
$errors = parent::validation($data, $files);
// We check the values that would be used as start and end.
if ($data['reset_start_date'] != 0) {
$coursedata['startdate'] = $data['reset_start_date'];
} else {
$coursedata['startdate'] = $course->startdate;
}
if ($data['reset_end_date'] != 0) {
// End date set by the user has preference.
$coursedata['enddate'] = $data['reset_end_date'];
} else if ($data['reset_start_date'] > 0) {
// Otherwise reset_course_userdata will add the start date time shift.
$timeshift = $data['reset_start_date'] - usergetmidnight($course->startdate);
$coursedata['enddate'] = $course->enddate + $timeshift;
} else {
$coursedata['enddate'] = $course->enddate;
}
if ($errorcode = course_validate_dates($coursedata)) {
$errors['reset_end_date'] = get_string($errorcode, 'error');
}
return $errors;
}
}
......@@ -3200,7 +3200,7 @@ class core_course_courselib_testcase extends advanced_testcase {
/**
* test_course_reset
* test_course_dates_reset
*
* @dataProvider course_dates_reset_provider
* @param int $startdate
......
<?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/>.
/**
* Testable course edit form.
*
* @package core_course
* @category test
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/course/edit_form.php');
/**
* Testable course edit form.
*
* @package core_course
* @category test
* @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class testable_course_edit_form extends course_edit_form {
/**
......
......@@ -13,3 +13,5 @@ information provided here is intended especially for developers.
- core_course_renderer::course_modchooser_module_types
- core_course_renderer::course_modchooser_module
- core_course_renderer::course_modchooser_title
* You can now specify a course end date when calling core_course_external::create_courses and core_course_external::update_courses
external functions. core_course_external::get_courses external function is now returning courses end date values.
......@@ -266,7 +266,6 @@ $string['setting_overwriteconf'] = 'Overwrite course configuration';
$string['setting_course_fullname'] = 'Course name';
$string['setting_course_shortname'] = 'Course short name';
$string['setting_course_startdate'] = 'Course start date';
$string['setting_course_enddate'] = 'Course end date';
$string['setting_keep_roles_and_enrolments'] = 'Keep current roles and enrolments';
$string['setting_keep_groups_and_groupings'] = 'Keep current groups and groupings';
$string['showtypes'] = 'Show type options';
......
......@@ -223,7 +223,7 @@ $string['duplicaterolename'] = 'There is already a role with this name!';
$string['duplicateroleshortname'] = 'There is already a role with this short name!';
$string['duplicateusername'] = 'Duplicate username - skipping record';
$string['emailfail'] = 'Emailing failed';
$string['enddatebeforstartdate'] = 'The end date must be after start date.';
$string['enddatebeforestartdate'] = 'The course end date must be after the start date.';
$string['error'] = 'Error occurred';
$string['error_question_answers_missing_in_db'] = 'Failed to find an answer matching "{$a->answer}" in the question_answers database table. This occurred while restoring the question with id {$a->filequestionid} in the backup file, which has been matched to the existing question with id {$a->dbquestionid} in the database.';
$string['errorprocessingarchive'] = 'Error processing archive file';
......@@ -438,6 +438,7 @@ $string['nopermissiontoviewletergrade'] = 'Missing permission to view letter gra
$string['nopermissiontoviewpage'] = 'You are not allowed to look at this page';
$string['nosite'] = 'Could not find a top-level course!';
$string['nositeid'] = 'No site ID';
$string['nostartdatenoenddate'] = 'A course end date can only be set if a start date is also set.';
$string['nostatstodisplay'] = 'Sorry, there is no available data to display';
$string['notallowedtoupdateprefremotely'] = 'You are not allowed to update this user preference remotely';
$string['notavailable'] = 'That is not currently available';
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment