diff --git a/admin/oacleanup.php b/admin/oacleanup.php index fe2e444472b..a27aff9341f 100644 --- a/admin/oacleanup.php +++ b/admin/oacleanup.php @@ -59,11 +59,19 @@ function online_assignment_cleanup($output=false) { if ($output) echo $OUTPUT->heading($fullname); /// retrieve a list of sections beyond what is currently being shown + $courseformatoptions = course_get_format($course)->get_format_options(); + if (!isset($courseformatoptions['numsections'])) { + // Course format does not use numsections + if ($output) { + echo 'No extra sections
'; + } + continue; + } $sql = "SELECT * FROM {course_sections} WHERE course=? AND section>? ORDER BY section ASC"; - $params = array($course->id, $course->numsections); + $params = array($course->id, $courseformatoptions['numsections']); if (!($xsections = $DB->get_records_sql($sql, $params))) { if ($output) echo 'No extra sections
'; continue; @@ -94,7 +102,7 @@ function online_assignment_cleanup($output=false) { /// the journal update erroneously stored it in course_sections->section $newsection = $xsection->section; /// double check the new section - if ($newsection > $course->numsections) { + if ($newsection > $courseformatoptions['numsections']) { /// get the record for section 0 for this course if (!($zerosection = $DB->get_record('course_sections', array('course'=>$course->id, 'section'=>'0')))) { continue; diff --git a/backup/cc/cc_lib/cc_convert_moodle2.php b/backup/cc/cc_lib/cc_convert_moodle2.php index 5f6d9e4be1e..56002a07a5a 100644 --- a/backup/cc/cc_lib/cc_convert_moodle2.php +++ b/backup/cc/cc_lib/cc_convert_moodle2.php @@ -83,14 +83,15 @@ abstract class cc_convert_moodle2 { $coursef = new XMLGenericDocument(); $course_file = $dir . DIRECTORY_SEPARATOR .'course' . DIRECTORY_SEPARATOR . 'course.xml'; $coursef->load($course_file); - $numsections = (int)$coursef->nodeValue('/course/numsections'); + //$numsections = (int)$coursef->nodeValue('/course/numsections'); + // TODO MDL-35781, this is commented because numsections is now optional attribute $section_list = $docp->nodeList('/moodle_backup/information/contents/sections/section'); if (!empty($section_list)) { $count = 0; foreach ($section_list as $node) { - if ($count > $numsections) { - break; - } + //if ($count > $numsections) { + // break; + //} $sectionid = $docp->nodeValue('sectionid', $node); $sectiontitle = $docp->nodeValue('title' , $node); $sectionpath = $docp->nodeValue('directory', $node); diff --git a/backup/moodle2/backup_stepslib.php b/backup/moodle2/backup_stepslib.php index 2667e0344fa..158b4a2ccab 100644 --- a/backup/moodle2/backup_stepslib.php +++ b/backup/moodle2/backup_stepslib.php @@ -394,6 +394,11 @@ class backup_section_structure_step extends backup_structure_step { $section->add_child($avail); $section->add_child($availfield); + // Add nested elements for course_format_options table + $formatoptions = new backup_nested_element('course_format_options', array('id'), array( + 'format', 'name', 'value')); + $section->add_child($formatoptions); + // Define sources $section->set_source_table('course_sections', array('id' => backup::VAR_SECTIONID)); $avail->set_source_table('course_sections_availability', array('coursesectionid' => backup::VAR_SECTIONID)); @@ -402,6 +407,12 @@ class backup_section_structure_step extends backup_structure_step { FROM {course_sections_avail_fields} csaf LEFT JOIN {user_info_field} uif ON uif.id = csaf.customfieldid WHERE csaf.coursesectionid = ?', array(backup::VAR_SECTIONID)); + $formatoptions->set_source_sql('SELECT cfo.id, cfo.format, cfo.name, cfo.value + FROM {course} c + JOIN {course_format_options} cfo + ON cfo.courseid = c.id AND cfo.format = c.format + WHERE c.id = ? AND cfo.sectionid = ?', + array(backup::VAR_COURSEID, backup::VAR_SECTIONID)); // Aliases $section->set_source_alias('section', 'number'); @@ -428,10 +439,10 @@ class backup_course_structure_step extends backup_structure_step { $course = new backup_nested_element('course', array('id', 'contextid'), array( 'shortname', 'fullname', 'idnumber', - 'summary', 'summaryformat', 'format', 'coursedisplay', 'showgrades', - 'newsitems', 'startdate', 'numsections', + 'summary', 'summaryformat', 'format', 'showgrades', + 'newsitems', 'startdate', 'marker', 'maxbytes', 'legacyfiles', 'showreports', - 'visible', 'hiddensections', 'groupmode', 'groupmodeforce', + 'visible', 'groupmode', 'groupmodeforce', 'defaultgroupingid', 'lang', 'theme', 'timecreated', 'timemodified', 'requested', @@ -476,6 +487,12 @@ class backup_course_structure_step extends backup_structure_step { $courserec = $DB->get_record('course', array('id' => $this->task->get_courseid())); $courserec->contextid = $this->task->get_contextid(); + $formatoptions = course_get_format($courserec)->get_format_options(); + $course->add_final_elements(array_keys($formatoptions)); + foreach ($formatoptions as $key => $value) { + $courserec->$key = $value; + } + $course->set_source_array(array($courserec)); $categoryrec = $DB->get_record('course_categories', array('id' => $courserec->category)); diff --git a/backup/moodle2/restore_stepslib.php b/backup/moodle2/restore_stepslib.php index afa6f5bf9d7..0a42f098603 100644 --- a/backup/moodle2/restore_stepslib.php +++ b/backup/moodle2/restore_stepslib.php @@ -1100,6 +1100,7 @@ class restore_section_structure_step extends restore_structure_step { $paths[] = new restore_path_element('availability', '/section/availability'); $paths[] = new restore_path_element('availability_field', '/section/availability_field'); } + $paths[] = new restore_path_element('course_format_options', '/section/course_format_options'); // Apply for 'format' plugins optional paths at section level $this->add_plugin_structure('format', $section); @@ -1233,6 +1234,17 @@ class restore_section_structure_step extends restore_structure_step { } } + public function process_course_format_options($data) { + global $DB; + $data = (object)$data; + $oldid = $data->id; + unset($data->id); + $data->sectionid = $this->task->get_sectionid(); + $data->courseid = $this->get_courseid(); + $newid = $DB->insert_record('course_format_options', $data); + $this->set_mapping('course_format_options', $oldid, $newid); + } + protected function after_execute() { // Add section related files, with 'course_section' itemid to match $this->add_related_files('course', 'section', 'course_section'); @@ -1382,6 +1394,8 @@ class restore_course_structure_step extends restore_structure_step { // Course record ready, update it $DB->update_record('course', $data); + course_get_format($data)->update_course_format_options($data); + // Role name aliases restore_dbops::set_course_role_names($this->get_restoreid(), $this->get_courseid()); } diff --git a/blocks/section_links/block_section_links.php b/blocks/section_links/block_section_links.php index a8a0a93fe46..2942b477c4a 100644 --- a/blocks/section_links/block_section_links.php +++ b/blocks/section_links/block_section_links.php @@ -72,6 +72,7 @@ class block_section_links extends block_base { } $course = $this->page->course; + $courseformatoptions = course_get_format($course)->get_format_options(); $context = context_course::instance($course->id); if ($course->format == 'weeks' or $course->format == 'weekscss') { @@ -86,18 +87,18 @@ class block_section_links extends block_base { } $inc = 1; - if(!empty($config->numsections1) and ($course->numsections > $config->numsections1)) { + if(!empty($config->numsections1) and ($courseformatoptions['numsections'] > $config->numsections1)) { $inc = $config->incby1; } else { - if ($course->numsections > 22) { + if ($courseformatoptions['numsections'] > 22) { $inc = 2; } } - if(!empty($config->numsections2) and ($course->numsections > $config->numsections2)) { + if(!empty($config->numsections2) and ($courseformatoptions['numsections'] > $config->numsections2)) { $inc = $config->incby2; } else { - if ($course->numsections > 40) { + if ($courseformatoptions['numsections'] > 40) { $inc = 5; } } @@ -105,12 +106,12 @@ class block_section_links extends block_base { $sql = "SELECT section, visible FROM {course_sections} WHERE course = ? AND - section < ".($course->numsections+1)." + section < ".($courseformatoptions['numsections']+1)." ORDER BY section"; if ($sections = $DB->get_records_sql($sql, array($course->id))) { $text = '
    '; - for ($i = $inc; $i <= $course->numsections; $i += $inc) { + for ($i = $inc; $i <= $courseformatoptions['numsections']; $i += $inc) { if (!isset($sections[$i])) { continue; } diff --git a/course/changenumsections.php b/course/changenumsections.php index 3354f1c38b9..deed3670e85 100644 --- a/course/changenumsections.php +++ b/course/changenumsections.php @@ -31,6 +31,7 @@ require_once($CFG->dirroot.'/course/lib.php'); $courseid = required_param('courseid', PARAM_INT); $increase = optional_param('increase', true, PARAM_BOOL); $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST); +$courseformatoptions = course_get_format($course)->get_format_options(); $PAGE->set_url('/course/changenumsections.php', array('courseid' => $courseid)); @@ -39,18 +40,21 @@ require_login($course); require_capability('moodle/course:update', context_course::instance($course->id)); require_sesskey(); -if ($increase) { - // Add an additional section. - $course->numsections++; -} else { - // Remove a section. - $course->numsections--; -} +if (isset($courseformatoptions['numsections'])) { + if ($increase) { + // Add an additional section. + $courseformatoptions['numsections']++; + } else { + // Remove a section. + $courseformatoptions['numsections']--; + } -// Don't go less than 0, intentionally redirect silently (for the case of -// double clicks). -if ($course->numsections >= 0) { - $DB->update_record('course', $course); + // Don't go less than 0, intentionally redirect silently (for the case of + // double clicks). + if ($courseformatoptions['numsections'] >= 0) { + course_get_format($course)->update_course_format_options( + array('numsections' => $courseformatoptions['numsections'])); + } } $url = course_get_url($course); diff --git a/course/edit.php b/course/edit.php index 6a5aefbeadc..8fbb3a9f9bf 100644 --- a/course/edit.php +++ b/course/edit.php @@ -41,7 +41,7 @@ if ($id) { // editing course print_error('cannoteditsiteform'); } - $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST); + $course = course_get_format($id)->get_course(); require_login($course); $category = $DB->get_record('course_categories', array('id'=>$course->category), '*', MUST_EXIST); $coursecontext = context_course::instance($course->id); diff --git a/course/edit_form.php b/course/edit_form.php index cb1feeb9ae3..70b709a9978 100644 --- a/course/edit_form.php +++ b/course/edit_form.php @@ -10,9 +10,11 @@ class course_edit_form extends moodleform { protected $context; function definition() { - global $USER, $CFG, $DB; + global $USER, $CFG, $DB, $PAGE; $mform = $this->_form; + $PAGE->requires->yui_module('moodle-course-formatchooser', 'M.course.init_formatchooser', + array(array('formid' => $mform->getAttribute('id')))); $course = $this->_customdata['course']; // this contains the data of this form $category = $this->_customdata['category']; @@ -120,29 +122,14 @@ class course_edit_form extends moodleform { $mform->addHelpButton('format', 'format'); $mform->setDefault('format', $courseconfig->format); - $mform->addElement('select', 'coursedisplay', get_string('coursedisplay'), - array(COURSE_DISPLAY_SINGLEPAGE => get_string('coursedisplay_single'), - COURSE_DISPLAY_MULTIPAGE => get_string('coursedisplay_multi'))); - $mform->addHelpButton('coursedisplay', 'coursedisplay'); - $mform->setDefault('coursedisplay', $courseconfig->coursedisplay); - - for ($i = 0; $i <= $courseconfig->maxsections; $i++) { - $sectionmenu[$i] = "$i"; - } - $mform->addElement('select', 'numsections', get_string('numberweeks'), $sectionmenu); - $mform->setDefault('numsections', $courseconfig->numsections); + // button to update format-specific options on format change (will be hidden by JavaScript) + $mform->registerNoSubmitButton('updatecourseformat'); + $mform->addElement('submit', 'updatecourseformat', get_string('courseformatudpate')); $mform->addElement('date_selector', 'startdate', get_string('startdate')); $mform->addHelpButton('startdate', 'startdate'); $mform->setDefault('startdate', time() + 3600 * 24); - $choices = array(); - $choices['0'] = get_string('hiddensectionscollapsed'); - $choices['1'] = get_string('hiddensectionsinvisible'); - $mform->addElement('select', 'hiddensections', get_string('hiddensections'), $choices); - $mform->addHelpButton('hiddensections', 'hiddensections'); - $mform->setDefault('hiddensections', $courseconfig->hiddensections); - $options = range(0, 10); $mform->addElement('select', 'newsitems', get_string('newsitemsnumber'), $options); $mform->addHelpButton('newsitems', 'newsitemsnumber'); @@ -193,6 +180,9 @@ class course_edit_form extends moodleform { $mform->addElement('select', 'theme', get_string('forcetheme'), $themes); } +//-------------------------------------------------------------------------------- + $mform->addElement('hidden', 'addcourseformatoptionshere'); + //-------------------------------------------------------------------------------- enrol_course_edit_form($mform, $course, $context); @@ -310,8 +300,22 @@ class course_edit_form extends moodleform { $gr_el =& $mform->getElement('defaultgroupingid'); $gr_el->load($options); } - } + // add course format options + $formatvalue = $mform->getElementValue('format'); + if (is_array($formatvalue) && !empty($formatvalue)) { + $courseformat = course_get_format((object)array('format' => $formatvalue[0])); + $newel = $mform->createElement('header', '', get_string('courseformatoptions', 'moodle', + $courseformat->get_format_name())); + $mform->insertElementBefore($newel, 'addcourseformatoptionshere'); + + $elements = $courseformat->create_edit_form_elements($mform); + for ($i = 0; $i < count($elements); $i++) { + $mform->insertElementBefore($mform->removeElement($elements[$i]->getName(), false), + 'addcourseformatoptionshere'); + } + } + } /// perform some extra moodle validation function validation($data, $files) { @@ -333,6 +337,12 @@ class course_edit_form extends moodleform { $errors = array_merge($errors, enrol_course_edit_validation($data, $this->context)); + $courseformat = course_get_format((object)array('format' => $data['format'])); + $formaterrors = $courseformat->edit_form_validation($data, $files, $errors); + if (!empty($formaterrors) && is_array($formaterrors)) { + $errors = array_merge($errors, $formaterrors); + } + return $errors; } } diff --git a/course/editsection.php b/course/editsection.php index 549bd981a1e..c75e3dc0ba4 100644 --- a/course/editsection.php +++ b/course/editsection.php @@ -16,7 +16,7 @@ // along with Moodle. If not, see . /** - * Edit the introduction of a section + * Edit the section basic information and availability * * @copyright 1999 Martin Dougiamas http://dougiamas.com * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -25,78 +25,58 @@ require_once("../config.php"); require_once("lib.php"); -require_once($CFG->libdir.'/filelib.php'); -require_once($CFG->libdir . '/gradelib.php'); -require_once($CFG->libdir . '/completionlib.php'); require_once($CFG->libdir . '/conditionlib.php'); -require_once('editsection_form.php'); - -$id = required_param('id',PARAM_INT); // Week/topic ID +$id = required_param('id', PARAM_INT); // course_sections.id $sectionreturn = optional_param('sr', 0, PARAM_INT); $PAGE->set_url('/course/editsection.php', array('id'=>$id, 'sr'=> $sectionreturn)); $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST); $course = $DB->get_record('course', array('id' => $section->course), '*', MUST_EXIST); +$sectionnum = $section->section; require_login($course); $context = context_course::instance($course->id); require_capability('moodle/course:update', $context); +// get section_info object with all availability options +$sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum); + $editoroptions = array('context'=>$context ,'maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true); -$section = file_prepare_standard_editor($section, 'summary', $editoroptions, $context, 'course', 'section', $section->id); -$section->usedefaultname = (is_null($section->name)); +$mform = course_get_format($course->id)->editsection_form($PAGE->url, + array('cs' => $sectioninfo, 'editoroptions' => $editoroptions)); +// set current value, make an editable copy of section_info object +// this will retrieve all format-specific options as well +$mform->set_data(convert_to_array($sectioninfo)); -if (!empty($CFG->enableavailability)) { - // Get section availability conditions from sectioncache. - $modinfo = get_fast_modinfo($course); - $sectioninfo = $modinfo->get_section_info($section->section); - $section->conditionsgrade = $sectioninfo->conditionsgrade; - $section->conditionscompletion = $sectioninfo->conditionscompletion; - $section->conditionsfield = $sectioninfo->conditionsfield; -} - -$mform = new editsection_form($PAGE->url, array('course' => $course, 'editoroptions' => $editoroptions, - 'cs' => $section, 'showavailability' => $section->showavailability)); -$mform->set_data($section); // set current value - -$returnurl = course_get_url($course, $sectionreturn); - -/// If data submitted, then process and store. if ($mform->is_cancelled()){ - redirect($returnurl); - + // form cancelled, return to course + redirect(course_get_url($course, $section, array('sr' => $sectionreturn))); } else if ($data = $mform->get_data()) { - if (empty($data->usedefaultname)) { - $section->name = $data->name; - } else { - $section->name = null; + // data submitted and validated, update and return to course + $DB->update_record('course_sections', $data); + rebuild_course_cache($course->id, true); + if (isset($data->section)) { + // usually edit form does not change relative section number but just in case + $sectionnum = $data->section; } - $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'section', $section->id); - $section->summary = $data->summary; - $section->summaryformat = $data->summaryformat; - if (!empty($CFG->enableavailability)) { - $section->availablefrom = $data->availablefrom; - $section->availableuntil = $data->availableuntil; - if (isset($data->groupingid)) { - $section->groupingid = $data->groupingid; - } - $section->showavailability = $data->showavailability; - } - $DB->update_record('course_sections', $section); if (!empty($CFG->enableavailability)) { // Update grade and completion conditions - condition_info_section::update_section_from_form($section, $data); + $sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum); + condition_info_section::update_section_from_form($sectioninfo, $data); + rebuild_course_cache($course->id, true); } - rebuild_course_cache($course->id); + course_get_format($course->id)->update_section_format_options($data); - add_to_log($course->id, "course", "editsection", "editsection.php?id=$section->id", "$section->section"); + add_to_log($course->id, "course", "editsection", "editsection.php?id=$id", "$sectionnum"); $PAGE->navigation->clear_cache(); - redirect($returnurl); + redirect(course_get_url($course, $section, array('sr' => $sectionreturn))); } -$sectionname = get_section_name($course, $section); +// the edit form is displayed for the first time or there was a validation +// error on the previous step. Display the edit form: +$sectionname = get_section_name($course, $sectionnum); $stredit = get_string('edita', '', " $sectionname"); $strsummaryof = get_string('summaryof', '', " $sectionname"); diff --git a/course/editsection_form.php b/course/editsection_form.php index 73da8d3d2a7..4dffac59072 100644 --- a/course/editsection_form.php +++ b/course/editsection_form.php @@ -5,7 +5,15 @@ if (!defined('MOODLE_INTERNAL')) { } require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->libdir.'/filelib.php'); +require_once($CFG->libdir.'/completionlib.php'); +require_once($CFG->libdir.'/gradelib.php'); +/** + * Default form for editing course section + * + * Course format plugins may specify different editing form to use + */ class editsection_form extends moodleform { function definition() { @@ -28,6 +36,13 @@ class editsection_form extends moodleform { $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); + // additional fields that course format has defined + $courseformat = course_get_format($course); + $formatoptions = $courseformat->section_format_options(true); + if (!empty($formatoptions)) { + $elements = $courseformat->create_edit_form_elements($mform, true); + } + $mform->_registerCancelButton('cancel'); } @@ -195,8 +210,6 @@ class editsection_form extends moodleform { CONDITION_STUDENTVIEW_HIDE => get_string('showavailabilitysection_hide', 'condition')); $mform->addElement('select', 'showavailability', get_string('showavailabilitysection', 'condition'), $showhide); - - $mform->setDefault('showavailability', $this->_customdata['showavailability']); } $this->add_action_buttons(); @@ -232,4 +245,47 @@ class editsection_form extends moodleform { return $errors; } + + /** + * Load in existing data as form defaults + * + * @param stdClass|array $default_values object or array of default values + */ + function set_data($default_values) { + if (!is_object($default_values)) { + // we need object for file_prepare_standard_editor + $default_values = (object)$default_values; + } + $editoroptions = $this->_customdata['editoroptions']; + $default_values = file_prepare_standard_editor($default_values, 'summary', $editoroptions, + $editoroptions['context'], 'course', 'section', $default_values->id); + $default_values->usedefaultname = (is_null($default_values->name)); + parent::set_data($default_values); + } + + /** + * Return submitted data if properly submitted or returns NULL if validation fails or + * if there is no submitted data. + * + * @return object submitted data; NULL if not valid or not submitted or cancelled + */ + function get_data() { + $data = parent::get_data(); + if ($data !== null) { + $editoroptions = $this->_customdata['editoroptions']; + if (!empty($data->usedefaultname)) { + $data->name = null; + } + $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, + $editoroptions['context'], 'course', 'section', $data->id); + $course = $this->_customdata['course']; + foreach (course_get_format($course)->section_format_options() as $option => $unused) { + // fix issue with unset checkboxes not being returned at all + if (!isset($data->$option)) { + $data->$option = null; + } + } + } + return $data; + } } diff --git a/course/externallib.php b/course/externallib.php index 290146a4bd3..659228c53b1 100644 --- a/course/externallib.php +++ b/course/externallib.php @@ -297,6 +297,7 @@ class core_course_external extends external_api { // now security checks $context = context_course::instance($course->id, IGNORE_MISSING); + $courseformatoptions = course_get_format($course)->get_format_options(); try { self::validate_context($context); } catch (Exception $e) { @@ -316,7 +317,10 @@ class core_course_external extends external_api { external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0); $courseinfo['format'] = $course->format; $courseinfo['startdate'] = $course->startdate; - $courseinfo['numsections'] = $course->numsections; + if (array_key_exists('numsections', $courseformatoptions)) { + // For backward-compartibility + $courseinfo['numsections'] = $courseformatoptions['numsections']; + } //some field should be returned only if the user has update permission $courseadmin = has_capability('moodle/course:update', $context); @@ -328,7 +332,10 @@ class core_course_external extends external_api { $courseinfo['newsitems'] = $course->newsitems; $courseinfo['visible'] = $course->visible; $courseinfo['maxbytes'] = $course->maxbytes; - $courseinfo['hiddensections'] = $course->hiddensections; + if (array_key_exists('hiddensections', $courseformatoptions)) { + // For backward-compartibility + $courseinfo['hiddensections'] = $courseformatoptions['hiddensections']; + } $courseinfo['groupmode'] = $course->groupmode; $courseinfo['groupmodeforce'] = $course->groupmodeforce; $courseinfo['defaultgroupingid'] = $course->defaultgroupingid; @@ -339,6 +346,13 @@ class core_course_external extends external_api { $courseinfo['enablecompletion'] = $course->enablecompletion; $courseinfo['completionstartonenrol'] = $course->completionstartonenrol; $courseinfo['completionnotify'] = $course->completionnotify; + $courseinfo['courseformatoptions'] = array(); + foreach ($courseformatoptions as $key => $value) { + $courseinfo['courseformatoptions'][] = array( + 'name' => $key, + 'value' => $value + ); + } } if ($courseadmin or $course->visible @@ -377,7 +391,9 @@ class core_course_external extends external_api { 'number of recent items appearing on the course page', VALUE_OPTIONAL), 'startdate' => new external_value(PARAM_INT, 'timestamp when the course start'), - 'numsections' => new external_value(PARAM_INT, 'number of weeks/topics'), + 'numsections' => new external_value(PARAM_INT, + '(deprecated, use courseformatoptions) number of weeks/topics', + VALUE_OPTIONAL), 'maxbytes' => new external_value(PARAM_INT, 'largest size of file that can be uploaded into the course', VALUE_OPTIONAL), @@ -386,7 +402,7 @@ class core_course_external extends external_api { 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL), 'hiddensections' => new external_value(PARAM_INT, - 'How the hidden sections in the course are displayed to students', + '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students', VALUE_OPTIONAL), 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_OPTIONAL), @@ -412,6 +428,13 @@ class core_course_external extends external_api { 'forced course language', VALUE_OPTIONAL), 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL), + 'courseformatoptions' => new external_multiple_structure( + new external_single_structure( + array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'), + 'value' => new external_value(PARAM_RAW, 'course format option value') + )), + 'additional options for particular course format', VALUE_OPTIONAL + ), ), 'course' ) ); @@ -447,8 +470,9 @@ class core_course_external extends external_api { VALUE_DEFAULT, $courseconfig->newsitems), 'startdate' => new external_value(PARAM_INT, 'timestamp when the course start', VALUE_OPTIONAL), - 'numsections' => new external_value(PARAM_INT, 'number of weeks/topics', - VALUE_DEFAULT, $courseconfig->numsections), + 'numsections' => new external_value(PARAM_INT, + '(deprecated, use courseformatoptions) number of weeks/topics', + VALUE_OPTIONAL), 'maxbytes' => new external_value(PARAM_INT, 'largest size of file that can be uploaded into the course', VALUE_DEFAULT, $courseconfig->maxbytes), @@ -458,8 +482,8 @@ class core_course_external extends external_api { 'visible' => new external_value(PARAM_INT, '1: available to student, 0:not available', VALUE_OPTIONAL), 'hiddensections' => new external_value(PARAM_INT, - 'How the hidden sections in the course are displayed to students', - VALUE_DEFAULT, $courseconfig->hiddensections), + '(deprecated, use courseformatoptions) How the hidden sections in the course are displayed to students', + VALUE_OPTIONAL), 'groupmode' => new external_value(PARAM_INT, 'no group, separate, visible', VALUE_DEFAULT, $courseconfig->groupmode), 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no', @@ -480,6 +504,12 @@ class core_course_external extends external_api { 'forced course language', VALUE_OPTIONAL), 'forcetheme' => new external_value(PARAM_PLUGIN, 'name of the force theme', VALUE_OPTIONAL), + 'courseformatoptions' => new external_multiple_structure( + new external_single_structure( + array('name' => new external_value(PARAM_ALPHANUMEXT, 'course format option name'), + 'value' => new external_value(PARAM_RAW, 'course format option value') + )), + 'additional options for particular course format', VALUE_OPTIONAL), ) ), 'courses to create' ) @@ -562,6 +592,12 @@ class core_course_external extends external_api { // Summary format. $course['summaryformat'] = external_validate_format($course['summaryformat']); + if (!empty($course['courseformatoptions'])) { + foreach ($course['courseformatoptions'] as $option) { + $course[$option['name']] = $option['value']; + } + } + //Note: create_course() core function check shortname, idnumber, category $course['id'] = create_course((object) $course)->id; diff --git a/course/format/formatlegacy.php b/course/format/formatlegacy.php index 516f3c5f94a..e6141a3bd7e 100644 --- a/course/format/formatlegacy.php +++ b/course/format/formatlegacy.php @@ -98,8 +98,46 @@ class format_legacy extends format_base { } } - // else, default behavior: - return parent::get_view_url($section, $options); + // if function is not defined + if (!$this->uses_sections() || + !array_key_exists('coursedisplay', $this->course_format_options())) { + // default behaviour + return parent::get_view_url($section, $options); + } + + $course = $this->get_course(); + $url = new moodle_url('/course/view.php', array('id' => $course->id)); + + $sr = null; + if (array_key_exists('sr', $options)) { + $sr = $options['sr']; + } + if (is_object($section)) { + $sectionno = $section->section; + } else { + $sectionno = $section; + } + if ($sectionno !== null) { + if ($sr !== null) { + if ($sr) { + $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE; + $sectionno = $sr; + } else { + $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE; + } + } else { + $usercoursedisplay = $course->coursedisplay; + } + if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) { + $url->param('section', $sectionno); + } else { + if (!empty($options['navigation'])) { + return null; + } + $url->set_anchor('section-'.$sectionno); + } + } + return $url; } /** @@ -202,4 +240,101 @@ class format_legacy extends format_base { } return parent::get_default_blocks(); } + + /** + * Definitions of the additional options that this course format uses for course + * + * By default course formats have the options that existed in Moodle 2.3: + * - coursedisplay + * - numsections + * - hiddensections + * + * @param bool $foreditform + * @return array of options + */ + public function course_format_options($foreditform = false) { + static $courseformatoptions = false; + if ($courseformatoptions === false) { + $courseconfig = get_config('moodlecourse'); + $courseformatoptions = array( + 'numsections' => array( + 'default' => $courseconfig->numsections, + 'type' => PARAM_INT, + ), + 'hiddensections' => array( + 'default' => $courseconfig->hiddensections, + 'type' => PARAM_INT, + ), + 'coursedisplay' => array( + 'default' => $courseconfig->coursedisplay, + 'type' => PARAM_INT, + ), + ); + } + if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) { + $courseconfig = get_config('moodlecourse'); + $sectionmenu = array(); + for ($i = 0; $i <= $courseconfig->maxsections; $i++) { + $sectionmenu[$i] = "$i"; + } + $courseformatoptionsedit = array( + 'numsections' => array( + 'label' => new lang_string('numberweeks'), + 'element_type' => 'select', + 'element_attributes' => array($sectionmenu), + ), + 'hiddensections' => array( + 'label' => new lang_string('hiddensections'), + 'help' => 'hiddensections', + 'help_component' => 'moodle', + 'element_type' => 'select', + 'element_attributes' => array( + array( + 0 => new lang_string('hiddensectionscollapsed'), + 1 => new lang_string('hiddensectionsinvisible') + ) + ), + ), + 'coursedisplay' => array( + 'label' => new lang_string('coursedisplay'), + 'element_type' => 'select', + 'element_attributes' => array( + array( + COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'), + COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi') + ) + ), + 'help' => 'coursedisplay', + 'help_component' => 'moodle', + ) + ); + $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit); + } + return $courseformatoptions; + } + + /** + * Updates format options for a course + * + * Legacy course formats may assume that course format options + * ('coursedisplay', 'numsections' and 'hiddensections') are shared between formats. + * Therefore we make sure to copy them from the previous format + * + * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data + * @param stdClass $oldcourse if this function is called from {@link update_course()} + * this object contains information about the course before update + * @return bool whether there were any changes to the options values + */ + public function update_course_format_options($data, $oldcourse = null) { + if ($oldcourse !== null) { + $data = (array)$data; + $oldcourse = (array)$oldcourse; + foreach ($this->course_format_options() as $key => $unused) { + if (array_key_exists($key, $oldcourse) && !array_key_exists($key, $data)) { + $data[$key] = $oldcourse[$key]; + } + } + } + return $this->update_format_options($data); + } } \ No newline at end of file diff --git a/course/format/lib.php b/course/format/lib.php index 9496601e9b4..34613fdb55a 100644 --- a/course/format/lib.php +++ b/course/format/lib.php @@ -68,6 +68,8 @@ abstract class format_base { protected $format; /** @var stdClass data for course object, please use {@link format_base::get_course()} */ protected $course = false; + /** @var array caches format options, please use {@link format_base::get_format_options()} */ + protected $formatoptions = array(); /** @var array cached instances */ private static $instances = array(); @@ -180,6 +182,7 @@ abstract class format_base { foreach (self::$instances[$courseid] as $format => $object) { // in case somebody keeps the reference to course format object self::$instances[$courseid][$format]->course = false; + self::$instances[$courseid][$format]->formatoptions = array(); } unset(self::$instances[$courseid]); } @@ -219,6 +222,16 @@ abstract class format_base { } if ($this->course === false) { $this->course = $DB->get_record('course', array('id' => $this->courseid)); + $options = $this->get_format_options(); + foreach ($options as $optionname => $optionvalue) { + if (!isset($this->course->$optionname)) { + $this->course->$optionname = $optionvalue; + } else { + debugging('The option name '.$optionname.' in course format '.$this->format. + ' is invalid because the field with the same name exists in {course} table', + DEBUG_DEVELOPER); + } + } } return $this->course; } @@ -342,35 +355,20 @@ abstract class format_base { public function get_view_url($section, $options = array()) { $course = $this->get_course(); $url = new moodle_url('/course/view.php', array('id' => $course->id)); - - $sr = null; + if (array_key_exists('sr', $options)) { - $sr = $options['sr']; - } - if (is_object($section)) { + $sectionno = $options['sr']; + } else if (is_object($section)) { $sectionno = $section->section; } else { $sectionno = $section; } - if ($sectionno !== null) { - if ($sr !== null) { - if ($sr) { - $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE; - $sectionno = $sr; - } else { - $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE; - } - } else { - $usercoursedisplay = $course->coursedisplay; - } - if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) { - $url->param('section', $sectionno); - } else { - if (!empty($options['navigation'])) { - return null; - } - $url->set_anchor('section-'.$sectionno); - } + if (!empty($options['navigation']) && $sectionno !== null) { + // by default assume that sections are never displayed on separate pages + return null; + } + if ($this->uses_sections() && $sectionno !== null) { + $url->set_anchor('section-'.$sectionno); } return $url; } @@ -411,6 +409,346 @@ abstract class format_base { ); return $blocknames; } + + /** + * Returns the localised name of this course format plugin + * + * @return lang_string + */ + public final function get_format_name() { + return new lang_string('pluginname', 'format_'.$this->get_format()); + } + + /** + * Definitions of the additional options that this course format uses for course + * + * This function may be called often, it should be as fast as possible. + * Avoid using get_string() method, use "new lang_string()" instead + * It is not recommended to use dynamic or course-dependant expressions here + * This function may be also called when course does not exist yet. + * + * Option names must be different from fields in the {course} talbe or any form elements on + * course edit form, it may even make sence to use special prefix for them. + * + * Each option must have the option name as a key and the array of properties as a value: + * 'default' - default value for this option (assumed null if not specified) + * 'type' - type of the option value (PARAM_INT, PARAM_RAW, etc.) + * + * Additional properties used by default implementation of + * {@link format_base::create_edit_form_elements()} (calls this method with $foreditform = true) + * 'label' - localised human-readable label for the edit form + * 'element_type' - type of the form element, default 'text' + * 'element_attributes' - additional attributes for the form element, these are 4th and further + * arguments in the moodleform::addElement() method + * 'help' - string for help button. Note that if 'help' value is 'myoption' then the string with + * the name 'myoption_help' must exist in the language file + * 'help_component' - language component to look for help string, by default this the component + * for this course format + * + * This is an interface for creating simple form elements. If format plugin wants to use other + * methods such as disableIf, it can be done by overriding create_edit_form_elements(). + * + * Course format options can be accessed as: + * $this->get_course()->OPTIONNAME (inside the format class) + * course_get_format($course)->get_course()->OPTIONNAME (outside of format class) + * + * All course options are returned by calling: + * $this->get_format_options(); + * + * @param bool $foreditform + * @return array of options + */ + public function course_format_options($foreditform = false) { + return array(); + } + + /** + * Definitions of the additional options that this course format uses for section + * + * See {@link format_base::course_format_options()} for return array definition. + * + * Additionally section format options may have property 'cache' set to true + * if this option needs to be cached in {@link get_fast_modinfo()}. The 'cache' property + * is recommended to be set only for fields used in {@link format_base::get_section_name()}, + * {@link format_base::extend_course_navigation()} and {@link format_base::get_view_url()} + * + * For better performance cached options are recommended to have 'cachedefault' property + * Unlike 'default', 'cachedefault' should be static and not access get_config(). + * + * Regardless of value of 'cache' all options are accessed in the code as + * $sectioninfo->OPTIONNAME + * where $sectioninfo is instance of section_info, returned by + * get_fast_modinfo($course)->get_section_info($sectionnum) + * or get_fast_modinfo($course)->get_section_info_all() + * + * All format options for particular section are returned by calling: + * $this->get_format_options($section); + * + * @param bool $foreditform + * @return array + */ + public function section_format_options($foreditform = false) { + return array(); + } + + /** + * Returns the format options stored for this course or course section + * + * When overriding please note that this function is called from rebuild_course_cache() + * and section_info object, therefore using of get_fast_modinfo() and/or any function that + * accesses it may lead to recursion. + * + * @param null|int|stdClass|section_info $section if null the course format options will be returned + * otherwise options for specified section will be returned. This can be either + * section object or relative section number (field course_sections.section) + * @return array + */ + public function get_format_options($section = null) { + global $DB; + if ($section === null) { + $options = $this->course_format_options(); + } else { + $options = $this->section_format_options(); + } + if (empty($options)) { + // there are no option for course/sections anyway, no need to go further + return array(); + } + if ($section === null) { + // course format options will be returned + $sectionid = 0; + } else if ($this->courseid && isset($section->id)) { + // course section format options will be returned + $sectionid = $section->id; + } else if ($this->courseid && is_int($section) && + ($sectionobj = $DB->get_record('course_sections', + array('section' => $section, 'courseid' => $this->courseid), 'id'))) { + // course section format options will be returned + $sectionid = $sectionobj->id; + } else { + // non-existing (yet) section was passed as an argument + // default format options for course section will be returned + $sectionid = -1; + } + if (!array_key_exists($sectionid, $this->formatoptions)) { + $this->formatoptions[$sectionid] = array(); + // first fill with default values + foreach ($options as $optionname => $optionparams) { + $this->formatoptions[$sectionid][$optionname] = null; + if (array_key_exists('default', $optionparams)) { + $this->formatoptions[$sectionid][$optionname] = $optionparams['default']; + } + } + if ($this->courseid && $sectionid !== -1) { + // overwrite the default options values with those stored in course_format_options table + // nothing can be stored if we are interested in generic course ($this->courseid == 0) + // or generic section ($sectionid === 0) + $records = $DB->get_records('course_format_options', + array('courseid' => $this->courseid, + 'format' => $this->format, + 'sectionid' => $sectionid + ), '', 'id,name,value'); + foreach ($records as $record) { + if (array_key_exists($record->name, $this->formatoptions[$sectionid])) { + $value = $record->value; + if ($value !== null && isset($options[$record->name]['type'])) { + // this will convert string value to number if needed + $value = clean_param($value, $options[$record->name]['type']); + } + $this->formatoptions[$sectionid][$record->name] = $value; + } + } + } + } + return $this->formatoptions[$sectionid]; + } + + /** + * Adds format options elements to the course/section edit form + * + * This function is called from {@link course_edit_form::definition_after_data()} + * + * @param MoodleQuickForm $mform form the elements are added to + * @param bool $forsection 'true' if this is a section edit form, 'false' if this is course edit form + * @return array array of references to the added form elements + */ + public function create_edit_form_elements(&$mform, $forsection = false) { + $elements = array(); + if ($forsection) { + $options = $this->section_format_options(true); + } else { + $options = $this->course_format_options(true); + } + foreach ($options as $optionname => $option) { + if (!isset($option['element_type'])) { + $option['element_type'] = 'text'; + } + $args = array($option['element_type'], $optionname, $option['label']); + if (!empty($option['element_attributes'])) { + $args = array_merge($args, $option['element_attributes']); + } + $elements[] = &call_user_func_array(array($mform, 'addElement'), $args); + if (isset($option['help'])) { + $helpcomponent = 'format_'. $this->get_format(); + if (isset($option['help_component'])) { + $helpcomponent = $option['help_component']; + } + $mform->addHelpButton($optionname, $option['help'], $helpcomponent); + } + if (isset($option['type'])) { + $mform->setType($optionname, $option['type']); + } + if (is_null($mform->getElementValue($optionname)) && isset($option['default'])) { + $mform->setDefault($optionname, $option['default']); + } + } + return $elements; + } + + /** + * Override if you need to perform some extra validation of the format options + * + * @param array $data array of ("fieldname"=>value) of submitted data + * @param array $files array of uploaded files "element_name"=>tmp_file_path + * @param array $errors errors already discovered in edit form validation + * @return array of "element_name"=>"error_description" if there are errors, + * or an empty array if everything is OK. + * Do not repeat errors from $errors param here + */ + public function edit_form_validation($data, $files, $errors) { + return array(); + } + + /** + * Updates format options for a course or section + * + * If $data does not contain property with the option name, the option will not be updated + * + * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data + * @param null|int null if these are options for course or section id (course_sections.id) + * if these are options for section + * @return bool whether there were any changes to the options values + */ + protected function update_format_options($data, $sectionid = null) { + global $DB; + if (!$sectionid) { + $allformatoptions = $this->course_format_options(); + $sectionid = 0; + } else { + $allformatoptions = $this->section_format_options(); + } + if (empty($allformatoptions)) { + // nothing to update anyway + return false; + } + $defaultoptions = array(); + $cached = array(); + foreach ($allformatoptions as $key => $option) { + $defaultoptions[$key] = null; + if (array_key_exists('default', $option)) { + $defaultoptions[$key] = $option['default']; + } + $cached[$key] = ($sectionid === 0 || !empty($option['cache'])); + } + $records = $DB->get_records('course_format_options', + array('courseid' => $this->courseid, + 'format' => $this->format, + 'sectionid' => $sectionid + ), '', 'name,id,value'); + $changed = $needrebuild = false; + $data = (array)$data; + foreach ($defaultoptions as $key => $value) { + if (isset($records[$key])) { + if (array_key_exists($key, $data) && $records[$key]->value !== $data[$key]) { + $DB->set_field('course_format_options', 'value', + $data[$key], array('id' => $records[$key]->id)); + $changed = true; + $needrebuild = $needrebuild || $cached[$key]; + } + } else { + if (array_key_exists($key, $data) && $data[$key] !== $value) { + $newvalue = $data[$key]; + $changed = true; + $needrebuild = $needrebuild || $cached[$key]; + } else { + $newvalue = $value; + // we still insert entry in DB but there are no changes from user point of + // view and no need to call rebuild_course_cache() + } + $DB->insert_record('course_format_options', array( + 'courseid' => $this->courseid, + 'format' => $this->format, + 'sectionid' => $sectionid, + 'name' => $key, + 'value' => $newvalue + )); + } + } + if ($needrebuild) { + rebuild_course_cache($this->courseid, true); + } + if ($changed) { + // reset internal caches + if (!$sectionid) { + $this->course = false; + } + unset($this->formatoptions[$sectionid]); + } + return $changed; + } + + /** + * Updates format options for a course + * + * If $data does not contain property with the option name, the option will not be updated + * + * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data + * @param stdClass $oldcourse if this function is called from {@link update_course()} + * this object contains information about the course before update + * @return bool whether there were any changes to the options values + */ + public function update_course_format_options($data, $oldcourse = null) { + return $this->update_format_options($data); + } + + /** + * Updates format options for a section + * + * Section id is expected in $data->id (or $data['id']) + * If $data does not contain property with the option name, the option will not be updated + * + * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data + * @return bool whether there were any changes to the options values + */ + public function update_section_format_options($data) { + $data = (array)$data; + return $this->update_format_options($data, $data['id']); + } + + /** + * Return an instance of moodleform to edit a specified section + * + * Default implementation returns instance of editsection_form that automatically adds + * additional fields defined in {@link format_base::section_format_options()} + * + * Format plugins may extend editsection_form if they want to have custom edit section form. + * + * @param mixed $action the action attribute for the form. If empty defaults to auto detect the + * current url. If a moodle_url object then outputs params as hidden variables. + * @param array $customdata the array with custom data to be passed to the form + * /course/editsection.php passes section_info object in 'cs' field + * for filling availability fields + * @return moodleform + */ + public function editsection_form($action, $customdata = array()) { + global $CFG; + require_once($CFG->dirroot. '/course/editsection_form.php'); + $context = context_course::instance($this->courseid); + if (!array_key_exists('course', $customdata)) { + $customdata['course'] = $this->get_course(); + } + return new editsection_form($action, $customdata); + } } /** @@ -443,5 +781,33 @@ class format_site extends format_base { public function get_view_url($section, $options = array()) { return new moodle_url('/'); } -} + /** + * Returns the list of blocks to be automatically added on the site frontpage when moodle is installed + * + * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT + * each of values is an array of block names (for left and right side columns) + */ + public function get_default_blocks() { + return blocks_get_default_site_course_blocks(); + } + + /** + * Definitions of the additional options that site uses + * + * @param bool $foreditform + * @return array of options + */ + public function course_format_options($foreditform = false) { + static $courseformatoptions = false; + if ($courseformatoptions === false) { + $courseformatoptions = array( + 'numsections' => array( + 'default' => 1, + 'type' => PARAM_INT, + ), + ); + } + return $courseformatoptions; + } +} diff --git a/course/format/renderer.php b/course/format/renderer.php index 26f28c213b2..ea83ace3053 100644 --- a/course/format/renderer.php +++ b/course/format/renderer.php @@ -448,6 +448,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { */ protected function get_nav_links($course, $sections, $sectionno) { // FIXME: This is really evil and should by using the navigation API. + $course = course_get_format($course)->get_course(); $canviewhidden = has_capability('moodle/course:viewhiddensections', context_course::instance($course->id)) or !$course->hiddensections; @@ -542,6 +543,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { global $PAGE; $modinfo = get_fast_modinfo($course); + $course = course_get_format($course)->get_course(); // Can we view the section in question? if (!($sectioninfo = $modinfo->get_section_info($displaysection))) { @@ -637,6 +639,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base { global $PAGE; $modinfo = get_fast_modinfo($course); + $course = course_get_format($course)->get_course(); $context = context_course::instance($course->id); // Title with completion help icon. diff --git a/course/format/topics/format.php b/course/format/topics/format.php index 5108a863e79..cf2c0527411 100644 --- a/course/format/topics/format.php +++ b/course/format/topics/format.php @@ -45,6 +45,7 @@ if (($marker >=0) && has_capability('moodle/course:setcurrentsection', $context) } // make sure all sections are created +$course = course_get_format($course)->get_course(); course_create_sections_if_missing($course, range(0, $course->numsections)); $renderer = $PAGE->get_renderer('format_topics'); diff --git a/course/format/weeks/format.php b/course/format/weeks/format.php index 86fac1e647d..2f988551ab8 100644 --- a/course/format/weeks/format.php +++ b/course/format/weeks/format.php @@ -38,6 +38,7 @@ if ($week = optional_param('week', 0, PARAM_INT)) { // End backwards-compatible aliasing.. // make sure all sections are created +$course = course_get_format($course)->get_course(); course_create_sections_if_missing($course, range(0, $course->numsections)); $renderer = $PAGE->get_renderer('format_weeks'); diff --git a/course/lib.php b/course/lib.php index 0227f970091..5f5535b1218 100644 --- a/course/lib.php +++ b/course/lib.php @@ -2939,7 +2939,10 @@ function move_section($course, $section, $move) { $sectiondest = $section + $move; - if ($sectiondest > $course->numsections or $sectiondest < 1) { + // compartibility with course formats using field 'numsections' + $courseformatoptions = course_get_format($course)->get_format_options(); + if (array_key_exists('numsections', $courseformatoptions) && + $sectiondest > $courseformatoptions['numsections'] or $sectiondest < 1) { return false; } @@ -2965,7 +2968,10 @@ function move_section_to($course, $section, $destination) { return true; } - if (($destination > $course->numsections) || ($destination < 1)) { + // compartibility with course formats using field 'numsections' + $courseformatoptions = course_get_format($course)->get_format_options(); + if ((array_key_exists('numsections', $courseformatoptions) && + ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) { return false; } @@ -3775,7 +3781,10 @@ function create_course($data, $editoroptions = NULL) { $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid)); } - $course = $DB->get_record('course', array('id'=>$newcourseid)); + // update course format options + course_get_format($newcourseid)->update_course_format_options($data); + + $course = course_get_format($newcourseid)->get_course(); // Setup the blocks blocks_add_default_course_blocks($course); @@ -3843,7 +3852,7 @@ function update_course($data, $editoroptions = NULL) { $data->timemodified = time(); - $oldcourse = $DB->get_record('course', array('id'=>$data->id), '*', MUST_EXIST); + $oldcourse = course_get_format($data->id)->get_course(); $context = context_course::instance($oldcourse->id); if ($editoroptions) { @@ -3879,6 +3888,9 @@ function update_course($data, $editoroptions = NULL) { // make sure the modinfo cache is reset rebuild_course_cache($data->id); + // update course format options with full course data + course_get_format($data->id)->update_course_format_options($data, $oldcourse); + $course = $DB->get_record('course', array('id'=>$data->id)); if ($movecat) { @@ -3901,6 +3913,14 @@ function update_course($data, $editoroptions = NULL) { // Trigger events events_trigger('course_updated', $course); + + if ($oldcourse->format !== $course->format) { + // Remove all options stored for the previous format + // We assume that new course format migrated everything it needed watching trigger + // 'course_updated' and in method format_XXX::update_course_format_options() + $DB->delete_records('course_format_options', + array('courseid' => $course->id, 'format' => $oldcourse->format)); + } } /** @@ -4218,8 +4238,6 @@ class course_request { // Apply course default settings $data->format = $courseconfig->format; - $data->numsections = $courseconfig->numsections; - $data->hiddensections = $courseconfig->hiddensections; $data->newsitems = $courseconfig->newsitems; $data->showgrades = $courseconfig->showgrades; $data->showreports = $courseconfig->showreports; diff --git a/course/tests/externallib_test.php b/course/tests/externallib_test.php index 984b9ed3588..82cba692f0a 100644 --- a/course/tests/externallib_test.php +++ b/course/tests/externallib_test.php @@ -339,6 +339,17 @@ class core_course_external_testcase extends externallib_advanced_testcase { $course2['completionnotify'] = 1; $course2['lang'] = 'en'; $course2['forcetheme'] = 'base'; + $course3['fullname'] = 'Test course 3'; + $course3['shortname'] = 'Testcourse3'; + $course3['categoryid'] = $category->id; + $course3['format'] = 'topics'; + $course3options = array('numsections' => 8, + 'hiddensections' => 1, + 'coursedisplay' => 1); + $course3['courseformatoptions'] = array(); + foreach ($course3options as $key => $value) { + $course3['courseformatoptions'][] = array('name' => $key, 'value' => $value); + } $courses = array($course1, $course2); $createdcourses = core_course_external::create_courses($courses); @@ -348,58 +359,64 @@ class core_course_external_testcase extends externallib_advanced_testcase { // Check that the courses were correctly created. foreach ($createdcourses as $createdcourse) { - $dbcourse = $DB->get_record('course', array('id' => $createdcourse['id'])); + $courseinfo = course_get_format($createdcourse['id'])->get_course(); if ($createdcourse['shortname'] == $course2['shortname']) { - $this->assertEquals($dbcourse->fullname, $course2['fullname']); - $this->assertEquals($dbcourse->shortname, $course2['shortname']); - $this->assertEquals($dbcourse->category, $course2['categoryid']); - $this->assertEquals($dbcourse->idnumber, $course2['idnumber']); - $this->assertEquals($dbcourse->summary, $course2['summary']); - $this->assertEquals($dbcourse->summaryformat, $course2['summaryformat']); - $this->assertEquals($dbcourse->format, $course2['format']); - $this->assertEquals($dbcourse->showgrades, $course2['showgrades']); - $this->assertEquals($dbcourse->newsitems, $course2['newsitems']); - $this->assertEquals($dbcourse->startdate, $course2['startdate']); - $this->assertEquals($dbcourse->numsections, $course2['numsections']); - $this->assertEquals($dbcourse->maxbytes, $course2['maxbytes']); - $this->assertEquals($dbcourse->showreports, $course2['showreports']); - $this->assertEquals($dbcourse->visible, $course2['visible']); - $this->assertEquals($dbcourse->hiddensections, $course2['hiddensections']); - $this->assertEquals($dbcourse->groupmode, $course2['groupmode']); - $this->assertEquals($dbcourse->groupmodeforce, $course2['groupmodeforce']); - $this->assertEquals($dbcourse->defaultgroupingid, $course2['defaultgroupingid']); - $this->assertEquals($dbcourse->completionnotify, $course2['completionnotify']); - $this->assertEquals($dbcourse->lang, $course2['lang']); + $this->assertEquals($courseinfo->fullname, $course2['fullname']); + $this->assertEquals($courseinfo->shortname, $course2['shortname']); + $this->assertEquals($courseinfo->category, $course2['categoryid']); + $this->assertEquals($courseinfo->idnumber, $course2['idnumber']); + $this->assertEquals($courseinfo->summary, $course2['summary']); + $this->assertEquals($courseinfo->summaryformat, $course2['summaryformat']); + $this->assertEquals($courseinfo->format, $course2['format']); + $this->assertEquals($courseinfo->showgrades, $course2['showgrades']); + $this->assertEquals($courseinfo->newsitems, $course2['newsitems']); + $this->assertEquals($courseinfo->startdate, $course2['startdate']); + $this->assertEquals($courseinfo->numsections, $course2['numsections']); + $this->assertEquals($courseinfo->maxbytes, $course2['maxbytes']); + $this->assertEquals($courseinfo->showreports, $course2['showreports']); + $this->assertEquals($courseinfo->visible, $course2['visible']); + $this->assertEquals($courseinfo->hiddensections, $course2['hiddensections']); + $this->assertEquals($courseinfo->groupmode, $course2['groupmode']); + $this->assertEquals($courseinfo->groupmodeforce, $course2['groupmodeforce']); + $this->assertEquals($courseinfo->defaultgroupingid, $course2['defaultgroupingid']); + $this->assertEquals($courseinfo->completionnotify, $course2['completionnotify']); + $this->assertEquals($courseinfo->lang, $course2['lang']); if (!empty($CFG->allowcoursethemes)) { - $this->assertEquals($dbcourse->theme, $course2['forcetheme']); + $this->assertEquals($courseinfo->theme, $course2['forcetheme']); } if (completion_info::is_enabled_for_site()) { - $this->assertEquals($dbcourse->enablecompletion, $course2['enabledcompletion']); - $this->assertEquals($dbcourse->completionstartonenrol, $course2['completionstartonenrol']); + $this->assertEquals($courseinfo->enablecompletion, $course2['enabledcompletion']); + $this->assertEquals($courseinfo->completionstartonenrol, $course2['completionstartonenrol']); } else { - $this->assertEquals($dbcourse->enablecompletion, 0); - $this->assertEquals($dbcourse->completionstartonenrol, 0); + $this->assertEquals($courseinfo->enablecompletion, 0); + $this->assertEquals($courseinfo->completionstartonenrol, 0); } } else if ($createdcourse['shortname'] == $course1['shortname']) { $courseconfig = get_config('moodlecourse'); - $this->assertEquals($dbcourse->fullname, $course1['fullname']); - $this->assertEquals($dbcourse->shortname, $course1['shortname']); - $this->assertEquals($dbcourse->category, $course1['categoryid']); - $this->assertEquals($dbcourse->summaryformat, FORMAT_HTML); - $this->assertEquals($dbcourse->format, $courseconfig->format); - $this->assertEquals($dbcourse->showgrades, $courseconfig->showgrades); - $this->assertEquals($dbcourse->newsitems, $courseconfig->newsitems); - $this->assertEquals($dbcourse->numsections, $courseconfig->numsections); - $this->assertEquals($dbcourse->maxbytes, $courseconfig->maxbytes); - $this->assertEquals($dbcourse->showreports, $courseconfig->showreports); - $this->assertEquals($dbcourse->hiddensections, $courseconfig->hiddensections); - $this->assertEquals($dbcourse->groupmode, $courseconfig->groupmode); - $this->assertEquals($dbcourse->groupmodeforce, $courseconfig->groupmodeforce); - $this->assertEquals($dbcourse->defaultgroupingid, 0); + $this->assertEquals($courseinfo->fullname, $course1['fullname']); + $this->assertEquals($courseinfo->shortname, $course1['shortname']); + $this->assertEquals($courseinfo->category, $course1['categoryid']); + $this->assertEquals($courseinfo->summaryformat, FORMAT_HTML); + $this->assertEquals($courseinfo->format, $courseconfig->format); + $this->assertEquals($courseinfo->showgrades, $courseconfig->showgrades); + $this->assertEquals($courseinfo->newsitems, $courseconfig->newsitems); + $this->assertEquals($courseinfo->maxbytes, $courseconfig->maxbytes); + $this->assertEquals($courseinfo->showreports, $courseconfig->showreports); + $this->assertEquals($courseinfo->groupmode, $courseconfig->groupmode); + $this->assertEquals($courseinfo->groupmodeforce, $courseconfig->groupmodeforce); + $this->assertEquals($courseinfo->defaultgroupingid, 0); + } else if ($createdcourse['shortname'] == $course3['shortname']) { + $this->assertEquals($courseinfo->fullname, $course3['fullname']); + $this->assertEquals($courseinfo->shortname, $course3['shortname']); + $this->assertEquals($courseinfo->category, $course3['categoryid']); + $this->assertEquals($courseinfo->format, $course3['format']); + $this->assertEquals($courseinfo->hiddensections, $course3options['hiddensections']); + $this->assertEquals($courseinfo->numsections, $course3options['numsections']); + $this->assertEquals($courseinfo->coursedisplay, $course3options['coursedisplay']); } else { throw moodle_exception('Unexpected shortname'); } @@ -459,7 +476,7 @@ class core_course_external_testcase extends externallib_advanced_testcase { $generatedcourses[$course1->id] = $course1; $course2 = self::getDataGenerator()->create_course(); $generatedcourses[$course2->id] = $course2; - $course3 = self::getDataGenerator()->create_course(); + $course3 = self::getDataGenerator()->create_course(array('format' => 'topics')); $generatedcourses[$course3->id] = $course3; // Set the required capabilities by the external function. @@ -504,6 +521,13 @@ class core_course_external_testcase extends externallib_advanced_testcase { $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol); $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion); $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol); + if ($dbcourse->format === 'topics') { + $this->assertEquals($course['courseformatoptions'], array( + array('name' => 'numsections', 'value' => $dbcourse->numsections), + array('name' => 'hiddensections', 'value' => $dbcourse->hiddensections), + array('name' => 'coursedisplay', 'value' => $dbcourse->coursedisplay), + )); + } } // Get all courses in the DB diff --git a/course/yui/formatchooser/formatchooser.js b/course/yui/formatchooser/formatchooser.js new file mode 100644 index 00000000000..0597c4ba736 --- /dev/null +++ b/course/yui/formatchooser/formatchooser.js @@ -0,0 +1,25 @@ +YUI.add('moodle-course-formatchooser', function(Y) { + var FORMATCHOOSER = function() { + FORMATCHOOSER.superclass.constructor.apply(this, arguments); + } + + Y.extend(FORMATCHOOSER, Y.Base, { + initializer : function(params) { + if (params && params.formid) { + var updatebut = Y.one('#'+params.formid+' #id_updatecourseformat'); + var formatselect = Y.one('#'+params.formid+' #id_format'); + if (updatebut && formatselect) { + updatebut.setStyle('display', 'none'); + formatselect.on('change', function() { + updatebut.simulate('click'); + }); + } + } + } + }); + + M.course = M.course || {}; + M.course.init_formatchooser = function(params) { + return new FORMATCHOOSER(params); + } +}, '@VERSION@', {requires:['base', 'node', 'node-event-simulate']}); diff --git a/enrol/database/lib.php b/enrol/database/lib.php index 4ae2cfb6cf3..398dfee6d39 100644 --- a/enrol/database/lib.php +++ b/enrol/database/lib.php @@ -753,6 +753,7 @@ class enrol_database_plugin extends enrol_plugin { $template = false; if ($templatecourse) { if ($template = $DB->get_record('course', array('shortname'=>$templatecourse))) { + $template = fullclone(course_get_format($template)->get_course()); unset($template->id); unset($template->fullname); unset($template->shortname); @@ -769,8 +770,6 @@ class enrol_database_plugin extends enrol_plugin { $template->summary = ''; $template->summaryformat = FORMAT_HTML; $template->format = $courseconfig->format; - $template->numsections = $courseconfig->numsections; - $template->hiddensections = $courseconfig->hiddensections; $template->newsitems = $courseconfig->newsitems; $template->showgrades = $courseconfig->showgrades; $template->showreports = $courseconfig->showreports; diff --git a/enrol/database/tests/sync_test.php b/enrol/database/tests/sync_test.php index f094a47b254..8a32b6a39ed 100644 --- a/enrol/database/tests/sync_test.php +++ b/enrol/database/tests/sync_test.php @@ -682,9 +682,10 @@ class enrol_database_testcase extends advanced_testcase { $this->assertEquals(2+1+4+1+count(self::$courses), $DB->count_records('course')); $course8['category'] = $defcat->id; - $course8['numsections'] = 666; - $this->assertTrue($DB->record_exists('course', $course8)); - + $record = $DB->get_record('course', $course8); + $this->assertFalse(empty($record)); + $courseformatoptions = course_get_format($record)->get_format_options(); + $this->assertEquals($courseformatoptions['numsections'], 666); // Test invalid category. diff --git a/enrol/imsenterprise/lib.php b/enrol/imsenterprise/lib.php index f70b07f3fad..9d11e00be2b 100644 --- a/enrol/imsenterprise/lib.php +++ b/enrol/imsenterprise/lib.php @@ -386,8 +386,6 @@ function process_group_tag($tagcontents) { $course->idnumber = $coursecode; $course->format = $courseconfig->format; $course->visible = $courseconfig->visible; - $course->numsections = $courseconfig->numsections; - $course->hiddensections = $courseconfig->hiddensections; $course->newsitems = $courseconfig->newsitems; $course->showgrades = $courseconfig->showgrades; $course->showreports = $courseconfig->showreports; diff --git a/enrol/ldap/lib.php b/enrol/ldap/lib.php index f9ab2236a4b..ca7fa8e5b0b 100644 --- a/enrol/ldap/lib.php +++ b/enrol/ldap/lib.php @@ -887,6 +887,7 @@ class enrol_ldap_plugin extends enrol_plugin { $template = false; if ($this->get_config('template')) { if ($template = $DB->get_record('course', array('shortname'=>$this->get_config('template')))) { + $template = fullclone(course_get_format($template)->get_course()); unset($template->id); // So we are clear to reinsert the record unset($template->fullname); unset($template->shortname); @@ -899,8 +900,6 @@ class enrol_ldap_plugin extends enrol_plugin { $template->summary = ''; $template->summaryformat = FORMAT_HTML; $template->format = $courseconfig->format; - $template->numsections = $courseconfig->numsections; - $template->hiddensections = $courseconfig->hiddensections; $template->newsitems = $courseconfig->newsitems; $template->showgrades = $courseconfig->showgrades; $template->showreports = $courseconfig->showreports; diff --git a/index.php b/index.php index b416975c341..35c8eeb5e84 100644 --- a/index.php +++ b/index.php @@ -98,6 +98,7 @@ echo $OUTPUT->header(); /// Print Section or custom info + $siteformatoptions = course_get_format($SITE)->get_format_options(); $modinfo = get_fast_modinfo($SITE); $modnames = get_module_types_names(); $modnamesplural = get_module_types_names(true); @@ -107,7 +108,7 @@ if (!empty($CFG->customfrontpageinclude)) { include($CFG->customfrontpageinclude); - } else { + } else if ($siteformatoptions['numsections'] > 0) { if ($editing) { // make sure section with number 1 exists course_create_sections_if_missing($SITE, 1); diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 39ab8077ba6..118b4344b98 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -308,6 +308,8 @@ $string['coursefileswarning'] = 'Course files are deprecated'; $string['coursefileswarning_help'] = 'Course files are deprecated since Moodle 2.0, please use external repositories instead as much as possible.'; $string['courseformatdata'] = 'Course format data'; $string['courseformats'] = 'Course formats'; +$string['courseformatoptions'] = 'Formatting options for {$a}'; +$string['courseformatudpate'] = 'Update format'; $string['coursegrades'] = 'Course grades'; $string['coursehelpcategory'] = 'Position the course on the course listing and may make it easier for students to find it.'; $string['coursehelpforce'] = 'Force the course group mode to every activity in the course.'; diff --git a/lib/adminlib.php b/lib/adminlib.php index deef07ed2e7..0bf564999e4 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -3174,7 +3174,7 @@ class admin_setting_sitesetselect extends admin_setting_configselect { * @return string The site name of the selected site */ public function get_setting() { - $site = get_site(); + $site = course_get_format(get_site())->get_course(); return $site->{$this->name}; } @@ -3196,6 +3196,7 @@ class admin_setting_sitesetselect extends admin_setting_configselect { $record->timemodified = time(); // update $SITE $SITE->{$this->name} = $data; + course_get_format($SITE)->update_course_format_options($record); return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin')); } } @@ -3366,7 +3367,7 @@ class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox { * @return string */ public function get_setting() { - $site = get_site(); + $site = course_get_format(get_site())->get_course(); return $site->{$this->name}; } @@ -3384,6 +3385,7 @@ class admin_setting_sitesetcheckbox extends admin_setting_configcheckbox { $record->timemodified = time(); // update $SITE $SITE->{$this->name} = $data; + course_get_format($SITE)->update_course_format_options($record); return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin')); } } @@ -3401,7 +3403,7 @@ class admin_setting_sitesettext extends admin_setting_configtext { * @return mixed string or null */ public function get_setting() { - $site = get_site(); + $site = course_get_format(get_site())->get_course(); return $site->{$this->name} != '' ? $site->{$this->name} : NULL; } @@ -3443,6 +3445,7 @@ class admin_setting_sitesettext extends admin_setting_configtext { $record->timemodified = time(); // update $SITE $SITE->{$this->name} = $data; + course_get_format($SITE)->update_course_format_options($record); return ($DB->update_record('course', $record) ? '' : get_string('dbupdatefailed', 'error')); } } @@ -3467,7 +3470,7 @@ class admin_setting_special_frontpagedesc extends admin_setting { * @return string The current setting */ public function get_setting() { - $site = get_site(); + $site = course_get_format(get_site())->get_course(); return $site->{$this->name}; } @@ -3484,6 +3487,7 @@ class admin_setting_special_frontpagedesc extends admin_setting { $record->{$this->name} = $data; $record->timemodified = time(); $SITE->{$this->name} = $data; + course_get_format($SITE)->update_course_format_options($record); return ($DB->update_record('course', $record) ? '' : get_string('errorsetting', 'admin')); } diff --git a/lib/db/install.php b/lib/db/install.php index 2e401fe817a..824813bee76 100644 --- a/lib/db/install.php +++ b/lib/db/install.php @@ -86,6 +86,10 @@ function xmldb_main_install() { $newsite->id = $DB->insert_record('course', $newsite); define('SITEID', $newsite->id); } + // set the field 'numsections'. We can not use format_site::update_format_options() because + // the file is not loaded + $DB->insert_record('course_format_options', array('courseid' => SITEID, 'format' => 'site', + 'sectionid' => 0, 'name' => 'numsections', 'value' => $newsite->numsections)); $SITE = get_site(); if ($newsite->id != $SITE->id) { throw new moodle_exception('generalexceptionmessage', 'error', '', 'Unexpected new site course id!'); diff --git a/lib/db/install.xml b/lib/db/install.xml index b6f01ece6b3..96dc090f9db 100644 --- a/lib/db/install.xml +++ b/lib/db/install.xml @@ -1,5 +1,5 @@ - @@ -77,21 +77,19 @@ - + - - - + + - - - + + @@ -101,8 +99,7 @@ - - + @@ -420,7 +417,7 @@ - +
    @@ -439,7 +436,24 @@
    - +
    + + + + + + + + + + + + + + + +
    + diff --git a/lib/db/upgrade.php b/lib/db/upgrade.php index b75d16171a1..8ade318b874 100644 --- a/lib/db/upgrade.php +++ b/lib/db/upgrade.php @@ -1311,5 +1311,93 @@ function xmldb_main_upgrade($oldversion) { upgrade_main_savepoint(true, 2012103003.00); } + if ($oldversion < 2012110200.00) { + + // Define table course_format_options to be created + $table = new xmldb_table('course_format_options'); + + // Adding fields to table course_format_options + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('format', XMLDB_TYPE_CHAR, '21', null, XMLDB_NOTNULL, null, null); + $table->add_field('sectionid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'format'); + $table->add_field('name', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); + $table->add_field('value', XMLDB_TYPE_TEXT, null, null, null, null, null); + + // Adding keys to table course_format_options + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + $table->add_key('courseid', XMLDB_KEY_FOREIGN, array('courseid'), 'course', array('id')); + + // Adding indexes to table course_format_options + $table->add_index('formatoption', XMLDB_INDEX_UNIQUE, array('courseid', 'format', 'sectionid', 'name')); + + // Conditionally launch create table for course_format_options + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Changing type of field format on table course to char with length 21 + $table = new xmldb_table('course'); + $field = new xmldb_field('format', XMLDB_TYPE_CHAR, '21', null, XMLDB_NOTNULL, null, 'topics', 'summaryformat'); + + // Launch change of type for field format + $dbman->change_field_type($table, $field); + + // Main savepoint reached + upgrade_main_savepoint(true, 2012110200.00); + } + + if ($oldversion < 2012110201.00) { + + // Copy fields 'coursedisplay', 'numsections', 'hiddensections' from table {course} + // to table {course_format_options} as the additional format options + $fields = array(); + $table = new xmldb_table('course'); + foreach (array('coursedisplay', 'numsections', 'hiddensections') as $fieldname) { + // first check that fields still exist + $field = new xmldb_field($fieldname); + if ($dbman->field_exists($table, $field)) { + $fields[] = $fieldname; + } + } + + if (!empty($fields)) { + $transaction = $DB->start_delegated_transaction(); + $rs = $DB->get_recordset_sql('SELECT id, format, '. join(',', $fields).' + FROM {course} + WHERE format <> ? AND format <> ?', + array('scorm', 'social')); + // (do not copy fields from scrom and social formats, we already know that they are not used) + foreach ($rs as $rec) { + foreach ($fields as $field) { + try { + $DB->insert_record('course_format_options', + array( + 'courseid' => $rec->id, + 'format' => $rec->format, + 'sectionid' => 0, + 'name' => $field, + 'value' => $rec->$field + )); + } catch (dml_exception $e) { + // index 'courseid,format,sectionid,name' violation + // continue; the entry in course_format_options already exists, use it + } + } + } + $rs->close(); + $transaction->allow_commit(); + + // Drop fields from table course + foreach ($fields as $fieldname) { + $field = new xmldb_field($fieldname); + $dbman->drop_field($table, $field); + } + } + + // Main savepoint reached + upgrade_main_savepoint(true, 2012110201.00); + } + return true; } diff --git a/lib/filelib.php b/lib/filelib.php index a9a49d9b7d5..a747672d063 100644 --- a/lib/filelib.php +++ b/lib/filelib.php @@ -3977,13 +3977,6 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) { send_file_not_found(); } - if ($course->numsections < $section->section) { - if (!has_capability('moodle/course:update', $context)) { - // block access to unavailable sections if can not edit course - send_file_not_found(); - } - } - $filename = array_pop($args); $filepath = $args ? '/'.implode('/', $args).'/' : '/'; if (!$file = $fs->get_file($context->id, 'course', 'section', $sectionid, $filepath, $filename) or $file->is_directory()) { diff --git a/lib/modinfolib.php b/lib/modinfolib.php index 405fd7bfa34..ec02959e928 100644 --- a/lib/modinfolib.php +++ b/lib/modinfolib.php @@ -364,8 +364,18 @@ class course_modinfo extends stdClass { 'availablefrom, availableuntil, showavailability, groupingid'); $compressedsections = array(); + $formatoptionsdef = course_get_format($courseid)->section_format_options(); // Remove unnecessary data and add availability foreach ($sections as $number => $section) { + // Add cached options from course format to $section object + foreach ($formatoptionsdef as $key => $option) { + if (!empty($option['cache'])) { + $formatoptions = course_get_format($courseid)->get_format_options($section); + if (!array_key_exists('cachedefault', $option) || $option['cachedefault'] !== $formatoptions[$key]) { + $section->$key = $formatoptions[$key]; + } + } + } // Clone just in case it is reused elsewhere $compressedsections[$number] = clone($section); section_info::convert_for_section_cache($compressedsections[$number]); @@ -1434,48 +1444,48 @@ class cached_cm_info { * Data about a single section on a course. This contains the fields from the * course_sections table, plus additional data when required. */ -class section_info extends stdClass { +class section_info implements IteratorAggregate { /** * Section ID - from course_sections table * @var int */ - public $id; + private $_id; /** * Course ID - from course_sections table * @var int */ - public $course; + private $_course; /** * Section number - from course_sections table * @var int */ - public $section; + private $_section; /** * Section name if specified - from course_sections table * @var string */ - public $name; + private $_name; /** * Section visibility (1 = visible) - from course_sections table * @var int */ - public $visible; + private $_visible; /** * Section summary text if specified - from course_sections table * @var string */ - public $summary; + private $_summary; /** * Section summary text format (FORMAT_xx constant) - from course_sections table * @var int */ - public $summaryformat; + private $_summaryformat; /** * When section is unavailable, this field controls whether it is shown to students (0 = @@ -1483,28 +1493,28 @@ class section_info extends stdClass { * from course_sections table * @var int */ - public $showavailability; + private $_showavailability; /** * Available date for this section (0 if not set, or set to seconds since epoch; before this * date, section does not display to students) - from course_sections table * @var int */ - public $availablefrom; + private $_availablefrom; /** * Available until date for this section (0 if not set, or set to seconds since epoch; from * this date, section does not display to students) - from course_sections table * @var int */ - public $availableuntil; + private $_availableuntil; /** * If section is restricted to users of a particular grouping, this is its id * (0 if not set) - from course_sections table * @var int */ - public $groupingid; + private $_groupingid; /** * Availability conditions for this section based on the completion of @@ -1512,7 +1522,7 @@ class section_info extends stdClass { * for that module) - from cached data in sectioncache field * @var array */ - public $conditionscompletion; + private $_conditionscompletion; /** * Availability conditions for this section based on course grades (array from @@ -1520,14 +1530,20 @@ class section_info extends stdClass { * sectioncache field * @var array */ - public $conditionsgrade; + private $_conditionsgrade; + + /** + * Availability conditions for this section based on user fields + * @var array + */ + private $_conditionsfield; /** * True if this section is available to students i.e. if all availability conditions * are met - obtained dynamically * @var bool */ - public $available; + private $_available; /** * If section is not available to students, this string gives information about @@ -1535,7 +1551,7 @@ class section_info extends stdClass { * January 2010') for display on main page - obtained dynamically * @var string */ - public $availableinfo; + private $_availableinfo; /** * True if this section is available to the CURRENT user (for example, if current user @@ -1543,7 +1559,7 @@ class section_info extends stdClass { * visible or not available, so this would be true in that case) * @var bool */ - public $uservisible; + private $_uservisible; /** * Default values for sectioncache fields; if a field has this value, it won't @@ -1562,6 +1578,13 @@ class section_info extends stdClass { 'groupingid' => '0', ); + /** + * Stores format options that have been cached when building 'coursecache' + * When the format option is requested we look first if it has been cached + * @var array + */ + private $cachedformatoptions = array(); + /** * Constructs object from database information plus extra required data. * @param object $data Array entry from cached sectioncache @@ -1575,56 +1598,142 @@ class section_info extends stdClass { global $CFG; // Data that is always present - $this->id = $data->id; + $this->_id = $data->id; + + $defaults = self::$sectioncachedefaults + + array('conditionscompletion' => array(), + 'conditionsgrade' => array(), + 'conditionsfield' => array()); // Data that may use default values to save cache size - foreach (self::$sectioncachedefaults as $field => $value) { + foreach ($defaults as $field => $value) { if (isset($data->{$field})) { - $this->{$field} = $data->{$field}; + $this->{'_'.$field} = $data->{$field}; } else { - $this->{$field} = $value; + $this->{'_'.$field} = $value; } } - // Data with array defaults - $this->conditionscompletion = isset($data->conditionscompletion) - ? $data->conditionscompletion : array(); - $this->conditionsgrade = isset($data->conditionsgrade) - ? $data->conditionsgrade : array(); - $this->conditionsfield = isset($data->conditionsfield) - ? $data->conditionsfield : array(); + // cached course format data + $formatoptionsdef = course_get_format($courseid)->section_format_options(); + foreach ($formatoptionsdef as $field => $option) { + if (!empty($option['cache'])) { + if (isset($data->{$field})) { + $this->cachedformatoptions[$field] = $data->{$field}; + } else if (array_key_exists('cachedefault', $option)) { + $this->cachedformatoptions[$field] = $option['cachedefault']; + } + } + } // Other data from other places - $this->course = $courseid; - $this->section = $number; - $this->sequence = $sequence; + $this->_course = $courseid; + $this->_section = $number; + $this->_sequence = $sequence; // Availability data if (!empty($CFG->enableavailability)) { // Get availability information $ci = new condition_info_section($this); - $this->available = $ci->is_available($this->availableinfo, true, + $this->_available = $ci->is_available($this->_availableinfo, true, $userid, $modinfo); // Display grouping info if available & not already displaying // (it would already display if current user doesn't have access) // for people with managegroups - same logic/class as grouping label // on individual activities. $context = context_course::instance($courseid); - if ($this->availableinfo === '' && $this->groupingid && + if ($this->_availableinfo === '' && $this->_groupingid && has_capability('moodle/course:managegroups', $context)) { $groupings = groups_get_all_groupings($courseid); - $this->availableinfo = html_writer::tag('span', '(' . format_string( - $groupings[$this->groupingid]->name, true, array('context' => $context)) . + $this->_availableinfo = html_writer::tag('span', '(' . format_string( + $groupings[$this->_groupingid]->name, true, array('context' => $context)) . ')', array('class' => 'groupinglabel')); } } else { - $this->available = true; + $this->_available = true; } // Update visibility for current user $this->update_user_visible($userid); } + /** + * Magic method to check if the property is set + * + * @param string $name name of the property + * @return bool + */ + public function __isset($name) { + if (property_exists($this, '_'.$name)) { + return isset($this->{'_'.$name}); + } + $defaultformatoptions = course_get_format($this->_course)->section_format_options(); + if (array_key_exists($name, $defaultformatoptions)) { + $value = $this->__get($name); + return isset($value); + } + return false; + } + + /** + * Magic method to check if the property is empty + * + * @param string $name name of the property + * @return bool + */ + public function __empty($name) { + if (property_exists($this, '_'.$name)) { + return empty($this->{'_'.$name}); + } + $defaultformatoptions = course_get_format($this->_course)->section_format_options(); + if (array_key_exists($name, $defaultformatoptions)) { + $value = $this->__get($name); + return empty($value); + } + return true; + } + + /** + * Magic method to retrieve the property, this is either basic section property + * or availability information or additional properties added by course format + * + * @param string $name name of the property + * @return bool + */ + public function __get($name) { + if (property_exists($this, '_'.$name)) { + return $this->{'_'.$name}; + } + if (array_key_exists($name, $this->cachedformatoptions)) { + return $this->cachedformatoptions[$name]; + } + $defaultformatoptions = course_get_format($this->_course)->section_format_options(); + // precheck if the option is defined in format to avoid unnecessary DB queries in get_format_options() + if (array_key_exists($name, $defaultformatoptions)) { + $formatoptions = course_get_format($this->_course)->get_format_options($this); + return $formatoptions[$name]; + } + debugging('Invalid section_info property accessed! '.$name); + return null; + } + + /** + * Implementation of IteratorAggregate::getIterator(), allows to cycle through properties + * and use {@link convert_to_array()} + * + * @return ArrayIterator + */ + public function getIterator() { + $ret = array(); + foreach (get_object_vars($this) as $key => $value) { + if (substr($key, 0, 1) == '_') { + $ret[substr($key, 1)] = $this->$key; + } + } + $ret = array_merge($ret, course_get_format($this->_course)->get_format_options($this)); + return new ArrayIterator($ret); + } + /** * Works out whether activity is visible *for current user* - if this is false, they * aren't allowed to access it. @@ -1633,11 +1742,11 @@ class section_info extends stdClass { */ private function update_user_visible($userid) { global $CFG; - $coursecontext = context_course::instance($this->course); - $this->uservisible = true; - if ((!$this->visible || !$this->available) && + $coursecontext = context_course::instance($this->_course); + $this->_uservisible = true; + if ((!$this->_visible || !$this->_available) && !has_capability('moodle/course:viewhiddensections', $coursecontext, $userid)) { - $this->uservisible = false; + $this->_uservisible = false; } } diff --git a/lib/navigationlib.php b/lib/navigationlib.php index c445b38e567..3ebac4dda3b 100644 --- a/lib/navigationlib.php +++ b/lib/navigationlib.php @@ -1887,7 +1887,14 @@ class global_navigation extends navigation_node { require_once($CFG->dirroot.'/course/lib.php'); $modinfo = get_fast_modinfo($course); - $sections = array_slice($modinfo->get_section_info_all(), 0, $course->numsections+1, true); + $sections = $modinfo->get_section_info_all(); + + // For course formats using 'numsections' trim the sections list + $courseformatoptions = course_get_format($course)->get_format_options(); + if (isset($courseformatoptions['numsections'])) { + $sections = array_slice($sections, 0, $courseformatoptions['numsections']+1, true); + } + $activities = array(); foreach ($sections as $key => $section) { diff --git a/lib/weblib.php b/lib/weblib.php index 3c0dc19e982..6be03c9b2dd 100644 --- a/lib/weblib.php +++ b/lib/weblib.php @@ -2187,6 +2187,7 @@ function navmenulist($course, $sections, $modinfo, $strsection, $strjumpto, $wid $menu = array(); $doneheading = false; + $courseformatoptions = course_get_format($course)->get_format_options(); $coursecontext = context_course::instance($course->id); $menu[] = '