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 @@
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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[] = '