';
+
+/// Print current category description
+if (!$editingon && $category->description) {
+ echo $OUTPUT->box_start();
+ $options = new stdClass;
+ $options->noclean = true;
+ $options->para = false;
+ $options->overflowdiv = true;
+ if (!isset($category->descriptionformat)) {
+ $category->descriptionformat = FORMAT_MOODLE;
+ }
+ $text = file_rewrite_pluginfile_urls($category->description, 'pluginfile.php', $context->id, 'coursecat', 'description', null);
+ echo format_text($text, $category->descriptionformat, $options);
+ echo $OUTPUT->box_end();
+}
+
+if ($editingon && $canmanage) {
+ echo $OUTPUT->container_start('buttons');
+
+ // Print button to update this category
+ $url = new moodle_url('/course/editcategory.php', array('id' => $category->id));
+ echo $OUTPUT->single_button($url, get_string('editcategorythis'), 'get');
+
+ // Print button for creating new categories
+ $url = new moodle_url('/course/editcategory.php', array('parent' => $category->id));
+ echo $OUTPUT->single_button($url, get_string('addsubcategory'), 'get');
+
+ echo $OUTPUT->container_end();
+}
+
+// Print out all the sub-categories
+// In order to view hidden subcategories the user must have the viewhiddencategories
+// capability in the current category.
+if (has_capability('moodle/category:viewhiddencategories', $context)) {
+ $categorywhere = '';
+} else {
+ $categorywhere = 'AND cc.visible = 1';
+}
+// We're going to preload the context for the subcategory as we know that we
+// need it later on for formatting.
+
+$ctxselect = context_helper::get_preload_record_columns_sql('ctx');
+$sql = "SELECT cc.*, $ctxselect
+ FROM {course_categories} cc
+ JOIN {context} ctx ON cc.id = ctx.instanceid
+ WHERE cc.parent = :parentid AND
+ ctx.contextlevel = :contextlevel
+ $categorywhere
+ ORDER BY cc.sortorder ASC";
+$subcategories = $DB->get_recordset_sql($sql, array('parentid' => $category->id, 'contextlevel' => CONTEXT_COURSECAT));
+// Prepare a table to display the sub categories.
+$table = new html_table;
+$table->attributes = array('border' => '0', 'cellspacing' => '2', 'cellpadding' => '4', 'class' => 'generalbox boxaligncenter category_subcategories');
+$table->head = array(new lang_string('subcategories'));
+$table->data = array();
+$baseurl = new moodle_url('/course/category.php');
+foreach ($subcategories as $subcategory) {
+ // Preload the context we will need it to format the category name shortly.
+ context_helper::preload_from_record($subcategory);
+ $context = context_coursecat::instance($subcategory->id);
+ // Prepare the things we need to create a link to the subcategory
+ $attributes = $subcategory->visible ? array() : array('class' => 'dimmed');
+ $text = format_string($subcategory->name, true, array('context' => $context));
+ // Add the subcategory to the table
+ $baseurl->param('id', $subcategory->id);
+ $table->data[] = array(html_writer::link($baseurl, $text, $attributes));
+}
+
+$subcategorieswereshown = (count($table->data) > 0);
+if ($subcategorieswereshown) {
+ echo html_writer::table($table);
+}
+
+// Print out all the courses.
+$courses = get_courses_page($category->id, 'c.sortorder ASC',
+ 'c.id,c.sortorder,c.shortname,c.fullname,c.summary,c.visible',
+ $totalcount, $page*$perpage, $perpage);
+$numcourses = count($courses);
+
+// We can consider that we are using pagination when the total count of courses is different than the one returned.
+$pagingmode = $totalcount != $numcourses;
+
+if (!$courses) {
+ // There is no course to display.
+ if (empty($subcategorieswereshown)) {
+ echo $OUTPUT->heading(get_string("nocoursesyet"));
+ }
+} else if ($numcourses <= $CFG->courseswithsummarieslimit and !$pagingmode and !$editingon) {
+ // We display courses with their summaries as we have not reached the limit, also we are not
+ // in paging mode and not allowed to edit either.
+ echo $OUTPUT->box_start('courseboxes');
+ print_courses($category);
+ echo $OUTPUT->box_end();
+} else {
+ // The conditions above have failed, we display a basic list of courses with paging/editing options.
+ echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "/course/category.php?id=$category->id&perpage=$perpage");
+
+ echo '';
+ echo ' ';
+}
+
+echo '
';
+if ($canmanage and $numcourses > 1) {
+ // Print button to re-sort courses by name
+ $url = new moodle_url('/course/category.php', array('id' => $category->id, 'resort' => 'name', 'sesskey' => sesskey()));
+ echo $OUTPUT->single_button($url, get_string('resortcoursesbyname'), 'get');
+}
+
+if (has_capability('moodle/course:create', $context)) {
+ // Print button to create a new course
+ $url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'category'));
+ echo $OUTPUT->single_button($url, get_string('addnewcourse'), 'get');
+}
+
+if (!empty($CFG->enablecourserequests) && $category->id == $CFG->defaultrequestcategory) {
+ print_course_request_buttons(context_system::instance());
+}
+echo '
';
+
+print_course_search();
+
+echo $OUTPUT->footer();
diff --git a/changenumsections.php b/changenumsections.php
new file mode 100644
index 0000000..deed367
--- /dev/null
+++ b/changenumsections.php
@@ -0,0 +1,63 @@
+.
+
+/**
+ * This script allows the number of sections in a course to be increased
+ * or decreased, redirecting to the course page.
+ *
+ * @package core_course
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+
+require_once(dirname(__FILE__).'/../config.php');
+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));
+
+// Authorisation checks.
+require_login($course);
+require_capability('moodle/course:update', context_course::instance($course->id));
+require_sesskey();
+
+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 ($courseformatoptions['numsections'] >= 0) {
+ course_get_format($course)->update_course_format_options(
+ array('numsections' => $courseformatoptions['numsections']));
+ }
+}
+
+$url = course_get_url($course);
+$url->set_anchor('changenumsections');
+// Redirect to where we were..
+redirect($url);
diff --git a/completion.js b/completion.js
new file mode 100644
index 0000000..4d9542a
--- /dev/null
+++ b/completion.js
@@ -0,0 +1,109 @@
+
+M.core_completion = {};
+
+M.core_completion.init = function(Y) {
+ // Check the reload-forcing
+ var changeDetector = Y.one('#completion_dynamic_change');
+ if (changeDetector.get('value') > 0) {
+ changeDetector.set('value', 0);
+ window.location.reload();
+ return;
+ }
+
+ var handle_success = function(id, o, args) {
+ Y.one('#completion_dynamic_change').set('value', 1);
+
+ if (o.responseText != 'OK') {
+ alert('An error occurred when attempting to save your tick mark.\n\n('+o.responseText+'.)'); //TODO: localize
+
+ } else {
+ var current = args.state.get('value');
+ var modulename = args.modulename.get('value');
+ if (current == 1) {
+ var altstr = M.str.completion['completion-alt-manual-y'].replace('{$a}', modulename);
+ var titlestr = M.str.completion['completion-title-manual-y'].replace('{$a}', modulename);
+ args.state.set('value', 0);
+ args.image.set('src', M.util.image_url('i/completion-manual-y', 'moodle'));
+ args.image.set('alt', altstr);
+ args.image.set('title', titlestr);
+ } else {
+ var altstr = M.str.completion['completion-alt-manual-n'].replace('{$a}', modulename);
+ var titlestr = M.str.completion['completion-title-manual-n'].replace('{$a}', modulename);
+ args.state.set('value', 1);
+ args.image.set('src', M.util.image_url('i/completion-manual-n', 'moodle'));
+ args.image.set('alt', altstr);
+ args.image.set('title', titlestr);
+ }
+ }
+
+ args.ajax.remove();
+ };
+
+ var handle_failure = function(id, o, args) {
+ alert('An error occurred when attempting to save your tick mark.\n\n('+o.responseText+'.)'); //TODO: localize
+ args.ajax.remove();
+ };
+
+ var toggle = function(e) {
+ e.preventDefault();
+
+ var form = e.target;
+ var cmid = 0;
+ var completionstate = 0;
+ var state = null;
+ var image = null;
+ var modulename = null;
+
+ var inputs = Y.Node.getDOMNode(form).getElementsByTagName('input');
+ for (var i=0; i');
+ form.append(ajax);
+
+ var cfg = {
+ method: "POST",
+ data: 'id='+cmid+'&completionstate='+completionstate+'&fromajax=1&sesskey='+M.cfg.sesskey,
+ on: {
+ success: handle_success,
+ failure: handle_failure
+ },
+ arguments: {state: state, image: image, ajax: ajax, modulename: modulename}
+ };
+
+ Y.use('io-base', function(Y) {
+ Y.io(M.cfg.wwwroot+'/course/togglecompletion.php', cfg);
+ });
+ };
+
+ // register submit handlers on manual tick completion forms
+ Y.all('form.togglecompletion').each(function(form) {
+ if (!form.hasClass('preventjs')) {
+ Y.on('submit', toggle, form);
+ }
+ });
+
+ // hide the help if there are no completion toggles or icons
+ var help = Y.one('#completionprogressid');
+ if (help && !(Y.one('form.togglecompletion') || Y.one('.autocompletion'))) {
+ help.setStyle('display', 'none');
+ }
+};
+
+
diff --git a/completion.php b/completion.php
new file mode 100644
index 0000000..559ccdb
--- /dev/null
+++ b/completion.php
@@ -0,0 +1,159 @@
+libdir.'/completionlib.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_unenrol.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_role.php');
+require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
+require_once $CFG->libdir.'/gradelib.php';
+require_once('completion_form.php');
+
+$id = required_param('id', PARAM_INT); // course id
+
+/// basic access control checks
+if ($id) { // editing course
+
+ if($id == SITEID){
+ // don't allow editing of 'site course' using this from
+ print_error('cannoteditsiteform');
+ }
+
+ if (!$course = $DB->get_record('course', array('id'=>$id))) {
+ print_error('invalidcourseid');
+ }
+ require_login($course);
+ require_capability('moodle/course:update', context_course::instance($course->id));
+
+} else {
+ require_login();
+ print_error('needcourseid');
+}
+
+/// Set up the page
+$streditcompletionsettings = get_string("editcoursecompletionsettings", 'completion');
+$PAGE->set_course($course);
+$PAGE->set_url('/course/completion.php', array('id' => $course->id));
+//$PAGE->navbar->add($streditcompletionsettings);
+$PAGE->set_title($course->shortname);
+$PAGE->set_heading($course->fullname);
+$PAGE->set_pagelayout('standard');
+
+/// first create the form
+$form = new course_completion_form('completion.php?id='.$id, compact('course'));
+
+// now override defaults if course already exists
+if ($form->is_cancelled()){
+ redirect($CFG->wwwroot.'/course/view.php?id='.$course->id);
+
+} else if ($data = $form->get_data()) {
+
+ $completion = new completion_info($course);
+
+/// process criteria unlocking if requested
+ if (!empty($data->settingsunlock)) {
+
+ $completion->delete_course_completion_data();
+
+ // Return to form (now unlocked)
+ redirect($CFG->wwwroot."/course/completion.php?id=$course->id");
+ }
+
+/// process data if submitted
+ // Delete old criteria
+ $completion->clear_criteria();
+
+ // Loop through each criteria type and run update_config
+ global $COMPLETION_CRITERIA_TYPES;
+ foreach ($COMPLETION_CRITERIA_TYPES as $type) {
+ $class = 'completion_criteria_'.$type;
+ $criterion = new $class();
+ $criterion->update_config($data);
+ }
+
+ // Handle aggregation methods
+ // Overall aggregation
+ $aggdata = array(
+ 'course' => $data->id,
+ 'criteriatype' => null
+ );
+ $aggregation = new completion_aggregation($aggdata);
+ $aggregation->setMethod($data->overall_aggregation);
+ $aggregation->save();
+
+ // Activity aggregation
+ if (empty($data->activity_aggregation)) {
+ $data->activity_aggregation = 0;
+ }
+
+ $aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ACTIVITY;
+ $aggregation = new completion_aggregation($aggdata);
+ $aggregation->setMethod($data->activity_aggregation);
+ $aggregation->save();
+
+ // Course aggregation
+ if (empty($data->course_aggregation)) {
+ $data->course_aggregation = 0;
+ }
+
+ $aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_COURSE;
+ $aggregation = new completion_aggregation($aggdata);
+ $aggregation->setMethod($data->course_aggregation);
+ $aggregation->save();
+
+ // Role aggregation
+ if (empty($data->role_aggregation)) {
+ $data->role_aggregation = 0;
+ }
+
+ $aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ROLE;
+ $aggregation = new completion_aggregation($aggdata);
+ $aggregation->setMethod($data->role_aggregation);
+ $aggregation->save();
+
+ add_to_log($course->id, 'course', 'completion updated', 'completion.php?id='.$course->id);
+
+ $url = new moodle_url('/course/view.php', array('id' => $course->id));
+ redirect($url);
+}
+
+
+/// Print the form
+
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($streditcompletionsettings);
+
+$form->display();
+
+echo $OUTPUT->footer();
diff --git a/completion_form.php b/completion_form.php
new file mode 100644
index 0000000..1f68101
--- /dev/null
+++ b/completion_form.php
@@ -0,0 +1,222 @@
+libdir.'/formslib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+class course_completion_form extends moodleform {
+
+ function definition() {
+ global $USER, $CFG, $DB, $js_enabled;
+
+ $courseconfig = get_config('moodlecourse');
+ $mform =& $this->_form;
+
+ $course = $this->_customdata['course'];
+ $completion = new completion_info($course);
+
+ $params = array(
+ 'course' => $course->id
+ );
+
+
+/// form definition
+//--------------------------------------------------------------------------------
+
+ // Check if there is existing criteria completions
+ if ($completion->is_course_locked()) {
+ $mform->addElement('header', '', get_string('completionsettingslocked', 'completion'));
+ $mform->addElement('static', '', '', get_string('err_settingslocked', 'completion'));
+ $mform->addElement('submit', 'settingsunlock', get_string('unlockcompletiondelete', 'completion'));
+ }
+
+ // Get array of all available aggregation methods
+ $aggregation_methods = $completion->get_aggregation_methods();
+
+ // Overall criteria aggregation
+ $mform->addElement('header', 'overallcriteria', get_string('overallcriteriaaggregation', 'completion'));
+ $mform->addElement('select', 'overall_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+ $mform->setDefault('overall_aggregation', $completion->get_aggregation_method());
+
+ // Course prerequisite completion criteria
+ $mform->addElement('header', 'courseprerequisites', get_string('completiondependencies', 'completion'));
+
+ // Get applicable courses
+ $courses = $DB->get_records_sql(
+ "
+ SELECT DISTINCT
+ c.id,
+ c.category,
+ c.fullname,
+ cc.id AS selected
+ FROM
+ {course} c
+ LEFT JOIN
+ {course_completion_criteria} cc
+ ON cc.courseinstance = c.id
+ AND cc.course = {$course->id}
+ INNER JOIN
+ {course_completion_criteria} ccc
+ ON ccc.course = c.id
+ WHERE
+ c.enablecompletion = ".COMPLETION_ENABLED."
+ AND c.id <> {$course->id}
+ "
+ );
+
+ if (!empty($courses)) {
+ if (count($courses) > 1) {
+ $mform->addElement('select', 'course_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+ $mform->setDefault('course_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_COURSE));
+ }
+
+ // Get category list
+ $list = array();
+ $parents = array();
+ make_categories_list($list, $parents);
+
+ // Get course list for select box
+ $selectbox = array();
+ $selected = array();
+ foreach ($courses as $c) {
+ $selectbox[$c->id] = $list[$c->category] . ' / ' . format_string($c->fullname, true, array('context' => context_course::instance($c->id)));
+
+ // If already selected
+ if ($c->selected) {
+ $selected[] = $c->id;
+ }
+ }
+
+ // Show multiselect box
+ $mform->addElement('select', 'criteria_course', get_string('coursesavailable', 'completion'), $selectbox, array('multiple' => 'multiple', 'size' => 6));
+
+ // Select current criteria
+ $mform->setDefault('criteria_course', $selected);
+
+ // Explain list
+ $mform->addElement('static', 'criteria_courses_explaination', '', get_string('coursesavailableexplaination', 'completion'));
+
+ } else {
+ $mform->addElement('static', 'nocourses', '', get_string('err_nocourses', 'completion'));
+ }
+
+ // Manual self completion
+ $mform->addElement('header', 'manualselfcompletion', get_string('manualselfcompletion', 'completion'));
+ $criteria = new completion_criteria_self($params);
+ $criteria->config_form_display($mform);
+
+ // Role completion criteria
+ $mform->addElement('header', 'roles', get_string('manualcompletionby', 'completion'));
+
+ $roles = get_roles_with_capability('moodle/course:markcomplete', CAP_ALLOW, context_course::instance($course->id, IGNORE_MISSING));
+
+ if (!empty($roles)) {
+ $mform->addElement('select', 'role_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+ $mform->setDefault('role_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ROLE));
+
+ foreach ($roles as $role) {
+ $params_a = array('role' => $role->id);
+ $criteria = new completion_criteria_role(array_merge($params, $params_a));
+ $criteria->config_form_display($mform, $role);
+ }
+ } else {
+ $mform->addElement('static', 'noroles', '', get_string('err_noroles', 'completion'));
+ }
+
+ // Activity completion criteria
+ $mform->addElement('header', 'activitiescompleted', get_string('activitiescompleted', 'completion'));
+
+ $activities = $completion->get_activities();
+ if (!empty($activities)) {
+ if (count($activities) > 1) {
+ $mform->addElement('select', 'activity_aggregation', get_string('aggregationmethod', 'completion'), $aggregation_methods);
+ $mform->setDefault('activity_aggregation', $completion->get_aggregation_method(COMPLETION_CRITERIA_TYPE_ACTIVITY));
+ }
+
+ foreach ($activities as $activity) {
+ $params_a = array('moduleinstance' => $activity->id);
+ $criteria = new completion_criteria_activity(array_merge($params, $params_a));
+ $criteria->config_form_display($mform, $activity);
+ }
+ } else {
+ $mform->addElement('static', 'noactivities', '', get_string('err_noactivities', 'completion'));
+ }
+
+ // Completion on date
+ $mform->addElement('header', 'date', get_string('date'));
+ $criteria = new completion_criteria_date($params);
+ $criteria->config_form_display($mform);
+
+ // Completion after enrolment duration
+ $mform->addElement('header', 'duration', get_string('durationafterenrolment', 'completion'));
+ $criteria = new completion_criteria_duration($params);
+ $criteria->config_form_display($mform);
+
+ // Completion on course grade
+ $mform->addElement('header', 'grade', get_string('coursegrade', 'completion'));
+
+ // Grade enable and passing grade
+ $course_grade = $DB->get_field('grade_items', 'gradepass', array('courseid' => $course->id, 'itemtype' => 'course'));
+ if (!$course_grade) {
+ $course_grade = '0.00000';
+ }
+ $criteria = new completion_criteria_grade($params);
+ $criteria->config_form_display($mform, $course_grade);
+
+ // Completion on unenrolment
+ $mform->addElement('header', 'unenrolment', get_string('unenrolment', 'completion'));
+ $criteria = new completion_criteria_unenrol($params);
+ $criteria->config_form_display($mform);
+
+
+//--------------------------------------------------------------------------------
+ $this->add_action_buttons();
+//--------------------------------------------------------------------------------
+ $mform->addElement('hidden', 'id', $course->id);
+ $mform->setType('id', PARAM_INT);
+
+ // If the criteria are locked, freeze values and submit button
+ if ($completion->is_course_locked()) {
+ $except = array('settingsunlock');
+ $mform->hardFreezeAllVisibleExcept($except);
+ $mform->addElement('cancel');
+ }
+ }
+
+
+/// perform some extra moodle validation
+ function validation($data, $files) {
+ global $DB, $CFG;
+
+ $errors = parent::validation($data, $files);
+
+ return $errors;
+ }
+}
+?>
diff --git a/delete.php b/delete.php
new file mode 100644
index 0000000..a47822b
--- /dev/null
+++ b/delete.php
@@ -0,0 +1,87 @@
+dirroot . '/course/lib.php');
+
+ $id = required_param('id', PARAM_INT); // course id
+ $delete = optional_param('delete', '', PARAM_ALPHANUM); // delete confirmation hash
+
+ $PAGE->set_url('/course/delete.php', array('id' => $id));
+ $PAGE->set_context(context_system::instance());
+ require_login();
+
+ $site = get_site();
+
+ $strdeletecourse = get_string("deletecourse");
+ $stradministration = get_string("administration");
+ $strcategories = get_string("categories");
+
+ if (! $course = $DB->get_record("course", array("id"=>$id))) {
+ print_error("invalidcourseid");
+ }
+ if ($site->id == $course->id) {
+ // can not delete frontpage!
+ print_error("invalidcourseid");
+ }
+
+ $coursecontext = context_course::instance($course->id);
+
+ if (!can_delete_course($id)) {
+ print_error('cannotdeletecourse');
+ }
+
+ $category = $DB->get_record("course_categories", array("id"=>$course->category));
+ $courseshortname = format_string($course->shortname, true, array('context' => context_course::instance($course->id)));
+ $categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
+
+ $PAGE->navbar->add($stradministration, new moodle_url('/admin/index.php/'));
+ $PAGE->navbar->add($strcategories, new moodle_url('/course/index.php'));
+ $PAGE->navbar->add($categoryname, new moodle_url('/course/category.php', array('id'=>$course->category)));
+ if (! $delete) {
+ $strdeletecheck = get_string("deletecheck", "", $courseshortname);
+ $strdeletecoursecheck = get_string("deletecoursecheck");
+
+ $PAGE->navbar->add($strdeletecheck);
+ $PAGE->set_title("$site->shortname: $strdeletecheck");
+ $PAGE->set_heading($site->fullname);
+ echo $OUTPUT->header();
+
+ $message = "$strdeletecoursecheck
" . format_string($course->fullname, true, array('context' => $coursecontext)) . " (" . $courseshortname . ")";
+
+ echo $OUTPUT->confirm($message, "delete.php?id=$course->id&delete=".md5($course->timemodified), "category.php?id=$course->category");
+
+ echo $OUTPUT->footer();
+ exit;
+ }
+
+ if ($delete != md5($course->timemodified)) {
+ print_error("invalidmd5");
+ }
+
+ if (!confirm_sesskey()) {
+ print_error('confirmsesskeybad', 'error');
+ }
+
+ // OK checks done, delete the course now.
+
+ add_to_log(SITEID, "course", "delete", "view.php?id=$course->id", "$course->fullname (ID $course->id)");
+
+ $strdeletingcourse = get_string("deletingcourse", "", $courseshortname);
+
+ $PAGE->navbar->add($strdeletingcourse);
+ $PAGE->set_title("$site->shortname: $strdeletingcourse");
+ $PAGE->set_heading($site->fullname);
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading($strdeletingcourse);
+
+ delete_course($course);
+ fix_course_sortorder(); //update course count in catagories
+
+ echo $OUTPUT->heading( get_string("deletedcourse", "", $courseshortname) );
+
+ echo $OUTPUT->continue_button("category.php?id=$course->category");
+
+ echo $OUTPUT->footer();
+
+
diff --git a/delete_category_form.php b/delete_category_form.php
new file mode 100644
index 0000000..8c32405
--- /dev/null
+++ b/delete_category_form.php
@@ -0,0 +1,151 @@
+libdir.'/formslib.php');
+require_once($CFG->libdir.'/questionlib.php');
+
+class delete_category_form extends moodleform {
+
+ var $_category;
+
+ function definition() {
+ global $CFG, $DB;
+
+ $mform =& $this->_form;
+ $category = $this->_customdata;
+ $categorycontext = context_coursecat::instance($category->id);
+ $this->_category = $category;
+
+ /// Check permissions, to see if it OK to give the option to delete
+ /// the contents, rather than move elsewhere.
+ /// Are there any subcategories of this one, can they be deleted?
+ $candeletecontent = true;
+ $tocheck = get_child_categories($category->id);
+ $containscategories = !empty($tocheck);
+ $categoryids = array($category->id);
+ while (!empty($tocheck)) {
+ $checkcat = array_pop($tocheck);
+ $childcategoryids[] = $checkcat->id;
+ $tocheck = $tocheck + get_child_categories($checkcat->id);
+ $chcontext = context_coursecat::instance($checkcat->id);
+ if ($candeletecontent && !has_capability('moodle/category:manage', $chcontext)) {
+ $candeletecontent = false;
+ }
+ }
+
+ /// Are there any courses in here, can they be deleted?
+ list($test, $params) = $DB->get_in_or_equal($categoryids);
+ $containedcourses = $DB->get_records_sql(
+ "SELECT id,1 FROM {course} c WHERE c.category $test", $params);
+ $containscourses = false;
+ if ($containedcourses) {
+ $containscourses = true;
+ foreach ($containedcourses as $courseid => $notused) {
+ if ($candeletecontent && !can_delete_course($courseid)) {
+ $candeletecontent = false;
+ break;
+ }
+ }
+ }
+
+ /// Are there any questions in the question bank here?
+ $containsquestions = question_context_has_any_questions($categorycontext);
+
+ /// Get the list of categories we might be able to move to.
+ $testcaps = array();
+ if ($containscourses) {
+ $testcaps[] = 'moodle/course:create';
+ }
+ if ($containscategories || $containsquestions) {
+ $testcaps[] = 'moodle/category:manage';
+ }
+ $displaylist = array();
+ $notused = array();
+ if (!empty($testcaps)) {
+ make_categories_list($displaylist, $notused, $testcaps, $category->id);
+ }
+
+ /// Now build the options.
+ $options = array();
+ if ($displaylist) {
+ $options[0] = get_string('movecontentstoanothercategory');
+ }
+ if ($candeletecontent) {
+ $options[1] = get_string('deleteallcannotundo');
+ }
+
+ /// Now build the form.
+ $mform->addElement('header','general', get_string('categorycurrentcontents', '', format_string($category->name, true, array('context' => $categorycontext))));
+
+ if ($containscourses || $containscategories || $containsquestions) {
+ if (empty($options)) {
+ print_error('youcannotdeletecategory', 'error', 'index.php', format_string($category->name, true, array('context' => $categorycontext)));
+ }
+
+ /// Describe the contents of this category.
+ $contents = '
';
+ $mform->addElement('static', 'emptymessage', get_string('thiscategorycontains'), $contents);
+
+ /// Give the options for what to do.
+ $mform->addElement('select', 'fulldelete', get_string('whattodo'), $options);
+ if (count($options) == 1) {
+ $optionkeys = array_keys($options);
+ $option = reset($optionkeys);
+ $mform->hardFreeze('fulldelete');
+ $mform->setConstant('fulldelete', $option);
+ }
+
+ if ($displaylist) {
+ $mform->addElement('select', 'newparent', get_string('movecategorycontentto'), $displaylist);
+ if (in_array($category->parent, $displaylist)) {
+ $mform->setDefault('newparent', $category->parent);
+ }
+ $mform->disabledIf('newparent', 'fulldelete', 'eq', '1');
+ }
+ } else {
+ $mform->addElement('hidden', 'fulldelete', 1);
+ $mform->setType('fulldelete', PARAM_INT);
+ $mform->addElement('static', 'emptymessage', '', get_string('deletecategoryempty'));
+ }
+
+ $mform->addElement('hidden', 'delete');
+ $mform->setType('delete', PARAM_ALPHANUM);
+ $mform->addElement('hidden', 'sure');
+ $mform->setType('sure', PARAM_ALPHANUM);
+ $mform->setDefault('sure', md5(serialize($category)));
+
+//--------------------------------------------------------------------------------
+ $this->add_action_buttons(true, get_string('delete'));
+
+ }
+
+/// perform some extra moodle validation
+ function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+
+ if (empty($data['fulldelete']) && empty($data['newparent'])) {
+ /// When they have chosen the move option, they must specify a destination.
+ $errors['newparent'] = get_string('required');
+ }
+
+ if ($data['sure'] != md5(serialize($this->_category))) {
+ $errors['categorylabel'] = get_string('categorymodifiedcancel');
+ }
+
+ return $errors;
+ }
+}
+
diff --git a/dndupload.js b/dndupload.js
new file mode 100644
index 0000000..1ee8395
--- /dev/null
+++ b/dndupload.js
@@ -0,0 +1,982 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * Javascript library for enableing a drag and drop upload to courses
+ *
+ * @package core
+ * @subpackage course
+ * @copyright 2012 Davo Smith
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+M.course_dndupload = {
+ // YUI object.
+ Y: null,
+ // URL for upload requests
+ url: M.cfg.wwwroot + '/course/dndupload.php',
+ // maximum size of files allowed in this form
+ maxbytes: 0,
+ // ID of the course we are on
+ courseid: null,
+ // Data about the different file/data handlers that are available
+ handlers: null,
+ // Nasty hack to distinguish between dragenter(first entry),
+ // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
+ entercount: 0,
+ // Used to keep track of the section we are dragging across - to make
+ // spotting movement between sections more reliable
+ currentsection: null,
+ // Used to store the pending uploads whilst the user is being asked for further input
+ uploadqueue: null,
+ // True if the there is currently a dialog being shown (asking for a name, or giving a
+ // choice of file handlers)
+ uploaddialog: false,
+ // An array containing the last selected file handler for each file type
+ lastselected: null,
+
+ // The following are used to identify specific parts of the course page
+
+ // The type of HTML element that is a course section
+ sectiontypename: 'li',
+ // The classes that an element must have to be identified as a course section
+ sectionclasses: ['section', 'main'],
+ // The ID of the main content area of the page (for adding the 'status' div)
+ pagecontentid: 'page',
+ // The selector identifying the list of modules within a section (note changing this may require
+ // changes to the get_mods_element function)
+ modslistselector: 'ul.section',
+
+ /**
+ * Initalise the drag and drop upload interface
+ * Note: one and only one of options.filemanager and options.formcallback must be defined
+ *
+ * @param Y the YUI object
+ * @param object options {
+ * courseid: ID of the course we are on
+ * maxbytes: maximum size of files allowed in this form
+ * handlers: Data about the different file/data handlers that are available
+ * }
+ */
+ init: function(Y, options) {
+ this.Y = Y;
+
+ if (!this.browser_supported()) {
+ return; // Browser does not support the required functionality
+ }
+
+ this.maxbytes = options.maxbytes;
+ this.courseid = options.courseid;
+ this.handlers = options.handlers;
+ this.uploadqueue = new Array();
+ this.lastselected = new Array();
+
+ var sectionselector = this.sectiontypename + '.' + this.sectionclasses.join('.');
+ var sections = this.Y.all(sectionselector);
+ if (sections.isEmpty()) {
+ return; // No sections - incompatible course format or front page.
+ }
+ sections.each( function(el) {
+ this.add_preview_element(el);
+ this.init_events(el);
+ }, this);
+
+ if (options.showstatus) {
+ this.add_status_div();
+ }
+ },
+
+ /**
+ * Add a div element to tell the user that drag and drop upload
+ * is available (or to explain why it is not available)
+ */
+ add_status_div: function() {
+ var coursecontents = document.getElementById(this.pagecontentid);
+ if (!coursecontents) {
+ return;
+ }
+
+ var div = document.createElement('div');
+ div.id = 'dndupload-status';
+ div.style.opacity = 0.0;
+ coursecontents.insertBefore(div, coursecontents.firstChild);
+
+ var Y = this.Y;
+ div = Y.one(div);
+ var handlefile = (this.handlers.filehandlers.length > 0);
+ var handletext = false;
+ var handlelink = false;
+ var i;
+ for (i=0; i 2) {
+ this.entercount = 2;
+ return false;
+ }
+ }
+
+ this.show_preview_element(section, type);
+
+ return false;
+ },
+
+ /**
+ * Handle a dragleave event: remove the 'add here' message (if present)
+ * @param e event data
+ * @return false to prevent the event from continuing to be processed
+ */
+ drag_leave: function(e) {
+ if (!this.check_drag(e)) {
+ return false;
+ }
+
+ this.entercount--;
+ if (this.entercount == 1) {
+ return false;
+ }
+ this.entercount = 0;
+ this.currentsection = null;
+
+ this.hide_preview_element();
+ return false;
+ },
+
+ /**
+ * Handle a dragover event: just prevent the browser default (necessary
+ * to allow drag and drop handling to work)
+ * @param e event data
+ * @return false to prevent the event from continuing to be processed
+ */
+ drag_over: function(e) {
+ this.check_drag(e);
+ return false;
+ },
+
+ /**
+ * Handle a drop event: hide the 'add here' message, check the attached
+ * data type and start the upload process
+ * @param e event data
+ * @return false to prevent the event from continuing to be processed
+ */
+ drop: function(e) {
+ if (!(type = this.check_drag(e))) {
+ return false;
+ }
+
+ this.hide_preview_element();
+
+ // Work out the number of the section we are on (from its id)
+ var section = this.get_section(e.currentTarget);
+ var sectionnumber = this.get_section_number(section);
+
+ // Process the file or the included data
+ if (type.type == 'Files') {
+ var files = e._event.dataTransfer.files;
+ for (var i=0, f; f=files[i]; i++) {
+ this.handle_file(f, section, sectionnumber);
+ }
+ } else {
+ var contents = e._event.dataTransfer.getData(type.realtype);
+ if (contents) {
+ this.handle_item(type, contents, section, sectionnumber);
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Find or create the 'ul' element that contains all of the module
+ * instances in this section
+ * @param section the DOM element representing the section
+ * @return false to prevent the event from continuing to be processed
+ */
+ get_mods_element: function(section) {
+ // Find the 'ul' containing the list of mods
+ var modsel = section.one(this.modslistselector);
+ if (!modsel) {
+ // Create the above 'ul' if it doesn't exist
+ var modsel = document.createElement('ul');
+ modsel.className = 'section img-text';
+ var contentel = section.get('children').pop();
+ var brel = contentel.get('children').pop();
+ contentel.insertBefore(modsel, brel);
+ modsel = this.Y.one(modsel);
+ }
+
+ return modsel;
+ },
+
+ /**
+ * Add a new dummy item to the list of mods, to be replaced by a real
+ * item & link once the AJAX upload call has completed
+ * @param name the label to show in the element
+ * @param section the DOM element reperesenting the course section
+ * @return DOM element containing the new item
+ */
+ add_resource_element: function(name, section) {
+ var modsel = this.get_mods_element(section);
+
+ var resel = {
+ parent: modsel,
+ li: document.createElement('li'),
+ div: document.createElement('div'),
+ indentdiv: document.createElement('div'),
+ a: document.createElement('a'),
+ icon: document.createElement('img'),
+ namespan: document.createElement('span'),
+ groupingspan: document.createElement('span'),
+ progressouter: document.createElement('span'),
+ progress: document.createElement('span')
+ };
+
+ resel.li.className = 'activity resource modtype_resource';
+
+ resel.indentdiv.className = 'mod-indent';
+ resel.li.appendChild(resel.indentdiv);
+
+ resel.div.className = 'activityinstance';
+ resel.indentdiv.appendChild(resel.div);
+
+ resel.a.href = '#';
+ resel.div.appendChild(resel.a);
+
+ resel.icon.src = M.util.image_url('i/ajaxloader');
+ resel.icon.className = 'activityicon iconlarge';
+ resel.a.appendChild(resel.icon);
+
+ resel.namespan.className = 'instancename';
+ resel.namespan.innerHTML = name;
+ resel.a.appendChild(resel.namespan);
+
+ resel.groupingspan.className = 'groupinglabel';
+ resel.div.appendChild(resel.groupingspan);
+
+ resel.progressouter.className = 'dndupload-progress-outer';
+ resel.progress.className = 'dndupload-progress-inner';
+ resel.progress.innerHTML = ' ';
+ resel.progressouter.appendChild(resel.progress);
+ resel.div.appendChild(resel.progressouter);
+
+ modsel.insertBefore(resel.li, modsel.get('children').pop()); // Leave the 'preview element' at the bottom
+
+ return resel;
+ },
+
+ /**
+ * Hide any visible dndupload-preview elements on the page
+ */
+ hide_preview_element: function() {
+ this.Y.all('li.dndupload-preview').addClass('dndupload-hidden');
+ },
+
+ /**
+ * Unhide the preview element for the given section and set it to display
+ * the correct message
+ * @param section the YUI node representing the selected course section
+ * @param type the details of the data type detected in the drag (including the message to display)
+ */
+ show_preview_element: function(section, type) {
+ this.hide_preview_element();
+ var preview = section.one('li.dndupload-preview').removeClass('dndupload-hidden');
+ preview.one('span').setContent(type.addmessage);
+ },
+
+ /**
+ * Add the preview element to a course section. Note: this needs to be done before 'addEventListener'
+ * is called, otherwise Firefox will ignore events generated when the mouse is over the preview
+ * element (instead of passing them up to the parent element)
+ * @param section the YUI node representing the selected course section
+ */
+ add_preview_element: function(section) {
+ var modsel = this.get_mods_element(section);
+ var preview = {
+ li: document.createElement('li'),
+ div: document.createElement('div'),
+ icon: document.createElement('img'),
+ namespan: document.createElement('span')
+ };
+
+ preview.li.className = 'dndupload-preview dndupload-hidden';
+
+ preview.div.className = 'mod-indent';
+ preview.li.appendChild(preview.div);
+
+ preview.icon.src = M.util.image_url('t/addfile');
+ preview.icon.className = 'icon';
+ preview.div.appendChild(preview.icon);
+
+ preview.div.appendChild(document.createTextNode(' '));
+
+ preview.namespan.className = 'instancename';
+ preview.namespan.innerHTML = M.util.get_string('addfilehere', 'moodle');
+ preview.div.appendChild(preview.namespan);
+
+ modsel.appendChild(preview.li);
+ },
+
+ /**
+ * Find the registered handler for the given file type. If there is more than one, ask the
+ * user which one to use. Then upload the file to the server
+ * @param file the details of the file, taken from the FileList in the drop event
+ * @param section the DOM element representing the selected course section
+ * @param sectionnumber the number of the selected course section
+ */
+ handle_file: function(file, section, sectionnumber) {
+ var handlers = new Array();
+ var filehandlers = this.handlers.filehandlers;
+ var extension = '';
+ var dotpos = file.name.lastIndexOf('.');
+ if (dotpos != -1) {
+ extension = file.name.substr(dotpos+1, file.name.length);
+ }
+
+ for (var i=0; i'+M.util.get_string('actionchoice', 'moodle', file.name)+'
';
+
+ var Y = this.Y;
+ var self = this;
+ var panel = new Y.Panel({
+ bodyContent: content,
+ width: 350,
+ zIndex: 5,
+ centered: true,
+ modal: true,
+ visible: true,
+ render: true,
+ buttons: [{
+ value: M.util.get_string('upload', 'moodle'),
+ action: function(e) {
+ e.preventDefault();
+ // Find out which module was selected
+ var module = false;
+ var div = Y.one('#dndupload_handlers'+uploadid);
+ div.all('input').each(function(input) {
+ if (input.get('checked')) {
+ module = input.get('value');
+ }
+ });
+ if (!module) {
+ return;
+ }
+ panel.hide();
+ // Remember this selection for next time
+ self.lastselected[extension] = module;
+ // Do the upload
+ self.upload_file(file, section, sectionnumber, module);
+ },
+ section: Y.WidgetStdMod.FOOTER
+ },{
+ value: M.util.get_string('cancel', 'moodle'),
+ action: function(e) {
+ e.preventDefault();
+ panel.hide();
+ },
+ section: Y.WidgetStdMod.FOOTER
+ }]
+ });
+ // When the panel is hidden - destroy it and then check for other pending uploads
+ panel.after("visibleChange", function(e) {
+ if (!panel.get('visible')) {
+ panel.destroy(true);
+ self.check_upload_queue();
+ }
+ });
+ },
+
+ /**
+ * Check to see if there are any other dialog boxes to show, now that the current one has
+ * been dealt with
+ */
+ check_upload_queue: function() {
+ this.uploaddialog = false;
+ if (this.uploadqueue.length == 0) {
+ return;
+ }
+
+ var details = this.uploadqueue.shift();
+ if (details.isfile) {
+ this.file_handler_dialog(details.handlers, details.extension, details.file, details.section, details.sectionnumber);
+ } else {
+ this.handle_item(details.type, details.contents, details.section, details.sectionnumber);
+ }
+ },
+
+ /**
+ * Do the file upload: show the dummy element, use an AJAX call to send the data
+ * to the server, update the progress bar for the file, then replace the dummy
+ * element with the real information once the AJAX call completes
+ * @param file the details of the file, taken from the FileList in the drop event
+ * @param section the DOM element representing the selected course section
+ * @param sectionnumber the number of the selected course section
+ */
+ upload_file: function(file, section, sectionnumber, module) {
+
+ // This would be an ideal place to use the Y.io function
+ // however, this does not support data encoded using the
+ // FormData object, which is needed to transfer data from
+ // the DataTransfer object into an XMLHTTPRequest
+ // This can be converted when the YUI issue has been integrated:
+ // http://yuilibrary.com/projects/yui3/ticket/2531274
+ var xhr = new XMLHttpRequest();
+ var self = this;
+
+ if (file.size > this.maxbytes) {
+ alert("'"+file.name+"' "+M.util.get_string('filetoolarge', 'moodle'));
+ return;
+ }
+
+ // Add the file to the display
+ var resel = this.add_resource_element(file.name, section);
+
+ // Update the progress bar as the file is uploaded
+ xhr.upload.addEventListener('progress', function(e) {
+ if (e.lengthComputable) {
+ var percentage = Math.round((e.loaded * 100) / e.total);
+ resel.progress.style.width = percentage + '%';
+ }
+ }, false);
+
+ // Wait for the AJAX call to complete, then update the
+ // dummy element with the returned details
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ var result = JSON.parse(xhr.responseText);
+ if (result) {
+ if (result.error == 0) {
+ // All OK - update the dummy element
+ resel.icon.src = result.icon;
+ resel.a.href = result.link;
+ resel.namespan.innerHTML = result.name;
+ if (!parseInt(result.visible, 10)) {
+ resel.a.className = 'dimmed';
+ }
+
+ if (result.groupingname) {
+ resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+ } else {
+ resel.div.removeChild(resel.groupingspan);
+ }
+
+ resel.div.removeChild(resel.progressouter);
+ resel.li.id = result.elementid;
+ resel.indentdiv.innerHTML += result.commands;
+ if (result.onclick) {
+ resel.a.onclick = result.onclick;
+ }
+ if (self.Y.UA.gecko > 0) {
+ // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+ // log the user out when clicking on the link (before refreshing the page).
+ resel.div.innerHTML = unescape(resel.div.innerHTML);
+ }
+ self.add_editing(result.elementid);
+ } else {
+ // Error - remove the dummy element
+ resel.parent.removeChild(resel.li);
+ alert(result.error);
+ }
+ }
+ } else {
+ alert(M.util.get_string('servererror', 'moodle'));
+ }
+ }
+ };
+
+ // Prepare the data to send
+ var formData = new FormData();
+ formData.append('repo_upload_file', file);
+ formData.append('sesskey', M.cfg.sesskey);
+ formData.append('course', this.courseid);
+ formData.append('section', sectionnumber);
+ formData.append('module', module);
+ formData.append('type', 'Files');
+
+ // Send the AJAX call
+ xhr.open("POST", this.url, true);
+ xhr.send(formData);
+ },
+
+ /**
+ * Show a dialog box to gather the name of the resource / activity to be created
+ * from the uploaded content
+ * @param type the details of the type of content
+ * @param contents the contents to be uploaded
+ * @section the DOM element for the section being uploaded to
+ * @sectionnumber the number of the section being uploaded to
+ */
+ handle_item: function(type, contents, section, sectionnumber) {
+ if (type.handlers.length == 0) {
+ // Nothing to handle this - should not have got here
+ return;
+ }
+
+ if (this.uploaddialog) {
+ var details = new Object();
+ details.isfile = false;
+ details.type = type;
+ details.contents = contents;
+ details.section = section;
+ details.setcionnumber = sectionnumber;
+ this.uploadqueue.push(details);
+ return;
+ }
+ this.uploaddialog = true;
+
+ var timestamp = new Date().getTime();
+ var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
+ var nameid = 'dndupload_handler_name'+uploadid;
+ var content = '';
+ content += '';
+ content += ' ';
+ if (type.handlers.length > 1) {
+ content += '
';
+ var sel = type.handlers[0].module;
+ for (var i=0; i';
+ content += ' ';
+ }
+ content += '
';
+ }
+
+ var Y = this.Y;
+ var self = this;
+ var panel = new Y.Panel({
+ bodyContent: content,
+ width: 350,
+ zIndex: 5,
+ centered: true,
+ modal: true,
+ visible: true,
+ render: true,
+ buttons: [{
+ value: M.util.get_string('upload', 'moodle'),
+ action: function(e) {
+ e.preventDefault();
+ var name = Y.one('#dndupload_handler_name'+uploadid).get('value');
+ name = name.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); // Trim
+ if (name == '') {
+ return;
+ }
+ var module = false;
+ if (type.handlers.length > 1) {
+ // Find out which module was selected
+ var div = Y.one('#dndupload_handlers'+uploadid);
+ div.all('input').each(function(input) {
+ if (input.get('checked')) {
+ module = input.get('value');
+ }
+ });
+ if (!module) {
+ return;
+ }
+ } else {
+ module = type.handlers[0].module;
+ }
+ panel.hide();
+ // Do the upload
+ self.upload_item(name, type.type, contents, section, sectionnumber, module);
+ },
+ section: Y.WidgetStdMod.FOOTER
+ },{
+ value: M.util.get_string('cancel', 'moodle'),
+ action: function(e) {
+ e.preventDefault();
+ panel.hide();
+ },
+ section: Y.WidgetStdMod.FOOTER
+ }]
+ });
+ // When the panel is hidden - destroy it and then check for other pending uploads
+ panel.after("visibleChange", function(e) {
+ if (!panel.get('visible')) {
+ panel.destroy(true);
+ self.check_upload_queue();
+ }
+ });
+ // Focus on the 'name' box
+ Y.one('#'+nameid).focus();
+ },
+
+ /**
+ * Upload any data types that are not files: display a dummy resource element, send
+ * the data to the server, update the progress bar for the file, then replace the
+ * dummy element with the real information once the AJAX call completes
+ * @param name the display name for the resource / activity to create
+ * @param type the details of the data type found in the drop event
+ * @param contents the actual data that was dropped
+ * @param section the DOM element representing the selected course section
+ * @param sectionnumber the number of the selected course section
+ * @param module the module chosen to handle this upload
+ */
+ upload_item: function(name, type, contents, section, sectionnumber, module) {
+
+ // This would be an ideal place to use the Y.io function
+ // however, this does not support data encoded using the
+ // FormData object, which is needed to transfer data from
+ // the DataTransfer object into an XMLHTTPRequest
+ // This can be converted when the YUI issue has been integrated:
+ // http://yuilibrary.com/projects/yui3/ticket/2531274
+ var xhr = new XMLHttpRequest();
+ var self = this;
+
+ // Add the item to the display
+ var resel = this.add_resource_element(name, section);
+
+ // Wait for the AJAX call to complete, then update the
+ // dummy element with the returned details
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ var result = JSON.parse(xhr.responseText);
+ if (result) {
+ if (result.error == 0) {
+ // All OK - update the dummy element
+ resel.icon.src = result.icon;
+ resel.a.href = result.link;
+ resel.namespan.innerHTML = result.name;
+ if (!parseInt(result.visible, 10)) {
+ resel.a.className = 'dimmed';
+ }
+
+ if (result.groupingname) {
+ resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+ } else {
+ resel.div.removeChild(resel.groupingspan);
+ }
+
+ resel.div.removeChild(resel.progressouter);
+ resel.li.id = result.elementid;
+ resel.div.innerHTML += result.commands;
+ if (result.onclick) {
+ resel.a.onclick = result.onclick;
+ }
+ if (self.Y.UA.gecko > 0) {
+ // Fix a Firefox bug which makes sites with a '~' in their wwwroot
+ // log the user out when clicking on the link (before refreshing the page).
+ resel.div.innerHTML = unescape(resel.div.innerHTML);
+ }
+ self.add_editing(result.elementid, sectionnumber);
+ } else {
+ // Error - remove the dummy element
+ resel.parent.removeChild(resel.li);
+ alert(result.error);
+ }
+ }
+ } else {
+ alert(M.util.get_string('servererror', 'moodle'));
+ }
+ }
+ };
+
+ // Prepare the data to send
+ var formData = new FormData();
+ formData.append('contents', contents);
+ formData.append('displayname', name);
+ formData.append('sesskey', M.cfg.sesskey);
+ formData.append('course', this.courseid);
+ formData.append('section', sectionnumber);
+ formData.append('type', type);
+ formData.append('module', module);
+
+ // Send the data
+ xhr.open("POST", this.url, true);
+ xhr.send(formData);
+ },
+
+ /**
+ * Call the AJAX course editing initialisation to add the editing tools
+ * to the newly-created resource link
+ * @param elementid the id of the DOM element containing the new resource link
+ * @param sectionnumber the number of the selected course section
+ */
+ add_editing: function(elementid) {
+ YUI().use('moodle-course-coursebase', function(Y) {
+ M.course.coursebase.invoke_function('setup_for_resource', '#' + elementid);
+ });
+ }
+};
diff --git a/dndupload.php b/dndupload.php
new file mode 100644
index 0000000..c4df5a8
--- /dev/null
+++ b/dndupload.php
@@ -0,0 +1,39 @@
+.
+
+/**
+ * Starting point for drag and drop course uploads
+ *
+ * @package core
+ * @subpackage lib
+ * @copyright 2012 Davo smith
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('AJAX_SCRIPT', true);
+
+require_once(dirname(dirname(__FILE__)).'/config.php');
+require_once($CFG->dirroot.'/course/dnduploadlib.php');
+
+$courseid = required_param('course', PARAM_INT);
+$section = required_param('section', PARAM_INT);
+$type = required_param('type', PARAM_TEXT);
+$modulename = required_param('module', PARAM_PLUGIN);
+$displayname = optional_param('displayname', null, PARAM_TEXT);
+$contents = optional_param('contents', null, PARAM_RAW); // It will be up to each plugin to clean this data, before saving it.
+
+$dndproc = new dndupload_ajax_processor($courseid, $section, $type, $modulename);
+$dndproc->process($displayname, $contents);
diff --git a/dnduploadlib.php b/dnduploadlib.php
new file mode 100644
index 0000000..26e41e8
--- /dev/null
+++ b/dnduploadlib.php
@@ -0,0 +1,696 @@
+.
+
+/**
+ * Library to handle drag and drop course uploads
+ *
+ * @package core
+ * @subpackage lib
+ * @copyright 2012 Davo smith
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/repository/lib.php');
+require_once($CFG->dirroot.'/repository/upload/lib.php');
+require_once($CFG->dirroot.'/course/lib.php');
+
+/**
+ * Add the Javascript to enable drag and drop upload to a course page
+ *
+ * @param object $course The currently displayed course
+ * @param array $modnames The list of enabled (visible) modules on this site
+ * @return void
+ */
+function dndupload_add_to_course($course, $modnames) {
+ global $CFG, $PAGE;
+
+ $showstatus = optional_param('notifyeditingon', false, PARAM_BOOL);
+
+ // Get all handlers.
+ $handler = new dndupload_handler($course, $modnames);
+ $jsdata = $handler->get_js_data();
+ if (empty($jsdata->types) && empty($jsdata->filehandlers)) {
+ return; // No valid handlers - don't enable drag and drop.
+ }
+
+ // Add the javascript to the page.
+ $jsmodule = array(
+ 'name' => 'coursedndupload',
+ 'fullpath' => new moodle_url('/course/dndupload.js'),
+ 'strings' => array(
+ array('addfilehere', 'moodle'),
+ array('dndworkingfiletextlink', 'moodle'),
+ array('dndworkingfilelink', 'moodle'),
+ array('dndworkingfiletext', 'moodle'),
+ array('dndworkingfile', 'moodle'),
+ array('dndworkingtextlink', 'moodle'),
+ array('dndworkingtext', 'moodle'),
+ array('dndworkinglink', 'moodle'),
+ array('filetoolarge', 'moodle'),
+ array('actionchoice', 'moodle'),
+ array('servererror', 'moodle'),
+ array('upload', 'moodle'),
+ array('cancel', 'moodle')
+ ),
+ 'requires' => array('node', 'event', 'panel', 'json', 'anim')
+ );
+ $vars = array(
+ array('courseid' => $course->id,
+ 'maxbytes' => get_max_upload_file_size($CFG->maxbytes, $course->maxbytes),
+ 'handlers' => $handler->get_js_data(),
+ 'showstatus' => $showstatus)
+ );
+
+ $PAGE->requires->js_init_call('M.course_dndupload.init', $vars, true, $jsmodule);
+}
+
+
+/**
+ * Stores all the information about the available dndupload handlers
+ *
+ * @package core
+ * @copyright 2012 Davo Smith
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class dndupload_handler {
+
+ /**
+ * @var array A list of all registered mime types that can be dropped onto a course
+ * along with the modules that will handle them.
+ */
+ protected $types = array();
+
+ /**
+ * @var array A list of the different file types (extensions) that different modules
+ * will handle.
+ */
+ protected $filehandlers = array();
+
+ /**
+ * Gather a list of dndupload handlers from the different mods
+ *
+ * @param object $course The course this is being added to (to check course_allowed_module() )
+ */
+ public function __construct($course, $modnames = null) {
+ global $CFG;
+
+ // Add some default types to handle.
+ // Note: 'Files' type is hard-coded into the Javascript as this needs to be ...
+ // ... treated a little differently.
+ $this->add_type('url', array('url', 'text/uri-list', 'text/x-moz-url'), get_string('addlinkhere', 'moodle'),
+ get_string('nameforlink', 'moodle'), 10);
+ $this->add_type('text/html', array('text/html'), get_string('addpagehere', 'moodle'),
+ get_string('nameforpage', 'moodle'), 20);
+ $this->add_type('text', array('text', 'text/plain'), get_string('addpagehere', 'moodle'),
+ get_string('nameforpage', 'moodle'), 30);
+
+ // Loop through all modules to find handlers.
+ $mods = get_plugin_list_with_function('mod', 'dndupload_register');
+ foreach ($mods as $component => $funcname) {
+ list($modtype, $modname) = normalize_component($component);
+ if ($modnames && !array_key_exists($modname, $modnames)) {
+ continue; // Module is deactivated (hidden) at the site level.
+ }
+ if (!course_allowed_module($course, $modname)) {
+ continue; // User does not have permission to add this module to the course.
+ }
+ $resp = $funcname();
+ if (!$resp) {
+ continue;
+ }
+ if (isset($resp['files'])) {
+ foreach ($resp['files'] as $file) {
+ $this->add_file_handler($file['extension'], $modname, $file['message']);
+ }
+ }
+ if (isset($resp['addtypes'])) {
+ foreach ($resp['addtypes'] as $type) {
+ if (isset($type['priority'])) {
+ $priority = $type['priority'];
+ } else {
+ $priority = 100;
+ }
+ $this->add_type($type['identifier'], $type['datatransfertypes'],
+ $type['addmessage'], $type['namemessage'], $priority);
+ }
+ }
+ if (isset($resp['types'])) {
+ foreach ($resp['types'] as $type) {
+ $this->add_type_handler($type['identifier'], $modname, $type['message']);
+ }
+ }
+ }
+ }
+
+ /**
+ * Used to add a new mime type that can be drag and dropped onto a
+ * course displayed in a browser window
+ *
+ * @param string $identifier The name that this type will be known as
+ * @param array $datatransfertypes An array of the different types in the browser
+ * 'dataTransfer.types' object that will map to this type
+ * @param string $addmessage The message to display in the browser when this type is being
+ * dragged onto the page
+ * @param string $namemessage The message to pop up when asking for the name to give the
+ * course module instance when it is created
+ * @param int $priority Controls the order in which types are checked by the browser (mainly
+ * needed to check for 'text' last as that is usually given as fallback)
+ */
+ public function add_type($identifier, $datatransfertypes, $addmessage, $namemessage, $priority=100) {
+ if ($this->is_known_type($identifier)) {
+ throw new coding_exception("Type $identifier is already registered");
+ }
+
+ $add = new stdClass;
+ $add->identifier = $identifier;
+ $add->datatransfertypes = $datatransfertypes;
+ $add->addmessage = $addmessage;
+ $add->namemessage = $namemessage;
+ $add->priority = $priority;
+ $add->handlers = array();
+
+ $this->types[$identifier] = $add;
+ }
+
+ /**
+ * Used to declare that a particular module will handle a particular type
+ * of dropped data
+ *
+ * @param string $type The name of the type (as declared in add_type)
+ * @param string $module The name of the module to handle this type
+ * @param string $message The message to show the user if more than one handler is registered
+ * for a type and the user needs to make a choice between them
+ */
+ public function add_type_handler($type, $module, $message) {
+ if (!$this->is_known_type($type)) {
+ throw new coding_exception("Trying to add handler for unknown type $type");
+ }
+
+ $add = new stdClass;
+ $add->type = $type;
+ $add->module = $module;
+ $add->message = $message;
+
+ $this->types[$type]->handlers[] = $add;
+ }
+
+ /**
+ * Used to declare that a particular module will handle a particular type
+ * of dropped file
+ *
+ * @param string $extension The file extension to handle ('*' for all types)
+ * @param string $module The name of the module to handle this type
+ * @param string $message The message to show the user if more than one handler is registered
+ * for a type and the user needs to make a choice between them
+ */
+ public function add_file_handler($extension, $module, $message) {
+ $extension = strtolower($extension);
+
+ $add = new stdClass;
+ $add->extension = $extension;
+ $add->module = $module;
+ $add->message = $message;
+
+ $this->filehandlers[] = $add;
+ }
+
+ /**
+ * Check to see if the type has been registered
+ *
+ * @param string $type The identifier of the type you are interested in
+ * @return bool True if the type is registered
+ */
+ public function is_known_type($type) {
+ return array_key_exists($type, $this->types);
+ }
+
+ /**
+ * Check to see if the module in question has registered to handle the
+ * type given
+ *
+ * @param string $module The name of the module
+ * @param string $type The identifier of the type
+ * @return bool True if the module has registered to handle that type
+ */
+ public function has_type_handler($module, $type) {
+ if (!$this->is_known_type($type)) {
+ throw new coding_exception("Checking for handler for unknown type $type");
+ }
+ foreach ($this->types[$type]->handlers as $handler) {
+ if ($handler->module == $module) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check to see if the module in question has registered to handle files
+ * with the given extension (or to handle all file types)
+ *
+ * @param string $module The name of the module
+ * @param string $extension The extension of the uploaded file
+ * @return bool True if the module has registered to handle files with
+ * that extension (or to handle all file types)
+ */
+ public function has_file_handler($module, $extension) {
+ foreach ($this->filehandlers as $handler) {
+ if ($handler->module == $module) {
+ if ($handler->extension == '*' || $handler->extension == $extension) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets a list of the file types that are handled by a particular module
+ *
+ * @param string $module The name of the module to check
+ * @return array of file extensions or string '*'
+ */
+ public function get_handled_file_types($module) {
+ $types = array();
+ foreach ($this->filehandlers as $handler) {
+ if ($handler->module == $module) {
+ if ($handler->extension == '*') {
+ return '*';
+ } else {
+ // Prepending '.' as otherwise mimeinfo fails.
+ $types[] = '.'.$handler->extension;
+ }
+ }
+ }
+ return $types;
+ }
+
+ /**
+ * Returns an object to pass onto the javascript code with data about all the
+ * registered file / type handlers
+ *
+ * @return object Data to pass on to Javascript code
+ */
+ public function get_js_data() {
+ global $CFG;
+
+ $ret = new stdClass;
+
+ // Sort the types by priority.
+ uasort($this->types, array($this, 'type_compare'));
+
+ $ret->types = array();
+ if (!empty($CFG->dndallowtextandlinks)) {
+ foreach ($this->types as $type) {
+ if (empty($type->handlers)) {
+ continue; // Skip any types without registered handlers.
+ }
+ $ret->types[] = $type;
+ }
+ }
+
+ $ret->filehandlers = $this->filehandlers;
+ $uploadrepo = repository::get_instances(array('type' => 'upload'));
+ if (empty($uploadrepo)) {
+ $ret->filehandlers = array(); // No upload repo => no file handlers.
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Comparison function used when sorting types by priority
+ * @param object $type1 first type to compare
+ * @param object $type2 second type to compare
+ * @return integer -1 for $type1 < $type2; 1 for $type1 > $type2; 0 for equal
+ */
+ protected function type_compare($type1, $type2) {
+ if ($type1->priority < $type2->priority) {
+ return -1;
+ }
+ if ($type1->priority > $type2->priority) {
+ return 1;
+ }
+ return 0;
+ }
+
+}
+
+/**
+ * Processes the upload, creating the course module and returning the result
+ *
+ * @package core
+ * @copyright 2012 Davo Smith
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class dndupload_ajax_processor {
+
+ /** Returned when no error has occurred */
+ const ERROR_OK = 0;
+
+ /** @var object The course that we are uploading to */
+ protected $course = null;
+
+ /** @var context_course The course context for capability checking */
+ protected $context = null;
+
+ /** @var int The section number we are uploading to */
+ protected $section = null;
+
+ /** @var string The type of upload (e.g. 'Files', 'text/plain') */
+ protected $type = null;
+
+ /** @var object The details of the module type that will be created */
+ protected $module= null;
+
+ /** @var object The course module that has been created */
+ protected $cm = null;
+
+ /** @var dndupload_handler used to check the allowed file types */
+ protected $dnduploadhandler = null;
+
+ /** @var string The name to give the new activity instance */
+ protected $displayname = null;
+
+ /**
+ * Set up some basic information needed to handle the upload
+ *
+ * @param int $courseid The ID of the course we are uploading to
+ * @param int $section The section number we are uploading to
+ * @param string $type The type of upload (as reported by the browser)
+ * @param string $modulename The name of the module requested to handle this upload
+ */
+ public function __construct($courseid, $section, $type, $modulename) {
+ global $DB;
+
+ if (!defined('AJAX_SCRIPT')) {
+ throw new coding_exception('dndupload_ajax_processor should only be used within AJAX requests');
+ }
+
+ $this->course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+
+ require_login($this->course, false);
+ $this->context = context_course::instance($this->course->id);
+
+ if (!is_number($section) || $section < 0) {
+ throw new coding_exception("Invalid section number $section");
+ }
+ $this->section = $section;
+ $this->type = $type;
+
+ if (!$this->module = $DB->get_record('modules', array('name' => $modulename))) {
+ throw new coding_exception("Module $modulename does not exist");
+ }
+
+ $this->dnduploadhandler = new dndupload_handler($this->course);
+ }
+
+ /**
+ * Check if this upload is a 'file' upload
+ *
+ * @return bool true if it is a 'file' upload, false otherwise
+ */
+ protected function is_file_upload() {
+ return ($this->type == 'Files');
+ }
+
+ /**
+ * Process the upload - creating the module in the course and returning the result to the browser
+ *
+ * @param string $displayname optional the name (from the browser) to give the course module instance
+ * @param string $content optional the content of the upload (for non-file uploads)
+ */
+ public function process($displayname = null, $content = null) {
+ require_capability('moodle/course:manageactivities', $this->context);
+
+ if ($this->is_file_upload()) {
+ require_capability('moodle/course:managefiles', $this->context);
+ if ($content != null) {
+ throw new moodle_exception('fileuploadwithcontent', 'moodle');
+ }
+ } else {
+ if (empty($content)) {
+ throw new moodle_exception('dnduploadwithoutcontent', 'moodle');
+ }
+ }
+
+ require_sesskey();
+
+ $this->displayname = $displayname;
+
+ if ($this->is_file_upload()) {
+ $this->handle_file_upload();
+ } else {
+ $this->handle_other_upload($content);
+ }
+ }
+
+ /**
+ * Handle uploads containing files - create the course module, ask the upload repository
+ * to process the file, ask the mod to set itself up, then return the result to the browser
+ */
+ protected function handle_file_upload() {
+ global $CFG;
+
+ // Add the file to a draft file area.
+ $draftitemid = file_get_unused_draft_itemid();
+ $maxbytes = get_max_upload_file_size($CFG->maxbytes, $this->course->maxbytes);
+ $types = $this->dnduploadhandler->get_handled_file_types($this->module->name);
+ $repo = repository::get_instances(array('type' => 'upload'));
+ if (empty($repo)) {
+ throw new moodle_exception('errornouploadrepo', 'moodle');
+ }
+ $repo = reset($repo); // Get the first (and only) upload repo.
+ $details = $repo->process_upload(null, $maxbytes, $types, '/', $draftitemid);
+ if (empty($this->displayname)) {
+ $this->displayname = $this->display_name_from_file($details['file']);
+ }
+
+ // Create a course module to hold the new instance.
+ $this->create_course_module();
+
+ // Ask the module to set itself up.
+ $moduledata = $this->prepare_module_data($draftitemid);
+ $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
+ if ($instanceid === 'invalidfunction') {
+ throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
+ }
+
+ // Finish setting up the course module.
+ $this->finish_setup_course_module($instanceid);
+ }
+
+ /**
+ * Handle uploads not containing file - create the course module, ask the mod to
+ * set itself up, then return the result to the browser
+ *
+ * @param string $content the content uploaded to the browser
+ */
+ protected function handle_other_upload($content) {
+ // Check this plugin is registered to handle this type of upload
+ if (!$this->dnduploadhandler->has_type_handler($this->module->name, $this->type)) {
+ $info = (object)array('modname' => $this->module->name, 'type' => $this->type);
+ throw new moodle_exception('moddoesnotsupporttype', 'moodle', $info);
+ }
+
+ // Create a course module to hold the new instance.
+ $this->create_course_module();
+
+ // Ask the module to set itself up.
+ $moduledata = $this->prepare_module_data(null, $content);
+ $instanceid = plugin_callback('mod', $this->module->name, 'dndupload', 'handle', array($moduledata), 'invalidfunction');
+ if ($instanceid === 'invalidfunction') {
+ throw new coding_exception("{$this->module->name} does not support drag and drop upload (missing {$this->module->name}_dndupload_handle function");
+ }
+
+ // Finish setting up the course module.
+ $this->finish_setup_course_module($instanceid);
+ }
+
+ /**
+ * Generate the name of the mod instance from the name of the file
+ * (remove the extension and convert underscore => space
+ *
+ * @param string $filename the filename of the uploaded file
+ * @return string the display name to use
+ */
+ protected function display_name_from_file($filename) {
+ $pos = textlib::strrpos($filename, '.');
+ if ($pos) { // Want to skip if $pos === 0 OR $pos === false.
+ $filename = textlib::substr($filename, 0, $pos);
+ }
+ return str_replace('_', ' ', $filename);
+ }
+
+ /**
+ * Create the coursemodule to hold the file/content that has been uploaded
+ */
+ protected function create_course_module() {
+ if (!course_allowed_module($this->course, $this->module->name)) {
+ throw new coding_exception("The module {$this->module->name} is not allowed to be added to this course");
+ }
+
+ $this->cm = new stdClass();
+ $this->cm->course = $this->course->id;
+ $this->cm->section = $this->section;
+ $this->cm->module = $this->module->id;
+ $this->cm->modulename = $this->module->name;
+ $this->cm->instance = 0; // This will be filled in after we create the instance.
+ $this->cm->visible = 1;
+ $this->cm->groupmode = $this->course->groupmode;
+ $this->cm->groupingid = $this->course->defaultgroupingid;
+
+ // Set the correct default for completion tracking.
+ $this->cm->completion = COMPLETION_TRACKING_NONE;
+ $completion = new completion_info($this->course);
+ if ($completion->is_enabled()) {
+ if (plugin_supports('mod', $this->cm->modulename, FEATURE_MODEDIT_DEFAULT_COMPLETION, true)) {
+ $this->cm->completion = COMPLETION_TRACKING_MANUAL;
+ }
+ }
+
+ if (!$this->cm->id = add_course_module($this->cm)) {
+ throw new coding_exception("Unable to create the course module");
+ }
+ // The following are used inside some few core functions, so may as well set them here.
+ $this->cm->coursemodule = $this->cm->id;
+ $groupbuttons = ($this->course->groupmode or (!$this->course->groupmodeforce));
+ if ($groupbuttons and plugin_supports('mod', $this->module->name, FEATURE_GROUPS, 0)) {
+ $this->cm->groupmodelink = (!$this->course->groupmodeforce);
+ } else {
+ $this->cm->groupmodelink = false;
+ $this->cm->groupmode = false;
+ }
+ }
+
+ /**
+ * Gather together all the details to pass on to the mod, so that it can initialise it's
+ * own database tables
+ *
+ * @param int $draftitemid optional the id of the draft area containing the file (for file uploads)
+ * @param string $content optional the content dropped onto the course (for non-file uploads)
+ * @return object data to pass on to the mod, containing:
+ * string $type the 'type' as registered with dndupload_handler (or 'Files')
+ * object $course the course the upload was for
+ * int $draftitemid optional the id of the draft area containing the files
+ * int $coursemodule id of the course module that has already been created
+ * string $displayname the name to use for this activity (can be overriden by the mod)
+ */
+ protected function prepare_module_data($draftitemid = null, $content = null) {
+ $data = new stdClass();
+ $data->type = $this->type;
+ $data->course = $this->course;
+ if ($draftitemid) {
+ $data->draftitemid = $draftitemid;
+ } else if ($content) {
+ $data->content = $content;
+ }
+ $data->coursemodule = $this->cm->id;
+ $data->displayname = $this->displayname;
+ return $data;
+ }
+
+ /**
+ * Called after the mod has set itself up, to finish off any course module settings
+ * (set instance id, add to correct section, set visibility, etc.) and send the response
+ *
+ * @param int $instanceid id returned by the mod when it was created
+ */
+ protected function finish_setup_course_module($instanceid) {
+ global $DB, $USER;
+
+ if (!$instanceid) {
+ // Something has gone wrong - undo everything we can.
+ delete_course_module($this->cm->id);
+ throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
+ }
+
+ // Note the section visibility
+ $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
+
+ $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
+ // Rebuild the course cache after update action
+ rebuild_course_cache($this->course->id, true);
+ $this->course->modinfo = null; // Otherwise we will just get the old version back again.
+
+ $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
+
+ set_coursemodule_visible($this->cm->id, $visible);
+ if (!$visible) {
+ $DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
+ }
+
+ // retrieve the final info about this module.
+ $info = get_fast_modinfo($this->course);
+ if (!isset($info->cms[$this->cm->id])) {
+ // The course module has not been properly created in the course - undo everything.
+ delete_course_module($this->cm->id);
+ throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
+ }
+ $mod = $info->get_cm($this->cm->id);
+ $mod->groupmodelink = $this->cm->groupmodelink;
+ $mod->groupmode = $this->cm->groupmode;
+
+ // Trigger mod_created event with information about this module.
+ $eventdata = new stdClass();
+ $eventdata->modulename = $mod->modname;
+ $eventdata->name = $mod->name;
+ $eventdata->cmid = $mod->id;
+ $eventdata->courseid = $this->course->id;
+ $eventdata->userid = $USER->id;
+ events_trigger('mod_created', $eventdata);
+
+ add_to_log($this->course->id, "course", "add mod",
+ "../mod/{$mod->modname}/view.php?id=$mod->id",
+ "{$mod->modname} $instanceid");
+ add_to_log($this->course->id, $mod->modname, "add",
+ "view.php?id=$mod->id",
+ "$instanceid", $mod->id);
+
+ $this->send_response($mod);
+ }
+
+ /**
+ * Send the details of the newly created activity back to the client browser
+ *
+ * @param cm_info $mod details of the mod just created
+ */
+ protected function send_response($mod) {
+ global $OUTPUT;
+
+ $resp = new stdClass();
+ $resp->error = self::ERROR_OK;
+ $resp->icon = $mod->get_icon_url()->out();
+ $resp->name = $mod->name;
+ $resp->link = $mod->get_url()->out();
+ $resp->elementid = 'module-'.$mod->id;
+ $resp->commands = make_editing_buttons($mod, true, true, 0, $mod->sectionnum);
+ $resp->onclick = $mod->get_on_click();
+ $resp->visible = $mod->visible;
+
+ // if using groupings, then display grouping name
+ if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
+ $groupings = groups_get_all_groupings($this->course->id);
+ $resp->groupingname = format_string($groupings[$mod->groupingid]->name);
+ }
+
+ echo $OUTPUT->header();
+ echo json_encode($resp);
+ die();
+ }
+}
diff --git a/edit.php b/edit.php
new file mode 100644
index 0000000..91f28ed
--- /dev/null
+++ b/edit.php
@@ -0,0 +1,173 @@
+.
+
+/**
+ * Edit course settings
+ *
+ * @package moodlecore
+ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once('lib.php');
+require_once('edit_form.php');
+
+$id = optional_param('id', 0, PARAM_INT); // course id
+$categoryid = optional_param('category', 0, PARAM_INT); // course category - can be changed in edit form
+$returnto = optional_param('returnto', 0, PARAM_ALPHANUM); // generic navigation return page switch
+
+$PAGE->set_pagelayout('admin');
+$pageparams = array('id'=>$id);
+if (empty($id)) {
+ $pageparams = array('category'=>$categoryid);
+}
+$PAGE->set_url('/course/edit.php', $pageparams);
+
+// basic access control checks
+if ($id) { // editing course
+ if ($id == SITEID){
+ // don't allow editing of 'site course' using this from
+ print_error('cannoteditsiteform');
+ }
+
+ $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);
+ require_capability('moodle/course:update', $coursecontext);
+
+} else if ($categoryid) { // creating new course in this category
+ $course = null;
+ require_login();
+ $category = $DB->get_record('course_categories', array('id'=>$categoryid), '*', MUST_EXIST);
+ $catcontext = context_coursecat::instance($category->id);
+ require_capability('moodle/course:create', $catcontext);
+ $PAGE->set_context($catcontext);
+
+} else {
+ require_login();
+ print_error('needcoursecategroyid');
+}
+
+// Prepare course and the editor
+$editoroptions = array('maxfiles' => EDITOR_UNLIMITED_FILES, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>false, 'noclean'=>true);
+if (!empty($course)) {
+ //add context for editor
+ $editoroptions['context'] = $coursecontext;
+ $course = file_prepare_standard_editor($course, 'summary', $editoroptions, $coursecontext, 'course', 'summary', 0);
+
+ // Inject current aliases
+ $aliases = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
+ foreach($aliases as $alias) {
+ $course->{'role_'.$alias->roleid} = $alias->name;
+ }
+
+} else {
+ //editor should respect category context if course context is not set.
+ $editoroptions['context'] = $catcontext;
+ $course = file_prepare_standard_editor($course, 'summary', $editoroptions, null, 'course', 'summary', null);
+}
+
+// first create the form
+$editform = new course_edit_form(NULL, array('course'=>$course, 'category'=>$category, 'editoroptions'=>$editoroptions, 'returnto'=>$returnto));
+if ($editform->is_cancelled()) {
+ switch ($returnto) {
+ case 'category':
+ $url = new moodle_url($CFG->wwwroot.'/course/category.php', array('id'=>$categoryid));
+ break;
+ case 'topcat':
+ $url = new moodle_url($CFG->wwwroot.'/course/');
+ break;
+ default:
+ if (!empty($course->id)) {
+ $url = new moodle_url($CFG->wwwroot.'/course/view.php', array('id'=>$course->id));
+ } else {
+ $url = new moodle_url($CFG->wwwroot.'/course/');
+ }
+ break;
+ }
+ redirect($url);
+
+} else if ($data = $editform->get_data()) {
+ // process data if submitted
+
+ if (empty($course->id)) {
+ // In creating the course
+ $course = create_course($data, $editoroptions);
+
+ // Get the context of the newly created course
+ $context = context_course::instance($course->id, MUST_EXIST);
+
+ if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
+ // deal with course creators - enrol them internally with default role
+ enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
+
+ }
+ if (!is_enrolled($context)) {
+ // Redirect to manual enrolment page if possible
+ $instances = enrol_get_instances($course->id, true);
+ foreach($instances as $instance) {
+ if ($plugin = enrol_get_plugin($instance->enrol)) {
+ if ($plugin->get_manual_enrol_link($instance)) {
+ // we know that the ajax enrol UI will have an option to enrol
+ redirect(new moodle_url('/enrol/users.php', array('id'=>$course->id)));
+ }
+ }
+ }
+ }
+ } else {
+ // Save any changes to the files used in the editor
+ update_course($data, $editoroptions);
+ }
+
+ // Redirect user to newly created/updated course.
+ redirect(new moodle_url('/course/view.php', array('id' => $course->id)));
+}
+
+
+// Print the form
+
+$site = get_site();
+
+$streditcoursesettings = get_string("editcoursesettings");
+$straddnewcourse = get_string("addnewcourse");
+$stradministration = get_string("administration");
+$strcategories = get_string("categories");
+
+if (!empty($course->id)) {
+ $PAGE->navbar->add($streditcoursesettings);
+ $title = $streditcoursesettings;
+ $fullname = $course->fullname;
+} else {
+ $PAGE->navbar->add($stradministration, new moodle_url('/admin/index.php'));
+ $PAGE->navbar->add($strcategories, new moodle_url('/course/index.php'));
+ $PAGE->navbar->add($straddnewcourse);
+ $title = "$site->shortname: $straddnewcourse";
+ $fullname = $site->fullname;
+}
+
+$PAGE->set_title($title);
+$PAGE->set_heading($fullname);
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($streditcoursesettings);
+
+$editform->display();
+
+echo $OUTPUT->footer();
+
diff --git a/edit_form.php b/edit_form.php
new file mode 100644
index 0000000..8f7e875
--- /dev/null
+++ b/edit_form.php
@@ -0,0 +1,358 @@
+libdir.'/formslib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+class course_edit_form extends moodleform {
+ protected $course;
+ protected $context;
+
+ function definition() {
+ 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'];
+ $editoroptions = $this->_customdata['editoroptions'];
+ $returnto = $this->_customdata['returnto'];
+
+ $systemcontext = context_system::instance();
+ $categorycontext = context_coursecat::instance($category->id);
+
+ if (!empty($course->id)) {
+ $coursecontext = context_course::instance($course->id);
+ $context = $coursecontext;
+ } else {
+ $coursecontext = null;
+ $context = $categorycontext;
+ }
+
+ $courseconfig = get_config('moodlecourse');
+
+ $this->course = $course;
+ $this->context = $context;
+
+/// form definition with new course defaults
+//--------------------------------------------------------------------------------
+ $mform->addElement('header','general', get_string('general', 'form'));
+
+ $mform->addElement('hidden', 'returnto', null);
+ $mform->setType('returnto', PARAM_ALPHANUM);
+ $mform->setConstant('returnto', $returnto);
+
+ // verify permissions to change course category or keep current
+ if (empty($course->id)) {
+ if (has_capability('moodle/course:create', $categorycontext)) {
+ $displaylist = array();
+ $parentlist = array();
+ make_categories_list($displaylist, $parentlist, 'moodle/course:create');
+ $mform->addElement('select', 'category', get_string('category'), $displaylist);
+ $mform->addHelpButton('category', 'category');
+ $mform->setDefault('category', $category->id);
+ } else {
+ $mform->addElement('hidden', 'category', null);
+ $mform->setType('category', PARAM_INT);
+ $mform->setConstant('category', $category->id);
+ }
+ } else {
+ if (has_capability('moodle/course:changecategory', $coursecontext)) {
+ $displaylist = array();
+ $parentlist = array();
+ make_categories_list($displaylist, $parentlist, 'moodle/course:create');
+ if (!isset($displaylist[$course->category])) {
+ //always keep current
+ $displaylist[$course->category] = format_string($DB->get_field('course_categories', 'name', array('id'=>$course->category)));
+ }
+ $mform->addElement('select', 'category', get_string('category'), $displaylist);
+ $mform->addHelpButton('category', 'category');
+ } else {
+ //keep current
+ $mform->addElement('hidden', 'category', null);
+ $mform->setType('category', PARAM_INT);
+ $mform->setConstant('category', $course->category);
+ }
+ }
+
+ $mform->addElement('text','fullname', get_string('fullnamecourse'),'maxlength="254" size="50"');
+ $mform->addHelpButton('fullname', 'fullnamecourse');
+ $mform->addRule('fullname', get_string('missingfullname'), 'required', null, 'client');
+ $mform->setType('fullname', PARAM_TEXT);
+ if (!empty($course->id) and !has_capability('moodle/course:changefullname', $coursecontext)) {
+ $mform->hardFreeze('fullname');
+ $mform->setConstant('fullname', $course->fullname);
+ }
+
+ $mform->addElement('text', 'shortname', get_string('shortnamecourse'), 'maxlength="100" size="20"');
+ $mform->addHelpButton('shortname', 'shortnamecourse');
+ $mform->addRule('shortname', get_string('missingshortname'), 'required', null, 'client');
+ $mform->setType('shortname', PARAM_TEXT);
+ if (!empty($course->id) and !has_capability('moodle/course:changeshortname', $coursecontext)) {
+ $mform->hardFreeze('shortname');
+ $mform->setConstant('shortname', $course->shortname);
+ }
+
+ $mform->addElement('text','idnumber', get_string('idnumbercourse'),'maxlength="100" size="10"');
+ $mform->addHelpButton('idnumber', 'idnumbercourse');
+ $mform->setType('idnumber', PARAM_RAW);
+ if (!empty($course->id) and !has_capability('moodle/course:changeidnumber', $coursecontext)) {
+ $mform->hardFreeze('idnumber');
+ $mform->setConstants('idnumber', $course->idnumber);
+ }
+
+
+ $mform->addElement('editor','summary_editor', get_string('coursesummary'), null, $editoroptions);
+ $mform->addHelpButton('summary_editor', 'coursesummary');
+ $mform->setType('summary_editor', PARAM_RAW);
+
+ if (!empty($course->id) and !has_capability('moodle/course:changesummary', $coursecontext)) {
+ $mform->hardFreeze('summary_editor');
+ }
+
+ $courseformats = get_sorted_course_formats(true);
+ $formcourseformats = array();
+ foreach ($courseformats as $courseformat) {
+ $formcourseformats[$courseformat] = get_string('pluginname', "format_$courseformat");
+ }
+ if (isset($course->format)) {
+ $course->format = course_get_format($course)->get_format(); // replace with default if not found
+ if (!in_array($course->format, $courseformats)) {
+ // this format is disabled. Still display it in the dropdown
+ $formcourseformats[$course->format] = get_string('withdisablednote', 'moodle',
+ get_string('pluginname', 'format_'.$course->format));
+ }
+ }
+
+ $mform->addElement('select', 'format', get_string('format'), $formcourseformats);
+ $mform->addHelpButton('format', 'format');
+ $mform->setDefault('format', $courseconfig->format);
+
+ // 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);
+
+ $options = range(0, 10);
+ $mform->addElement('select', 'newsitems', get_string('newsitemsnumber'), $options);
+ $mform->addHelpButton('newsitems', 'newsitemsnumber');
+ $mform->setDefault('newsitems', $courseconfig->newsitems);
+
+ $mform->addElement('selectyesno', 'showgrades', get_string('showgrades'));
+ $mform->addHelpButton('showgrades', 'showgrades');
+ $mform->setDefault('showgrades', $courseconfig->showgrades);
+
+ $mform->addElement('selectyesno', 'showreports', get_string('showreports'));
+ $mform->addHelpButton('showreports', 'showreports');
+ $mform->setDefault('showreports', $courseconfig->showreports);
+
+ // Handle non-existing $course->maxbytes on course creation.
+ $coursemaxbytes = !isset($course->maxbytes) ? null : $course->maxbytes;
+
+ // Let's prepare the maxbytes popup.
+ $choices = get_max_upload_sizes($CFG->maxbytes, 0, 0, $coursemaxbytes);
+ $mform->addElement('select', 'maxbytes', get_string('maximumupload'), $choices);
+ $mform->addHelpButton('maxbytes', 'maximumupload');
+ $mform->setDefault('maxbytes', $courseconfig->maxbytes);
+
+ if (!empty($course->legacyfiles) or !empty($CFG->legacyfilesinnewcourses)) {
+ if (empty($course->legacyfiles)) {
+ //0 or missing means no legacy files ever used in this course - new course or nobody turned on legacy files yet
+ $choices = array('0'=>get_string('no'), '2'=>get_string('yes'));
+ } else {
+ $choices = array('1'=>get_string('no'), '2'=>get_string('yes'));
+ }
+ $mform->addElement('select', 'legacyfiles', get_string('courselegacyfiles'), $choices);
+ $mform->addHelpButton('legacyfiles', 'courselegacyfiles');
+ if (!isset($courseconfig->legacyfiles)) {
+ // in case this was not initialised properly due to switching of $CFG->legacyfilesinnewcourses
+ $courseconfig->legacyfiles = 0;
+ }
+ $mform->setDefault('legacyfiles', $courseconfig->legacyfiles);
+ }
+
+ if (!empty($CFG->allowcoursethemes)) {
+ $themeobjects = get_list_of_themes();
+ $themes=array();
+ $themes[''] = get_string('forceno');
+ foreach ($themeobjects as $key=>$theme) {
+ if (empty($theme->hidefromselector)) {
+ $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
+ }
+ }
+ $mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
+ }
+
+//--------------------------------------------------------------------------------
+ $mform->addElement('hidden', 'addcourseformatoptionshere');
+
+//--------------------------------------------------------------------------------
+ enrol_course_edit_form($mform, $course, $context);
+
+//--------------------------------------------------------------------------------
+ $mform->addElement('header','', get_string('groups', 'group'));
+
+ $choices = array();
+ $choices[NOGROUPS] = get_string('groupsnone', 'group');
+ $choices[SEPARATEGROUPS] = get_string('groupsseparate', 'group');
+ $choices[VISIBLEGROUPS] = get_string('groupsvisible', 'group');
+ $mform->addElement('select', 'groupmode', get_string('groupmode', 'group'), $choices);
+ $mform->addHelpButton('groupmode', 'groupmode', 'group');
+ $mform->setDefault('groupmode', $courseconfig->groupmode);
+
+ $choices = array();
+ $choices['0'] = get_string('no');
+ $choices['1'] = get_string('yes');
+ $mform->addElement('select', 'groupmodeforce', get_string('groupmodeforce', 'group'), $choices);
+ $mform->addHelpButton('groupmodeforce', 'groupmodeforce', 'group');
+ $mform->setDefault('groupmodeforce', $courseconfig->groupmodeforce);
+
+ //default groupings selector
+ $options = array();
+ $options[0] = get_string('none');
+ $mform->addElement('select', 'defaultgroupingid', get_string('defaultgrouping', 'group'), $options);
+
+//--------------------------------------------------------------------------------
+ $mform->addElement('header','', get_string('availability'));
+
+ $choices = array();
+ $choices['0'] = get_string('courseavailablenot');
+ $choices['1'] = get_string('courseavailable');
+ $mform->addElement('select', 'visible', get_string('availability'), $choices);
+ $mform->addHelpButton('visible', 'availability');
+ $mform->setDefault('visible', $courseconfig->visible);
+ if (!has_capability('moodle/course:visibility', $context)) {
+ $mform->hardFreeze('visible');
+ if (!empty($course->id)) {
+ $mform->setConstant('visible', $course->visible);
+ } else {
+ $mform->setConstant('visible', $courseconfig->visible);
+ }
+ }
+
+//--------------------------------------------------------------------------------
+ $mform->addElement('header','', get_string('language'));
+
+ $languages=array();
+ $languages[''] = get_string('forceno');
+ $languages += get_string_manager()->get_list_of_translations();
+ $mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
+ $mform->setDefault('lang', $courseconfig->lang);
+
+//--------------------------------------------------------------------------------
+ if (completion_info::is_enabled_for_site()) {
+ $mform->addElement('header','', get_string('progress','completion'));
+ $mform->addElement('select', 'enablecompletion', get_string('completion','completion'),
+ array(0=>get_string('completiondisabled','completion'), 1=>get_string('completionenabled','completion')));
+ $mform->setDefault('enablecompletion', $courseconfig->enablecompletion);
+
+ $mform->addElement('advcheckbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
+ $mform->setDefault('completionstartonenrol', $courseconfig->completionstartonenrol);
+ $mform->disabledIf('completionstartonenrol', 'enablecompletion', 'eq', 0);
+ } else {
+ $mform->addElement('hidden', 'enablecompletion');
+ $mform->setType('enablecompletion', PARAM_INT);
+ $mform->setDefault('enablecompletion',0);
+
+ $mform->addElement('hidden', 'completionstartonenrol');
+ $mform->setType('completionstartonenrol', PARAM_INT);
+ $mform->setDefault('completionstartonenrol',0);
+ }
+
+/// customizable role names in this course
+//--------------------------------------------------------------------------------
+ $mform->addElement('header','rolerenaming', get_string('rolerenaming'));
+ $mform->addHelpButton('rolerenaming', 'rolerenaming');
+
+ if ($roles = get_all_roles()) {
+ $roles = role_fix_names($roles, null, ROLENAME_ORIGINAL);
+ $assignableroles = get_roles_for_contextlevels(CONTEXT_COURSE);
+ foreach ($roles as $role) {
+ $mform->addElement('text', 'role_'.$role->id, get_string('yourwordforx', '', $role->localname));
+ $mform->setType('role_'.$role->id, PARAM_TEXT);
+ if (!in_array($role->id, $assignableroles)) {
+ $mform->setAdvanced('role_'.$role->id);
+ }
+ }
+ }
+
+//--------------------------------------------------------------------------------
+ $this->add_action_buttons();
+//--------------------------------------------------------------------------------
+ $mform->addElement('hidden', 'id', null);
+ $mform->setType('id', PARAM_INT);
+
+/// finally set the current form data
+//--------------------------------------------------------------------------------
+ $this->set_data($course);
+ }
+
+ function definition_after_data() {
+ global $DB;
+
+ $mform = $this->_form;
+
+ // add available groupings
+ if ($courseid = $mform->getElementValue('id') and $mform->elementExists('defaultgroupingid')) {
+ $options = array();
+ if ($groupings = $DB->get_records('groupings', array('courseid'=>$courseid))) {
+ foreach ($groupings as $grouping) {
+ $options[$grouping->id] = format_string($grouping->name);
+ }
+ }
+ $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) {
+ global $DB, $CFG;
+
+ $errors = parent::validation($data, $files);
+ if ($foundcourses = $DB->get_records('course', array('shortname'=>$data['shortname']))) {
+ if (!empty($data['id'])) {
+ unset($foundcourses[$data['id']]);
+ }
+ if (!empty($foundcourses)) {
+ foreach ($foundcourses as $foundcourse) {
+ $foundcoursenames[] = $foundcourse->fullname;
+ }
+ $foundcoursenamestring = implode(',', $foundcoursenames);
+ $errors['shortname']= get_string('shortnametaken', '', $foundcoursenamestring);
+ }
+ }
+
+ $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/editcategory.php b/editcategory.php
new file mode 100644
index 0000000..fab0f52
--- /dev/null
+++ b/editcategory.php
@@ -0,0 +1,162 @@
+.
+
+/**
+ * Page for creating or editing course category name/parent/description.
+ * When called with an id parameter, edits the category with that id.
+ * Otherwise it creates a new category with default parent from the parent
+ * parameter, which may be 0.
+ *
+ * @package core
+ * @subpackage course
+ * @copyright 2007 Nicolas Connault
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once('lib.php');
+require_once('editcategory_form.php');
+
+require_login();
+
+$id = optional_param('id', 0, PARAM_INT);
+$itemid = 0; //initalise itemid, as all files in category description has item id 0
+
+if ($id) {
+ if (!$category = $DB->get_record('course_categories', array('id' => $id))) {
+ print_error('unknowcategory');
+ }
+ $PAGE->set_url('/course/editcategory.php', array('id' => $id));
+ $categorycontext = context_coursecat::instance($id);
+ $PAGE->set_context($categorycontext);
+ require_capability('moodle/category:manage', $categorycontext);
+ $strtitle = get_string('editcategorysettings');
+ $editorcontext = $categorycontext;
+ $title = $strtitle;
+ $fullname = $category->name;
+} else {
+ $parent = required_param('parent', PARAM_INT);
+ $PAGE->set_url('/course/editcategory.php', array('parent' => $parent));
+ if ($parent) {
+ if (!$DB->record_exists('course_categories', array('id' => $parent))) {
+ print_error('unknowcategory');
+ }
+ $context = context_coursecat::instance($parent);
+ } else {
+ $context = get_system_context();
+ }
+ $PAGE->set_context($context);
+ $category = new stdClass();
+ $category->id = 0;
+ $category->parent = $parent;
+ require_capability('moodle/category:manage', $context);
+ $strtitle = get_string("addnewcategory");
+ $editorcontext = $context;
+ $itemid = null; //set this explicitly, so files for parent category should not get loaded in draft area.
+ $title = "$SITE->shortname: ".get_string('addnewcategory');
+ $fullname = $SITE->fullname;
+}
+
+$PAGE->set_pagelayout('admin');
+
+$editoroptions = array(
+ 'maxfiles' => EDITOR_UNLIMITED_FILES,
+ 'maxbytes' => $CFG->maxbytes,
+ 'trusttext' => true,
+ 'context' => $editorcontext
+);
+$category = file_prepare_standard_editor($category, 'description', $editoroptions, $editorcontext, 'coursecat', 'description', $itemid);
+
+$mform = new editcategory_form('editcategory.php', compact('category', 'editoroptions'));
+$mform->set_data($category);
+
+if ($mform->is_cancelled()) {
+ if ($id) {
+ redirect($CFG->wwwroot . '/course/category.php?id=' . $id . '&categoryedit=on');
+ } else if ($parent) {
+ redirect($CFG->wwwroot .'/course/category.php?id=' . $parent . '&categoryedit=on');
+ } else {
+ redirect($CFG->wwwroot .'/course/index.php?categoryedit=on');
+ }
+} else if ($data = $mform->get_data()) {
+ $newcategory = new stdClass();
+ $newcategory->name = $data->name;
+ $newcategory->idnumber = $data->idnumber;
+ $newcategory->description_editor = $data->description_editor;
+ $newcategory->parent = $data->parent; // if $data->parent = 0, the new category will be a top-level category
+
+ if (isset($data->theme) && !empty($CFG->allowcategorythemes)) {
+ $newcategory->theme = $data->theme;
+ }
+
+ $logaction = 'update';
+ if ($id) {
+ // Update an existing category.
+ $newcategory->id = $category->id;
+ if ($newcategory->parent != $category->parent) {
+ // check category manage capability if parent changed
+ require_capability('moodle/category:manage', get_category_or_system_context((int)$newcategory->parent));
+ $parent_cat = $DB->get_record('course_categories', array('id' => $newcategory->parent));
+ move_category($newcategory, $parent_cat);
+ }
+ } else {
+ // Create a new category.
+ $newcategory->description = $data->description_editor['text'];
+
+ // Don't overwrite the $newcategory object as it'll be processed by file_postupdate_standard_editor in a moment
+ $category = create_course_category($newcategory);
+ $newcategory->id = $category->id;
+ $categorycontext = $category->context;
+ $logaction = 'add';
+ }
+
+ $newcategory = file_postupdate_standard_editor($newcategory, 'description', $editoroptions, $categorycontext, 'coursecat', 'description', 0);
+ $DB->update_record('course_categories', $newcategory);
+ add_to_log(SITEID, "category", $logaction, "editcategory.php?id=$newcategory->id", $newcategory->id);
+ fix_course_sortorder();
+
+ redirect('category.php?id='.$newcategory->id.'&categoryedit=on');
+}
+
+// Unfortunately the navigation never generates correctly for this page because technically this page doesn't actually
+// exist on the navigation; you get here through the course management page.
+// First up we'll try to make the course management page active seeing as that is where the user thinks they are.
+// The big prolem here is that the course management page is a common page for both editing users and common users and
+// is only added to the admin tree if the user has permission to edit at the system level.
+$node = $PAGE->settingsnav->get('root');
+if ($node) {
+ $node = $node->get('courses');
+ if ($node) {
+ $node = $node->get('coursemgmt');
+ }
+}
+if ($node) {
+ // The course management page exists so make that active.
+ $node->make_active();
+} else {
+ // Failing that we'll override the URL, not as accurate and chances are things
+ // won't be 100% correct all the time but should work most times.
+ // A common reason to arrive here is having the management capability within only a particular category (not at system level).
+ navigation_node::override_active_url(new moodle_url('/course/index.php', array('categoryedit' => 'on')));
+}
+
+$PAGE->set_title($title);
+$PAGE->set_heading($fullname);
+echo $OUTPUT->header();
+echo $OUTPUT->heading($strtitle);
+$mform->display();
+echo $OUTPUT->footer();
+
diff --git a/editcategory_form.php b/editcategory_form.php
new file mode 100644
index 0000000..3e3cfe3
--- /dev/null
+++ b/editcategory_form.php
@@ -0,0 +1,74 @@
+dirroot.'/course/moodleform_mod.php');
+class editcategory_form extends moodleform {
+
+ // form definition
+ function definition() {
+ global $CFG, $DB;
+ $mform =& $this->_form;
+ $category = $this->_customdata['category'];
+ $editoroptions = $this->_customdata['editoroptions'];
+
+ // get list of categories to use as parents, with site as the first one
+ $options = array();
+ if (has_capability('moodle/category:manage', get_system_context()) || $category->parent == 0) {
+ $options[0] = get_string('top');
+ }
+ $parents = array();
+ if ($category->id) {
+ // Editing an existing category.
+ make_categories_list($options, $parents, 'moodle/category:manage', $category->id);
+ if (empty($options[$category->parent])) {
+ $options[$category->parent] = $DB->get_field('course_categories', 'name', array('id'=>$category->parent));
+ }
+ $strsubmit = get_string('savechanges');
+ } else {
+ // Making a new category
+ make_categories_list($options, $parents, 'moodle/category:manage');
+ $strsubmit = get_string('createcategory');
+ }
+
+ $mform->addElement('select', 'parent', get_string('parentcategory'), $options);
+ $mform->addElement('text', 'name', get_string('categoryname'), array('size'=>'30'));
+ $mform->addRule('name', get_string('required'), 'required', null);
+ $mform->addElement('text', 'idnumber', get_string('idnumbercoursecategory'),'maxlength="100" size="10"');
+ $mform->addHelpButton('idnumber', 'idnumbercoursecategory');
+ $mform->addElement('editor', 'description_editor', get_string('description'), null, $editoroptions);
+ $mform->setType('description_editor', PARAM_RAW);
+ if (!empty($CFG->allowcategorythemes)) {
+ $themes = array(''=>get_string('forceno'));
+ $allthemes = get_list_of_themes();
+ foreach ($allthemes as $key=>$theme) {
+ if (empty($theme->hidefromselector)) {
+ $themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
+ }
+ }
+ $mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
+ }
+
+ $mform->addElement('hidden', 'id', 0);
+ $mform->setType('id', PARAM_INT);
+ $mform->setDefault('id', $category->id);
+
+ $this->add_action_buttons(true, $strsubmit);
+ }
+
+ function validation($data, $files) {
+ global $DB;
+ $errors = parent::validation($data, $files);
+ if (!empty($data['idnumber'])) {
+ if ($existing = $DB->get_record('course_categories', array('idnumber' => $data['idnumber']))) {
+ if (!$data['id'] || $existing->id != $data['id']) {
+ $errors['idnumber']= get_string('idnumbertaken');
+ }
+ }
+ }
+
+ return $errors;
+ }
+}
+
diff --git a/editsection.php b/editsection.php
new file mode 100644
index 0000000..c75e3dc
--- /dev/null
+++ b/editsection.php
@@ -0,0 +1,91 @@
+.
+
+/**
+ * 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
+ * @package course
+ */
+
+require_once("../config.php");
+require_once("lib.php");
+require_once($CFG->libdir . '/conditionlib.php');
+
+$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);
+$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 ($mform->is_cancelled()){
+ // form cancelled, return to course
+ redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
+} else if ($data = $mform->get_data()) {
+ // 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;
+ }
+ if (!empty($CFG->enableavailability)) {
+ // Update grade and completion conditions
+ $sectioninfo = get_fast_modinfo($course)->get_section_info($sectionnum);
+ condition_info_section::update_section_from_form($sectioninfo, $data);
+ rebuild_course_cache($course->id, true);
+ }
+ course_get_format($course->id)->update_section_format_options($data);
+
+ add_to_log($course->id, "course", "editsection", "editsection.php?id=$id", "$sectionnum");
+ $PAGE->navigation->clear_cache();
+ redirect(course_get_url($course, $section, array('sr' => $sectionreturn)));
+}
+
+// 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");
+
+$PAGE->set_title($stredit);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add($stredit);
+echo $OUTPUT->header();
+
+echo $OUTPUT->heading($strsummaryof);
+
+$mform->display();
+echo $OUTPUT->footer();
diff --git a/editsection_form.php b/editsection_form.php
new file mode 100644
index 0000000..7fdbdf6
--- /dev/null
+++ b/editsection_form.php
@@ -0,0 +1,330 @@
+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() {
+
+ $mform = $this->_form;
+ $course = $this->_customdata['course'];
+ $mform->addElement('checkbox', 'usedefaultname', get_string('sectionusedefaultname'));
+ $mform->setDefault('usedefaultname', true);
+
+ $mform->addElement('text', 'name', get_string('sectionname'), array('size'=>'30'));
+ $mform->setType('name', PARAM_TEXT);
+ $mform->disabledIf('name','usedefaultname','checked');
+
+ /// Prepare course and the editor
+
+ $mform->addElement('editor', 'summary_editor', get_string('summary'), null, $this->_customdata['editoroptions']);
+ $mform->addHelpButton('summary_editor', 'summary');
+ $mform->setType('summary_editor', PARAM_RAW);
+
+ $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');
+ }
+
+ public function definition_after_data() {
+ global $CFG, $DB;
+
+ $mform = $this->_form;
+ $course = $this->_customdata['course'];
+
+ if (!empty($CFG->enableavailability)) {
+ $mform->addElement('header', '', get_string('availabilityconditions', 'condition'));
+ // String used by conditions more than once
+ $strcondnone = get_string('none', 'condition');
+ // Grouping conditions - only if grouping is enabled at site level
+ if (!empty($CFG->enablegroupmembersonly)) {
+ $options = array();
+ $options[0] = get_string('none');
+ if ($groupings = $DB->get_records('groupings', array('courseid' => $course->id))) {
+ foreach ($groupings as $grouping) {
+ $context = context_course::instance($course->id);
+ $options[$grouping->id] = format_string(
+ $grouping->name, true, array('context' => $context));
+ }
+ }
+ $mform->addElement('select', 'groupingid', get_string('groupingsection', 'group'), $options);
+ $mform->addHelpButton('groupingid', 'groupingsection', 'group');
+ }
+
+ // Available from/to defaults to midnight because then the display
+ // will be nicer where it tells users when they can access it (it
+ // shows only the date and not time).
+ $date = usergetdate(time());
+ $midnight = make_timestamp($date['year'], $date['mon'], $date['mday']);
+
+ // Date and time conditions.
+ $mform->addElement('date_time_selector', 'availablefrom',
+ get_string('availablefrom', 'condition'),
+ array('optional' => true, 'defaulttime' => $midnight));
+ $mform->addElement('date_time_selector', 'availableuntil',
+ get_string('availableuntil', 'condition'),
+ array('optional' => true, 'defaulttime' => $midnight));
+
+ // Conditions based on grades
+ $gradeoptions = array();
+ $items = grade_item::fetch_all(array('courseid' => $course->id));
+ $items = $items ? $items : array();
+ foreach ($items as $id => $item) {
+ $gradeoptions[$id] = $item->get_name();
+ }
+ asort($gradeoptions);
+ $gradeoptions = array(0 => $strcondnone) + $gradeoptions;
+
+ $grouparray = array();
+ $grouparray[] = $mform->createElement('select', 'conditiongradeitemid', '', $gradeoptions);
+ $grouparray[] = $mform->createElement('static', '', '',
+ ' ' . get_string('grade_atleast', 'condition').' ');
+ $grouparray[] = $mform->createElement('text', 'conditiongrademin', '', array('size' => 3));
+ $grouparray[] = $mform->createElement('static', '', '',
+ '% ' . get_string('grade_upto', 'condition') . ' ');
+ $grouparray[] = $mform->createElement('text', 'conditiongrademax', '', array('size' => 3));
+ $grouparray[] = $mform->createElement('static', '', '', '%');
+ $group = $mform->createElement('group', 'conditiongradegroup',
+ get_string('gradecondition', 'condition'), $grouparray);
+
+ // Get full version (including condition info) of section object
+ $ci = new condition_info_section($this->_customdata['cs']);
+ $fullcs = $ci->get_full_section();
+ $count = count($fullcs->conditionsgrade) + 1;
+
+ // Grade conditions
+ $this->repeat_elements(array($group), $count, array(), 'conditiongraderepeats',
+ 'conditiongradeadds', 2, get_string('addgrades', 'condition'), true);
+ $mform->addHelpButton('conditiongradegroup[0]', 'gradecondition', 'condition');
+
+ // Conditions based on user fields
+ $operators = condition_info::get_condition_user_field_operators();
+ $useroptions = condition_info::get_condition_user_fields();
+ asort($useroptions);
+
+ $useroptions = array(0 => $strcondnone) + $useroptions;
+ $grouparray = array();
+ $grouparray[] =& $mform->createElement('select', 'conditionfield', '', $useroptions);
+ $grouparray[] =& $mform->createElement('select', 'conditionfieldoperator', '', $operators);
+ $grouparray[] =& $mform->createElement('text', 'conditionfieldvalue');
+ $mform->setType('conditionfieldvalue', PARAM_RAW);
+ $group = $mform->createElement('group', 'conditionfieldgroup', get_string('userfield', 'condition'), $grouparray);
+
+ $fieldcount = count($fullcs->conditionsfield) + 1;
+
+ $this->repeat_elements(array($group), $fieldcount, array(), 'conditionfieldrepeats', 'conditionfieldadds', 2,
+ get_string('adduserfields', 'condition'), true);
+ $mform->addHelpButton('conditionfieldgroup[0]', 'userfield', 'condition');
+
+ // Conditions based on completion
+ $completion = new completion_info($course);
+ if ($completion->is_enabled()) {
+ $completionoptions = array();
+ $modinfo = get_fast_modinfo($course);
+ foreach ($modinfo->cms as $id => $cm) {
+ // Add each course-module if it:
+ // (a) has completion turned on
+ // (b) does not belong to current course-section
+ if ($cm->completion && ($fullcs->id != $cm->section)) {
+ $completionoptions[$id] = $cm->name;
+ }
+ }
+ asort($completionoptions);
+ $completionoptions = array(0 => $strcondnone) +
+ $completionoptions;
+
+ $completionvalues = array(
+ COMPLETION_COMPLETE => get_string('completion_complete', 'condition'),
+ COMPLETION_INCOMPLETE => get_string('completion_incomplete', 'condition'),
+ COMPLETION_COMPLETE_PASS => get_string('completion_pass', 'condition'),
+ COMPLETION_COMPLETE_FAIL => get_string('completion_fail', 'condition'));
+
+ $grouparray = array();
+ $grouparray[] = $mform->createElement('select', 'conditionsourcecmid', '',
+ $completionoptions);
+ $grouparray[] = $mform->createElement('select', 'conditionrequiredcompletion', '',
+ $completionvalues);
+ $group = $mform->createElement('group', 'conditioncompletiongroup',
+ get_string('completioncondition', 'condition'), $grouparray);
+
+ $count = count($fullcs->conditionscompletion) + 1;
+ $this->repeat_elements(array($group), $count, array(),
+ 'conditioncompletionrepeats', 'conditioncompletionadds', 2,
+ get_string('addcompletions', 'condition'), true);
+ $mform->addHelpButton('conditioncompletiongroup[0]',
+ 'completionconditionsection', 'condition');
+ }
+
+ // Availability conditions - set up form values
+ if (!empty($CFG->enableavailability)) {
+ $num = 0;
+ foreach ($fullcs->conditionsgrade as $gradeitemid => $minmax) {
+ $groupelements = $mform->getElement(
+ 'conditiongradegroup[' . $num . ']')->getElements();
+ $groupelements[0]->setValue($gradeitemid);
+ $groupelements[2]->setValue(is_null($minmax->min) ? '' :
+ format_float($minmax->min, 5, true, true));
+ $groupelements[4]->setValue(is_null($minmax->max) ? '' :
+ format_float($minmax->max, 5, true, true));
+ $num++;
+ }
+
+ $num = 0;
+ foreach ($fullcs->conditionsfield as $fieldid => $data) {
+ $groupelements = $mform->getElement(
+ 'conditionfieldgroup[' . $num . ']')->getElements();
+ $groupelements[0]->setValue($fieldid);
+ $groupelements[1]->setValue(is_null($data->operator) ? '' :
+ $data->operator);
+ $groupelements[2]->setValue(is_null($data->value) ? '' :
+ $data->value);
+ $num++;
+ }
+
+ if ($completion->is_enabled()) {
+ $num = 0;
+ foreach ($fullcs->conditionscompletion as $othercmid => $state) {
+ $groupelements = $mform->getElement('conditioncompletiongroup[' . $num . ']')->getElements();
+ $groupelements[0]->setValue($othercmid);
+ $groupelements[1]->setValue($state);
+ $num++;
+ }
+ }
+ }
+
+ // Do we display availability info to students?
+ $showhide = array(
+ CONDITION_STUDENTVIEW_SHOW => get_string('showavailabilitysection_show', 'condition'),
+ CONDITION_STUDENTVIEW_HIDE => get_string('showavailabilitysection_hide', 'condition'));
+ $mform->addElement('select', 'showavailability',
+ get_string('showavailabilitysection', 'condition'), $showhide);
+ }
+
+ $this->add_action_buttons();
+ }
+
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+ // Conditions: Don't let them set dates which make no sense
+ if (array_key_exists('availablefrom', $data) &&
+ $data['availablefrom'] && $data['availableuntil'] &&
+ $data['availablefrom'] >= $data['availableuntil']) {
+ $errors['availablefrom'] = get_string('badavailabledates', 'condition');
+ }
+
+ // Conditions: Verify that the grade conditions are numbers, and make sense.
+ if (array_key_exists('conditiongradegroup', $data)) {
+ foreach ($data['conditiongradegroup'] as $i => $gradedata) {
+ if ($gradedata['conditiongrademin'] !== '' &&
+ !is_numeric(unformat_float($gradedata['conditiongrademin']))) {
+ $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+ continue;
+ }
+ if ($gradedata['conditiongrademax'] !== '' &&
+ !is_numeric(unformat_float($gradedata['conditiongrademax']))) {
+ $errors["conditiongradegroup[{$i}]"] = get_string('gradesmustbenumeric', 'condition');
+ continue;
+ }
+ if ($gradedata['conditiongrademin'] !== '' && $gradedata['conditiongrademax'] !== '' &&
+ unformat_float($gradedata['conditiongrademax']) <= unformat_float($gradedata['conditiongrademin'])) {
+ $errors["conditiongradegroup[{$i}]"] = get_string('badgradelimits', 'condition');
+ continue;
+ }
+ if ($gradedata['conditiongrademin'] === '' && $gradedata['conditiongrademax'] === '' &&
+ $gradedata['conditiongradeitemid']) {
+ $errors["conditiongradegroup[{$i}]"] = get_string('gradeitembutnolimits', 'condition');
+ continue;
+ }
+ if (($gradedata['conditiongrademin'] !== '' || $gradedata['conditiongrademax'] !== '') &&
+ !$gradedata['conditiongradeitemid']) {
+ $errors["conditiongradegroup[{$i}]"] = get_string('gradelimitsbutnoitem', 'condition');
+ continue;
+ }
+ }
+ }
+
+ // Conditions: Verify that the user profile field has not been declared more than once
+ if (array_key_exists('conditionfieldgroup', $data)) {
+ // Array to store the existing fields
+ $arrcurrentfields = array();
+ // Error message displayed if any condition is declared more than once. We use lang string because
+ // this way we don't actually generate the string unless there is an error.
+ $stralreadydeclaredwarning = new lang_string('fielddeclaredmultipletimes', 'condition');
+ foreach ($data['conditionfieldgroup'] as $i => $fielddata) {
+ if ($fielddata['conditionfield'] == 0) { // Don't need to bother if none is selected
+ continue;
+ }
+ if (in_array($fielddata['conditionfield'], $arrcurrentfields)) {
+ $errors["conditionfieldgroup[{$i}]"] = $stralreadydeclaredwarning->out();
+ }
+ // Add the field to the array
+ $arrcurrentfields[] = $fielddata['conditionfield'];
+ }
+ }
+
+ 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/enrol.php b/enrol.php
new file mode 100644
index 0000000..aceb88b
--- /dev/null
+++ b/enrol.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Redirection of old enrol entry point.
+ *
+ * @copyright 1999 Martin Dougiamas http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require('../config.php');
+
+$id = required_param('id', PARAM_INT);
+
+redirect(new moodle_url('/enrol/index.php', array('id'=>$id)));
diff --git a/externallib.php b/externallib.php
new file mode 100644
index 0000000..b5c82f4
--- /dev/null
+++ b/externallib.php
@@ -0,0 +1,1813 @@
+.
+
+
+/**
+ * External course API
+ *
+ * @package core_course
+ * @category external
+ * @copyright 2009 Petr Skodak
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once("$CFG->libdir/externallib.php");
+
+/**
+ * Course external functions
+ *
+ * @package core_course
+ * @category external
+ * @copyright 2011 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.2
+ */
+class core_course_external extends external_api {
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.2
+ */
+ public static function get_course_contents_parameters() {
+ return new external_function_parameters(
+ array('courseid' => new external_value(PARAM_INT, 'course id'),
+ 'options' => new external_multiple_structure (
+ new external_single_structure(
+ array('name' => new external_value(PARAM_ALPHANUM, 'option name'),
+ 'value' => new external_value(PARAM_RAW, 'the value of the option, this param is personaly validated in the external function.')
+ )
+ ), 'Options, not used yet, might be used in later version', VALUE_DEFAULT, array())
+ )
+ );
+ }
+
+ /**
+ * Get course contents
+ *
+ * @param int $courseid course id
+ * @param array $options These options are not used yet, might be used in later version
+ * @return array
+ * @since Moodle 2.2
+ */
+ public static function get_course_contents($courseid, $options = array()) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ //validate parameter
+ $params = self::validate_parameters(self::get_course_contents_parameters(),
+ array('courseid' => $courseid, 'options' => $options));
+
+ //retrieve the course
+ $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
+
+ //check course format exist
+ if (!file_exists($CFG->dirroot . '/course/format/' . $course->format . '/lib.php')) {
+ throw new moodle_exception('cannotgetcoursecontents', 'webservice', '', null, get_string('courseformatnotfound', 'error', '', $course->format));
+ } else {
+ require_once($CFG->dirroot . '/course/format/' . $course->format . '/lib.php');
+ }
+
+ // now security checks
+ $context = context_course::instance($course->id, IGNORE_MISSING);
+ try {
+ self::validate_context($context);
+ } catch (Exception $e) {
+ $exceptionparam = new stdClass();
+ $exceptionparam->message = $e->getMessage();
+ $exceptionparam->courseid = $course->id;
+ throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
+ }
+
+ $canupdatecourse = has_capability('moodle/course:update', $context);
+
+ //create return value
+ $coursecontents = array();
+
+ if ($canupdatecourse or $course->visible
+ or has_capability('moodle/course:viewhiddencourses', $context)) {
+
+ //retrieve sections
+ $modinfo = get_fast_modinfo($course);
+ $sections = $modinfo->get_section_info_all();
+
+ //for each sections (first displayed to last displayed)
+ foreach ($sections as $key => $section) {
+
+ if (!$section->uservisible) {
+ continue;
+ }
+
+ // reset $sectioncontents
+ $sectionvalues = array();
+ $sectionvalues['id'] = $section->id;
+ $sectionvalues['name'] = get_section_name($course, $section);
+ $sectionvalues['visible'] = $section->visible;
+ list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
+ external_format_text($section->summary, $section->summaryformat,
+ $context->id, 'course', 'section', $section->id);
+ $sectioncontents = array();
+
+ //for each module of the section
+ foreach ($modinfo->sections[$section->section] as $cmid) { //matching /course/lib.php:print_section() logic
+ $cm = $modinfo->cms[$cmid];
+
+ // stop here if the module is not visible to the user
+ if (!$cm->uservisible) {
+ continue;
+ }
+
+ $module = array();
+
+ //common info (for people being able to see the module or availability dates)
+ $module['id'] = $cm->id;
+ $module['name'] = format_string($cm->name, true);
+ $module['modname'] = $cm->modname;
+ $module['modplural'] = $cm->modplural;
+ $module['modicon'] = $cm->get_icon_url()->out(false);
+ $module['indent'] = $cm->indent;
+
+ $modcontext = context_module::instance($cm->id);
+
+ if (!empty($cm->showdescription)) {
+ $module['description'] = $cm->get_content();
+ }
+
+ //url of the module
+ $url = $cm->get_url();
+ if ($url) { //labels don't have url
+ $module['url'] = $cm->get_url()->out();
+ }
+
+ $canviewhidden = has_capability('moodle/course:viewhiddenactivities',
+ context_module::instance($cm->id));
+ //user that can view hidden module should know about the visibility
+ $module['visible'] = $cm->visible;
+
+ //availability date (also send to user who can see hidden module when the showavailabilyt is ON)
+ if ($canupdatecourse or ($CFG->enableavailability && $canviewhidden && $cm->showavailability)) {
+ $module['availablefrom'] = $cm->availablefrom;
+ $module['availableuntil'] = $cm->availableuntil;
+ }
+
+ $baseurl = 'webservice/pluginfile.php';
+
+ //call $modulename_export_contents
+ //(each module callback take care about checking the capabilities)
+ require_once($CFG->dirroot . '/mod/' . $cm->modname . '/lib.php');
+ $getcontentfunction = $cm->modname.'_export_contents';
+ if (function_exists($getcontentfunction)) {
+ if ($contents = $getcontentfunction($cm, $baseurl)) {
+ $module['contents'] = $contents;
+ }
+ }
+
+ //assign result to $sectioncontents
+ $sectioncontents[] = $module;
+
+ }
+ $sectionvalues['modules'] = $sectioncontents;
+
+ // assign result to $coursecontents
+ $coursecontents[] = $sectionvalues;
+ }
+ }
+ return $coursecontents;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.2
+ */
+ public static function get_course_contents_returns() {
+ return new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'Section ID'),
+ 'name' => new external_value(PARAM_TEXT, 'Section name'),
+ 'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
+ 'summary' => new external_value(PARAM_RAW, 'Section description'),
+ 'summaryformat' => new external_format_value('summary'),
+ 'modules' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'activity id'),
+ 'url' => new external_value(PARAM_URL, 'activity url', VALUE_OPTIONAL),
+ 'name' => new external_value(PARAM_RAW, 'activity module name'),
+ 'description' => new external_value(PARAM_RAW, 'activity description', VALUE_OPTIONAL),
+ 'visible' => new external_value(PARAM_INT, 'is the module visible', VALUE_OPTIONAL),
+ 'modicon' => new external_value(PARAM_URL, 'activity icon url'),
+ 'modname' => new external_value(PARAM_PLUGIN, 'activity module type'),
+ 'modplural' => new external_value(PARAM_TEXT, 'activity module plural name'),
+ 'availablefrom' => new external_value(PARAM_INT, 'module availability start date', VALUE_OPTIONAL),
+ 'availableuntil' => new external_value(PARAM_INT, 'module availability en date', VALUE_OPTIONAL),
+ 'indent' => new external_value(PARAM_INT, 'number of identation in the site'),
+ 'contents' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ // content info
+ 'type'=> new external_value(PARAM_TEXT, 'a file or a folder or external link'),
+ 'filename'=> new external_value(PARAM_FILE, 'filename'),
+ 'filepath'=> new external_value(PARAM_PATH, 'filepath'),
+ 'filesize'=> new external_value(PARAM_INT, 'filesize'),
+ 'fileurl' => new external_value(PARAM_URL, 'downloadable file url', VALUE_OPTIONAL),
+ 'content' => new external_value(PARAM_RAW, 'Raw content, will be used when type is content', VALUE_OPTIONAL),
+ 'timecreated' => new external_value(PARAM_INT, 'Time created'),
+ 'timemodified' => new external_value(PARAM_INT, 'Time modified'),
+ 'sortorder' => new external_value(PARAM_INT, 'Content sort order'),
+
+ // copyright related info
+ 'userid' => new external_value(PARAM_INT, 'User who added this content to moodle'),
+ 'author' => new external_value(PARAM_TEXT, 'Content owner'),
+ 'license' => new external_value(PARAM_TEXT, 'Content license'),
+ )
+ ), VALUE_DEFAULT, array()
+ )
+ )
+ ), 'list of module'
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function get_courses_parameters() {
+ return new external_function_parameters(
+ array('options' => new external_single_structure(
+ array('ids' => new external_multiple_structure(
+ new external_value(PARAM_INT, 'Course id')
+ , 'List of course id. If empty return all courses
+ except front page course.',
+ VALUE_OPTIONAL)
+ ), 'options - operator OR is used', VALUE_DEFAULT, array())
+ )
+ );
+ }
+
+ /**
+ * Get courses
+ *
+ * @param array $options It contains an array (list of ids)
+ * @return array
+ * @since Moodle 2.2
+ */
+ public static function get_courses($options = array()) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ //validate parameter
+ $params = self::validate_parameters(self::get_courses_parameters(),
+ array('options' => $options));
+
+ //retrieve courses
+ if (!array_key_exists('ids', $params['options'])
+ or empty($params['options']['ids'])) {
+ $courses = $DB->get_records('course');
+ } else {
+ $courses = $DB->get_records_list('course', 'id', $params['options']['ids']);
+ }
+
+ //create return value
+ $coursesinfo = array();
+ foreach ($courses as $course) {
+
+ // 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) {
+ $exceptionparam = new stdClass();
+ $exceptionparam->message = $e->getMessage();
+ $exceptionparam->courseid = $course->id;
+ throw new moodle_exception('errorcoursecontextnotvalid', 'webservice', '', $exceptionparam);
+ }
+ require_capability('moodle/course:view', $context);
+
+ $courseinfo = array();
+ $courseinfo['id'] = $course->id;
+ $courseinfo['fullname'] = $course->fullname;
+ $courseinfo['shortname'] = $course->shortname;
+ $courseinfo['categoryid'] = $course->category;
+ list($courseinfo['summary'], $courseinfo['summaryformat']) =
+ external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
+ $courseinfo['format'] = $course->format;
+ $courseinfo['startdate'] = $course->startdate;
+ 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);
+ if ($courseadmin) {
+ $courseinfo['categorysortorder'] = $course->sortorder;
+ $courseinfo['idnumber'] = $course->idnumber;
+ $courseinfo['showgrades'] = $course->showgrades;
+ $courseinfo['showreports'] = $course->showreports;
+ $courseinfo['newsitems'] = $course->newsitems;
+ $courseinfo['visible'] = $course->visible;
+ $courseinfo['maxbytes'] = $course->maxbytes;
+ 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;
+ $courseinfo['lang'] = $course->lang;
+ $courseinfo['timecreated'] = $course->timecreated;
+ $courseinfo['timemodified'] = $course->timemodified;
+ $courseinfo['forcetheme'] = $course->theme;
+ $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
+ or has_capability('moodle/course:viewhiddencourses', $context)) {
+ $coursesinfo[] = $courseinfo;
+ }
+ }
+
+ return $coursesinfo;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.2
+ */
+ public static function get_courses_returns() {
+ return new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'course id'),
+ 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
+ 'categoryid' => new external_value(PARAM_INT, 'category id'),
+ 'categorysortorder' => new external_value(PARAM_INT,
+ 'sort order into the category', VALUE_OPTIONAL),
+ 'fullname' => new external_value(PARAM_TEXT, 'full name'),
+ 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
+ 'summary' => new external_value(PARAM_RAW, 'summary'),
+ 'summaryformat' => new external_format_value('summary'),
+ 'format' => new external_value(PARAM_PLUGIN,
+ 'course format: weeks, topics, social, site,..'),
+ 'showgrades' => new external_value(PARAM_INT,
+ '1 if grades are shown, otherwise 0', VALUE_OPTIONAL),
+ 'newsitems' => new external_value(PARAM_INT,
+ '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,
+ '(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),
+ 'showreports' => new external_value(PARAM_INT,
+ 'are activity report shown (yes = 1, no =0)', VALUE_OPTIONAL),
+ 'visible' => new external_value(PARAM_INT,
+ '1: available to student, 0:not available', VALUE_OPTIONAL),
+ 'hiddensections' => new external_value(PARAM_INT,
+ '(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),
+ 'groupmodeforce' => new external_value(PARAM_INT, '1: yes, 0: no',
+ VALUE_OPTIONAL),
+ 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
+ VALUE_OPTIONAL),
+ 'timecreated' => new external_value(PARAM_INT,
+ 'timestamp when the course have been created', VALUE_OPTIONAL),
+ 'timemodified' => new external_value(PARAM_INT,
+ 'timestamp when the course have been modified', VALUE_OPTIONAL),
+ 'enablecompletion' => new external_value(PARAM_INT,
+ 'Enabled, control via completion and activity settings. Disbaled,
+ not shown in activity settings.',
+ VALUE_OPTIONAL),
+ 'completionstartonenrol' => new external_value(PARAM_INT,
+ '1: begin tracking a student\'s progress in course completion
+ after course enrolment. 0: does not',
+ VALUE_OPTIONAL),
+ 'completionnotify' => new external_value(PARAM_INT,
+ '1: yes 0: no', VALUE_OPTIONAL),
+ 'lang' => new external_value(PARAM_SAFEDIR,
+ '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'
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.2
+ */
+ public static function create_courses_parameters() {
+ $courseconfig = get_config('moodlecourse'); //needed for many default values
+ return new external_function_parameters(
+ array(
+ 'courses' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'fullname' => new external_value(PARAM_TEXT, 'full name'),
+ 'shortname' => new external_value(PARAM_TEXT, 'course short name'),
+ 'categoryid' => new external_value(PARAM_INT, 'category id'),
+ 'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
+ 'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
+ 'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
+ 'format' => new external_value(PARAM_PLUGIN,
+ 'course format: weeks, topics, social, site,..',
+ VALUE_DEFAULT, $courseconfig->format),
+ 'showgrades' => new external_value(PARAM_INT,
+ '1 if grades are shown, otherwise 0', VALUE_DEFAULT,
+ $courseconfig->showgrades),
+ 'newsitems' => new external_value(PARAM_INT,
+ 'number of recent items appearing on the course page',
+ VALUE_DEFAULT, $courseconfig->newsitems),
+ 'startdate' => new external_value(PARAM_INT,
+ 'timestamp when the course start', VALUE_OPTIONAL),
+ '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),
+ 'showreports' => new external_value(PARAM_INT,
+ 'are activity report shown (yes = 1, no =0)', VALUE_DEFAULT,
+ $courseconfig->showreports),
+ 'visible' => new external_value(PARAM_INT,
+ '1: available to student, 0:not available', VALUE_OPTIONAL),
+ 'hiddensections' => new external_value(PARAM_INT,
+ '(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',
+ VALUE_DEFAULT, $courseconfig->groupmodeforce),
+ 'defaultgroupingid' => new external_value(PARAM_INT, 'default grouping id',
+ VALUE_DEFAULT, 0),
+ 'enablecompletion' => new external_value(PARAM_INT,
+ 'Enabled, control via completion and activity settings. Disabled,
+ not shown in activity settings.',
+ VALUE_OPTIONAL),
+ 'completionstartonenrol' => new external_value(PARAM_INT,
+ '1: begin tracking a student\'s progress in course completion after
+ course enrolment. 0: does not',
+ VALUE_OPTIONAL),
+ 'completionnotify' => new external_value(PARAM_INT,
+ '1: yes 0: no', VALUE_OPTIONAL),
+ 'lang' => new external_value(PARAM_SAFEDIR,
+ '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'
+ )
+ )
+ );
+ }
+
+ /**
+ * Create courses
+ *
+ * @param array $courses
+ * @return array courses (id and shortname only)
+ * @since Moodle 2.2
+ */
+ public static function create_courses($courses) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+ require_once($CFG->libdir . '/completionlib.php');
+
+ $params = self::validate_parameters(self::create_courses_parameters(),
+ array('courses' => $courses));
+
+ $availablethemes = get_plugin_list('theme');
+ $availablelangs = get_string_manager()->get_list_of_translations();
+
+ $transaction = $DB->start_delegated_transaction();
+
+ foreach ($params['courses'] as $course) {
+
+ // Ensure the current user is allowed to run this function
+ $context = context_coursecat::instance($course['categoryid'], IGNORE_MISSING);
+ try {
+ self::validate_context($context);
+ } catch (Exception $e) {
+ $exceptionparam = new stdClass();
+ $exceptionparam->message = $e->getMessage();
+ $exceptionparam->catid = $course['categoryid'];
+ throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
+ }
+ require_capability('moodle/course:create', $context);
+
+ // Make sure lang is valid
+ if (array_key_exists('lang', $course) and empty($availablelangs[$course['lang']])) {
+ throw new moodle_exception('errorinvalidparam', 'webservice', '', 'lang');
+ }
+
+ // Make sure theme is valid
+ if (array_key_exists('forcetheme', $course)) {
+ if (!empty($CFG->allowcoursethemes)) {
+ if (empty($availablethemes[$course['forcetheme']])) {
+ throw new moodle_exception('errorinvalidparam', 'webservice', '', 'forcetheme');
+ } else {
+ $course['theme'] = $course['forcetheme'];
+ }
+ }
+ }
+
+ //force visibility if ws user doesn't have the permission to set it
+ $category = $DB->get_record('course_categories', array('id' => $course['categoryid']));
+ if (!has_capability('moodle/course:visibility', $context)) {
+ $course['visible'] = $category->visible;
+ }
+
+ //set default value for completion
+ $courseconfig = get_config('moodlecourse');
+ if (completion_info::is_enabled_for_site()) {
+ if (!array_key_exists('enablecompletion', $course)) {
+ $course['enablecompletion'] = $courseconfig->enablecompletion;
+ }
+ if (!array_key_exists('completionstartonenrol', $course)) {
+ $course['completionstartonenrol'] = $courseconfig->completionstartonenrol;
+ }
+ } else {
+ $course['enablecompletion'] = 0;
+ $course['completionstartonenrol'] = 0;
+ }
+
+ $course['category'] = $course['categoryid'];
+
+ // 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;
+
+ $resultcourses[] = array('id' => $course['id'], 'shortname' => $course['shortname']);
+ }
+
+ $transaction->allow_commit();
+
+ return $resultcourses;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.2
+ */
+ public static function create_courses_returns() {
+ return new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'course id'),
+ 'shortname' => new external_value(PARAM_TEXT, 'short name'),
+ )
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.2
+ */
+ public static function delete_courses_parameters() {
+ return new external_function_parameters(
+ array(
+ 'courseids' => new external_multiple_structure(new external_value(PARAM_INT, 'course ID')),
+ )
+ );
+ }
+
+ /**
+ * Delete courses
+ *
+ * @param array $courseids A list of course ids
+ * @since Moodle 2.2
+ */
+ public static function delete_courses($courseids) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot."/course/lib.php");
+
+ // Parameter validation.
+ $params = self::validate_parameters(self::delete_courses_parameters(), array('courseids'=>$courseids));
+
+ $transaction = $DB->start_delegated_transaction();
+
+ foreach ($params['courseids'] as $courseid) {
+ $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
+
+ // Check if the context is valid.
+ $coursecontext = context_course::instance($course->id);
+ self::validate_context($coursecontext);
+
+ // Check if the current user has enought permissions.
+ if (!can_delete_course($courseid)) {
+ throw new moodle_exception('cannotdeletecategorycourse', 'error',
+ '', format_string($course->fullname)." (id: $courseid)");
+ }
+
+ delete_course($course, false);
+ }
+
+ $transaction->allow_commit();
+
+ return null;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.2
+ */
+ public static function delete_courses_returns() {
+ return null;
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function duplicate_course_parameters() {
+ return new external_function_parameters(
+ array(
+ 'courseid' => new external_value(PARAM_INT, 'course to duplicate id'),
+ 'fullname' => new external_value(PARAM_TEXT, 'duplicated course full name'),
+ 'shortname' => new external_value(PARAM_TEXT, 'duplicated course short name'),
+ 'categoryid' => new external_value(PARAM_INT, 'duplicated course category parent'),
+ 'visible' => new external_value(PARAM_INT, 'duplicated course visible, default to yes', VALUE_DEFAULT, 1),
+ 'options' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
+ "activities" (int) Include course activites (default to 1 that is equal to yes),
+ "blocks" (int) Include course blocks (default to 1 that is equal to yes),
+ "filters" (int) Include course filters (default to 1 that is equal to yes),
+ "users" (int) Include users (default to 0 that is equal to no),
+ "role_assignments" (int) Include role assignments (default to 0 that is equal to no),
+ "comments" (int) Include user comments (default to 0 that is equal to no),
+ "completion_information" (int) Include user course completion information (default to 0 that is equal to no),
+ "logs" (int) Include course logs (default to 0 that is equal to no),
+ "histories" (int) Include histories (default to 0 that is equal to no)'
+ ),
+ 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
+ )
+ )
+ ), VALUE_DEFAULT, array()
+ ),
+ )
+ );
+ }
+
+ /**
+ * Duplicate a course
+ *
+ * @param int $courseid
+ * @param string $fullname Duplicated course fullname
+ * @param string $shortname Duplicated course shortname
+ * @param int $categoryid Duplicated course parent category id
+ * @param int $visible Duplicated course availability
+ * @param array $options List of backup options
+ * @return array New course info
+ * @since Moodle 2.3
+ */
+ public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
+ global $CFG, $USER, $DB;
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+ // Parameter validation.
+ $params = self::validate_parameters(
+ self::duplicate_course_parameters(),
+ array(
+ 'courseid' => $courseid,
+ 'fullname' => $fullname,
+ 'shortname' => $shortname,
+ 'categoryid' => $categoryid,
+ 'visible' => $visible,
+ 'options' => $options
+ )
+ );
+
+ // Context validation.
+
+ if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
+ throw new moodle_exception('invalidcourseid', 'error');
+ }
+
+ // Category where duplicated course is going to be created.
+ $categorycontext = context_coursecat::instance($params['categoryid']);
+ self::validate_context($categorycontext);
+
+ // Course to be duplicated.
+ $coursecontext = context_course::instance($course->id);
+ self::validate_context($coursecontext);
+
+ $backupdefaults = array(
+ 'activities' => 1,
+ 'blocks' => 1,
+ 'filters' => 1,
+ 'users' => 0,
+ 'role_assignments' => 0,
+ 'comments' => 0,
+ 'completion_information' => 0,
+ 'logs' => 0,
+ 'histories' => 0
+ );
+
+ $backupsettings = array();
+ // Check for backup and restore options.
+ if (!empty($params['options'])) {
+ foreach ($params['options'] as $option) {
+
+ // Strict check for a correct value (allways 1 or 0, true or false).
+ $value = clean_param($option['value'], PARAM_INT);
+
+ if ($value !== 0 and $value !== 1) {
+ throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+ }
+
+ if (!isset($backupdefaults[$option['name']])) {
+ throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+ }
+
+ $backupsettings[$option['name']] = $value;
+ }
+ }
+
+ // Capability checking.
+
+ // The backup controller check for this currently, this may be redundant.
+ require_capability('moodle/course:create', $categorycontext);
+ require_capability('moodle/restore:restorecourse', $categorycontext);
+ require_capability('moodle/backup:backupcourse', $coursecontext);
+
+ if (!empty($backupsettings['users'])) {
+ require_capability('moodle/backup:userinfo', $coursecontext);
+ require_capability('moodle/restore:userinfo', $categorycontext);
+ }
+
+ // Check if the shortname is used.
+ if ($foundcourses = $DB->get_records('course', array('shortname'=>$shortname))) {
+ foreach ($foundcourses as $foundcourse) {
+ $foundcoursenames[] = $foundcourse->fullname;
+ }
+
+ $foundcoursenamestring = implode(',', $foundcoursenames);
+ throw new moodle_exception('shortnametaken', '', '', $foundcoursenamestring);
+ }
+
+ // Backup the course.
+
+ $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
+ backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id);
+
+ foreach ($backupsettings as $name => $value) {
+ $bc->get_plan()->get_setting($name)->set_value($value);
+ }
+
+ $backupid = $bc->get_backupid();
+ $backupbasepath = $bc->get_plan()->get_basepath();
+
+ $bc->execute_plan();
+ $results = $bc->get_results();
+ $file = $results['backup_destination'];
+
+ $bc->destroy();
+
+ // Restore the backup immediately.
+
+ // Check if we need to unzip the file because the backup temp dir does not contains backup files.
+ if (!file_exists($backupbasepath . "/moodle_backup.xml")) {
+ $file->extract_to_pathname(get_file_packer(), $backupbasepath);
+ }
+
+ // Create new course.
+ $newcourseid = restore_dbops::create_new_course($params['fullname'], $params['shortname'], $params['categoryid']);
+
+ $rc = new restore_controller($backupid, $newcourseid,
+ backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_NEW_COURSE);
+
+ foreach ($backupsettings as $name => $value) {
+ $setting = $rc->get_plan()->get_setting($name);
+ if ($setting->get_status() == backup_setting::NOT_LOCKED) {
+ $setting->set_value($value);
+ }
+ }
+
+ if (!$rc->execute_precheck()) {
+ $precheckresults = $rc->get_precheck_results();
+ if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($backupbasepath);
+ }
+
+ $errorinfo = '';
+
+ foreach ($precheckresults['errors'] as $error) {
+ $errorinfo .= $error;
+ }
+
+ if (array_key_exists('warnings', $precheckresults)) {
+ foreach ($precheckresults['warnings'] as $warning) {
+ $errorinfo .= $warning;
+ }
+ }
+
+ throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
+ }
+ }
+
+ $rc->execute_plan();
+ $rc->destroy();
+
+ $course = $DB->get_record('course', array('id' => $newcourseid), '*', MUST_EXIST);
+ $course->fullname = $params['fullname'];
+ $course->shortname = $params['shortname'];
+ $course->visible = $params['visible'];
+
+ // Set shortname and fullname back.
+ $DB->update_record('course', $course);
+
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($backupbasepath);
+ }
+
+ // Delete the course backup file created by this WebService. Originally located in the course backups area.
+ $file->delete();
+
+ return array('id' => $course->id, 'shortname' => $course->shortname);
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.3
+ */
+ public static function duplicate_course_returns() {
+ return new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'course id'),
+ 'shortname' => new external_value(PARAM_TEXT, 'short name'),
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters for import_course
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.4
+ */
+ public static function import_course_parameters() {
+ return new external_function_parameters(
+ array(
+ 'importfrom' => new external_value(PARAM_INT, 'the id of the course we are importing from'),
+ 'importto' => new external_value(PARAM_INT, 'the id of the course we are importing to'),
+ 'deletecontent' => new external_value(PARAM_INT, 'whether to delete the course content where we are importing to (default to 0 = No)', VALUE_DEFAULT, 0),
+ 'options' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_ALPHA, 'The backup option name:
+ "activities" (int) Include course activites (default to 1 that is equal to yes),
+ "blocks" (int) Include course blocks (default to 1 that is equal to yes),
+ "filters" (int) Include course filters (default to 1 that is equal to yes)'
+ ),
+ 'value' => new external_value(PARAM_RAW, 'the value for the option 1 (yes) or 0 (no)'
+ )
+ )
+ ), VALUE_DEFAULT, array()
+ ),
+ )
+ );
+ }
+
+ /**
+ * Imports a course
+ *
+ * @param int $importfrom The id of the course we are importing from
+ * @param int $importto The id of the course we are importing to
+ * @param bool $deletecontent Whether to delete the course we are importing to content
+ * @param array $options List of backup options
+ * @return null
+ * @since Moodle 2.4
+ */
+ public static function import_course($importfrom, $importto, $deletecontent = 0, $options = array()) {
+ global $CFG, $USER, $DB;
+ require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+ require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+ // Parameter validation.
+ $params = self::validate_parameters(
+ self::import_course_parameters(),
+ array(
+ 'importfrom' => $importfrom,
+ 'importto' => $importto,
+ 'deletecontent' => $deletecontent,
+ 'options' => $options
+ )
+ );
+
+ if ($params['deletecontent'] !== 0 and $params['deletecontent'] !== 1) {
+ throw new moodle_exception('invalidextparam', 'webservice', '', $option['deletecontent']);
+ }
+
+ // Context validation.
+
+ if (! ($importfrom = $DB->get_record('course', array('id'=>$params['importfrom'])))) {
+ throw new moodle_exception('invalidcourseid', 'error');
+ }
+
+ if (! ($importto = $DB->get_record('course', array('id'=>$params['importto'])))) {
+ throw new moodle_exception('invalidcourseid', 'error');
+ }
+
+ $importfromcontext = context_course::instance($importfrom->id);
+ self::validate_context($importfromcontext);
+
+ $importtocontext = context_course::instance($importto->id);
+ self::validate_context($importtocontext);
+
+ $backupdefaults = array(
+ 'activities' => 1,
+ 'blocks' => 1,
+ 'filters' => 1
+ );
+
+ $backupsettings = array();
+
+ // Check for backup and restore options.
+ if (!empty($params['options'])) {
+ foreach ($params['options'] as $option) {
+
+ // Strict check for a correct value (allways 1 or 0, true or false).
+ $value = clean_param($option['value'], PARAM_INT);
+
+ if ($value !== 0 and $value !== 1) {
+ throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+ }
+
+ if (!isset($backupdefaults[$option['name']])) {
+ throw new moodle_exception('invalidextparam', 'webservice', '', $option['name']);
+ }
+
+ $backupsettings[$option['name']] = $value;
+ }
+ }
+
+ // Capability checking.
+
+ require_capability('moodle/backup:backuptargetimport', $importfromcontext);
+ require_capability('moodle/restore:restoretargetimport', $importtocontext);
+
+ $bc = new backup_controller(backup::TYPE_1COURSE, $importfrom->id, backup::FORMAT_MOODLE,
+ backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
+
+ foreach ($backupsettings as $name => $value) {
+ $bc->get_plan()->get_setting($name)->set_value($value);
+ }
+
+ $backupid = $bc->get_backupid();
+ $backupbasepath = $bc->get_plan()->get_basepath();
+
+ $bc->execute_plan();
+ $bc->destroy();
+
+ // Restore the backup immediately.
+
+ // Check if we must delete the contents of the destination course.
+ if ($params['deletecontent']) {
+ $restoretarget = backup::TARGET_EXISTING_DELETING;
+ } else {
+ $restoretarget = backup::TARGET_EXISTING_ADDING;
+ }
+
+ $rc = new restore_controller($backupid, $importto->id,
+ backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, $restoretarget);
+
+ foreach ($backupsettings as $name => $value) {
+ $rc->get_plan()->get_setting($name)->set_value($value);
+ }
+
+ if (!$rc->execute_precheck()) {
+ $precheckresults = $rc->get_precheck_results();
+ if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($backupbasepath);
+ }
+
+ $errorinfo = '';
+
+ foreach ($precheckresults['errors'] as $error) {
+ $errorinfo .= $error;
+ }
+
+ if (array_key_exists('warnings', $precheckresults)) {
+ foreach ($precheckresults['warnings'] as $warning) {
+ $errorinfo .= $warning;
+ }
+ }
+
+ throw new moodle_exception('backupprecheckerrors', 'webservice', '', $errorinfo);
+ }
+ } else {
+ if ($restoretarget == backup::TARGET_EXISTING_DELETING) {
+ restore_dbops::delete_course_content($importto->id);
+ }
+ }
+
+ $rc->execute_plan();
+ $rc->destroy();
+
+ if (empty($CFG->keeptempdirectoriesonbackup)) {
+ fulldelete($backupbasepath);
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.4
+ */
+ public static function import_course_returns() {
+ return null;
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function get_categories_parameters() {
+ return new external_function_parameters(
+ array(
+ 'criteria' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'key' => new external_value(PARAM_ALPHA,
+ 'The category column to search, expected keys (value format) are:'.
+ '"id" (int) the category id,'.
+ '"name" (string) the category name,'.
+ '"parent" (int) the parent category id,'.
+ '"idnumber" (string) category idnumber'.
+ ' - user must have \'moodle/category:manage\' to search on idnumber,'.
+ '"visible" (int) whether the returned categories must be visible or hidden. If the key is not passed,
+ then the function return all categories that the user can see.'.
+ ' - user must have \'moodle/category:manage\' or \'moodle/category:viewhiddencategories\' to search on visible,'.
+ '"theme" (string) only return the categories having this theme'.
+ ' - user must have \'moodle/category:manage\' to search on theme'),
+ 'value' => new external_value(PARAM_RAW, 'the value to match')
+ )
+ ), 'criteria', VALUE_DEFAULT, array()
+ ),
+ 'addsubcategories' => new external_value(PARAM_BOOL, 'return the sub categories infos
+ (1 - default) otherwise only the category info (0)', VALUE_DEFAULT, 1)
+ )
+ );
+ }
+
+ /**
+ * Get categories
+ *
+ * @param array $criteria Criteria to match the results
+ * @param booln $addsubcategories obtain only the category (false) or its subcategories (true - default)
+ * @return array list of categories
+ * @since Moodle 2.3
+ */
+ public static function get_categories($criteria = array(), $addsubcategories = true) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ // Validate parameters.
+ $params = self::validate_parameters(self::get_categories_parameters(),
+ array('criteria' => $criteria, 'addsubcategories' => $addsubcategories));
+
+ // Retrieve the categories.
+ $categories = array();
+ if (!empty($params['criteria'])) {
+
+ $conditions = array();
+ $wheres = array();
+ foreach ($params['criteria'] as $crit) {
+ $key = trim($crit['key']);
+
+ // Trying to avoid duplicate keys.
+ if (!isset($conditions[$key])) {
+
+ $context = context_system::instance();
+ $value = null;
+ switch ($key) {
+ case 'id':
+ $value = clean_param($crit['value'], PARAM_INT);
+ break;
+
+ case 'idnumber':
+ if (has_capability('moodle/category:manage', $context)) {
+ $value = clean_param($crit['value'], PARAM_RAW);
+ } else {
+ // We must throw an exception.
+ // Otherwise the dev client would think no idnumber exists.
+ throw new moodle_exception('criteriaerror',
+ 'webservice', '', null,
+ 'You don\'t have the permissions to search on the "idnumber" field.');
+ }
+ break;
+
+ case 'name':
+ $value = clean_param($crit['value'], PARAM_TEXT);
+ break;
+
+ case 'parent':
+ $value = clean_param($crit['value'], PARAM_INT);
+ break;
+
+ case 'visible':
+ if (has_capability('moodle/category:manage', $context)
+ or has_capability('moodle/category:viewhiddencategories',
+ context_system::instance())) {
+ $value = clean_param($crit['value'], PARAM_INT);
+ } else {
+ throw new moodle_exception('criteriaerror',
+ 'webservice', '', null,
+ 'You don\'t have the permissions to search on the "visible" field.');
+ }
+ break;
+
+ case 'theme':
+ if (has_capability('moodle/category:manage', $context)) {
+ $value = clean_param($crit['value'], PARAM_THEME);
+ } else {
+ throw new moodle_exception('criteriaerror',
+ 'webservice', '', null,
+ 'You don\'t have the permissions to search on the "theme" field.');
+ }
+ break;
+
+ default:
+ throw new moodle_exception('criteriaerror',
+ 'webservice', '', null,
+ 'You can not search on this criteria: ' . $key);
+ }
+
+ if (isset($value)) {
+ $conditions[$key] = $crit['value'];
+ $wheres[] = $key . " = :" . $key;
+ }
+ }
+ }
+
+ if (!empty($wheres)) {
+ $wheres = implode(" AND ", $wheres);
+
+ $categories = $DB->get_records_select('course_categories', $wheres, $conditions);
+
+ // Retrieve its sub subcategories (all levels).
+ if ($categories and !empty($params['addsubcategories'])) {
+ $newcategories = array();
+
+ // Check if we required visible/theme checks.
+ $additionalselect = '';
+ $additionalparams = array();
+ if (isset($conditions['visible'])) {
+ $additionalselect .= ' AND visible = :visible';
+ $additionalparams['visible'] = $conditions['visible'];
+ }
+ if (isset($conditions['theme'])) {
+ $additionalselect .= ' AND theme= :theme';
+ $additionalparams['theme'] = $conditions['theme'];
+ }
+
+ foreach ($categories as $category) {
+ $sqlselect = $DB->sql_like('path', ':path') . $additionalselect;
+ $sqlparams = array('path' => $category->path.'/%') + $additionalparams; // It will NOT include the specified category.
+ $subcategories = $DB->get_records_select('course_categories', $sqlselect, $sqlparams);
+ $newcategories = $newcategories + $subcategories; // Both arrays have integer as keys.
+ }
+ $categories = $categories + $newcategories;
+ }
+ }
+
+ } else {
+ // Retrieve all categories in the database.
+ $categories = $DB->get_records('course_categories');
+ }
+
+ // The not returned categories. key => category id, value => reason of exclusion.
+ $excludedcats = array();
+
+ // The returned categories.
+ $categoriesinfo = array();
+
+ // We need to sort the categories by path.
+ // The parent cats need to be checked by the algo first.
+ usort($categories, "core_course_external::compare_categories_by_path");
+
+ foreach ($categories as $category) {
+
+ // Check if the category is a child of an excluded category, if yes exclude it too (excluded => do not return).
+ $parents = explode('/', $category->path);
+ unset($parents[0]); // First key is always empty because path start with / => /1/2/4.
+ foreach ($parents as $parentid) {
+ // Note: when the parent exclusion was due to the context,
+ // the sub category could still be returned.
+ if (isset($excludedcats[$parentid]) and $excludedcats[$parentid] != 'context') {
+ $excludedcats[$category->id] = 'parent';
+ }
+ }
+
+ // Check category depth is <= maxdepth (do not check for user who can manage categories).
+ if ((!empty($CFG->maxcategorydepth) && count($parents) > $CFG->maxcategorydepth)
+ and !has_capability('moodle/category:manage', $context)) {
+ $excludedcats[$category->id] = 'depth';
+ }
+
+ // Check the user can use the category context.
+ $context = context_coursecat::instance($category->id);
+ try {
+ self::validate_context($context);
+ } catch (Exception $e) {
+ $excludedcats[$category->id] = 'context';
+
+ // If it was the requested category then throw an exception.
+ if (isset($params['categoryid']) && $category->id == $params['categoryid']) {
+ $exceptionparam = new stdClass();
+ $exceptionparam->message = $e->getMessage();
+ $exceptionparam->catid = $category->id;
+ throw new moodle_exception('errorcatcontextnotvalid', 'webservice', '', $exceptionparam);
+ }
+ }
+
+ // Return the category information.
+ if (!isset($excludedcats[$category->id])) {
+
+ // Final check to see if the category is visible to the user.
+ if ($category->visible
+ or has_capability('moodle/category:viewhiddencategories', context_system::instance())
+ or has_capability('moodle/category:manage', $context)) {
+
+ $categoryinfo = array();
+ $categoryinfo['id'] = $category->id;
+ $categoryinfo['name'] = $category->name;
+ list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
+ external_format_text($category->description, $category->descriptionformat,
+ $context->id, 'coursecat', 'description', null);
+ $categoryinfo['parent'] = $category->parent;
+ $categoryinfo['sortorder'] = $category->sortorder;
+ $categoryinfo['coursecount'] = $category->coursecount;
+ $categoryinfo['depth'] = $category->depth;
+ $categoryinfo['path'] = $category->path;
+
+ // Some fields only returned for admin.
+ if (has_capability('moodle/category:manage', $context)) {
+ $categoryinfo['idnumber'] = $category->idnumber;
+ $categoryinfo['visible'] = $category->visible;
+ $categoryinfo['visibleold'] = $category->visibleold;
+ $categoryinfo['timemodified'] = $category->timemodified;
+ $categoryinfo['theme'] = $category->theme;
+ }
+
+ $categoriesinfo[] = $categoryinfo;
+ } else {
+ $excludedcats[$category->id] = 'visibility';
+ }
+ }
+ }
+
+ // Sorting the resulting array so it looks a bit better for the client developer.
+ usort($categoriesinfo, "core_course_external::compare_categories_by_sortorder");
+
+ return $categoriesinfo;
+ }
+
+ /**
+ * Sort categories array by path
+ * private function: only used by get_categories
+ *
+ * @param array $category1
+ * @param array $category2
+ * @return int result of strcmp
+ * @since Moodle 2.3
+ */
+ private static function compare_categories_by_path($category1, $category2) {
+ return strcmp($category1->path, $category2->path);
+ }
+
+ /**
+ * Sort categories array by sortorder
+ * private function: only used by get_categories
+ *
+ * @param array $category1
+ * @param array $category2
+ * @return int result of strcmp
+ * @since Moodle 2.3
+ */
+ private static function compare_categories_by_sortorder($category1, $category2) {
+ return strcmp($category1['sortorder'], $category2['sortorder']);
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.3
+ */
+ public static function get_categories_returns() {
+ return new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'category id'),
+ 'name' => new external_value(PARAM_TEXT, 'category name'),
+ 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
+ 'description' => new external_value(PARAM_RAW, 'category description'),
+ 'descriptionformat' => new external_format_value('description'),
+ 'parent' => new external_value(PARAM_INT, 'parent category id'),
+ 'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
+ 'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
+ 'visible' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+ 'visibleold' => new external_value(PARAM_INT, '1: available, 0:not available', VALUE_OPTIONAL),
+ 'timemodified' => new external_value(PARAM_INT, 'timestamp', VALUE_OPTIONAL),
+ 'depth' => new external_value(PARAM_INT, 'category depth'),
+ 'path' => new external_value(PARAM_TEXT, 'category path'),
+ 'theme' => new external_value(PARAM_THEME, 'category theme', VALUE_OPTIONAL),
+ ), 'List of categories'
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function create_categories_parameters() {
+ return new external_function_parameters(
+ array(
+ 'categories' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'name' => new external_value(PARAM_TEXT, 'new category name'),
+ 'parent' => new external_value(PARAM_INT,
+ 'the parent category id inside which the new category will be created
+ - set to 0 for a root category',
+ VALUE_DEFAULT, 0),
+ 'idnumber' => new external_value(PARAM_RAW,
+ 'the new category idnumber', VALUE_OPTIONAL),
+ 'description' => new external_value(PARAM_RAW,
+ 'the new category description', VALUE_OPTIONAL),
+ 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
+ 'theme' => new external_value(PARAM_THEME,
+ 'the new category theme. This option must be enabled on moodle',
+ VALUE_OPTIONAL),
+ )
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Create categories
+ *
+ * @param array $categories - see create_categories_parameters() for the array structure
+ * @return array - see create_categories_returns() for the array structure
+ * @since Moodle 2.3
+ */
+ public static function create_categories($categories) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ $params = self::validate_parameters(self::create_categories_parameters(),
+ array('categories' => $categories));
+
+ $transaction = $DB->start_delegated_transaction();
+
+ $createdcategories = array();
+ foreach ($params['categories'] as $category) {
+ if ($category['parent']) {
+ if (!$DB->record_exists('course_categories', array('id' => $category['parent']))) {
+ throw new moodle_exception('unknowcategory');
+ }
+ $context = context_coursecat::instance($category['parent']);
+ } else {
+ $context = context_system::instance();
+ }
+ self::validate_context($context);
+ require_capability('moodle/category:manage', $context);
+
+ // Check name.
+ if (textlib::strlen($category['name'])>255) {
+ throw new moodle_exception('categorytoolong');
+ }
+
+ $newcategory = new stdClass();
+ $newcategory->name = $category['name'];
+ $newcategory->parent = $category['parent'];
+ $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
+ // Format the description.
+ if (!empty($category['description'])) {
+ $newcategory->description = $category['description'];
+ }
+ $newcategory->descriptionformat = external_validate_format($category['descriptionformat']);
+ if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
+ $newcategory->theme = $category['theme'];
+ }
+ // Check id number.
+ if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
+ if (textlib::strlen($category['idnumber'])>100) {
+ throw new moodle_exception('idnumbertoolong');
+ }
+ if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
+ if ($existing->id) {
+ throw new moodle_exception('idnumbertaken');
+ }
+ }
+ $newcategory->idnumber = $category['idnumber'];
+ }
+
+ $newcategory = create_course_category($newcategory);
+ // Populate special fields.
+ fix_course_sortorder();
+
+ $createdcategories[] = array('id' => $newcategory->id, 'name' => $newcategory->name);
+ }
+
+ $transaction->allow_commit();
+
+ return $createdcategories;
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function create_categories_returns() {
+ return new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'new category id'),
+ 'name' => new external_value(PARAM_TEXT, 'new category name'),
+ )
+ )
+ );
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function update_categories_parameters() {
+ return new external_function_parameters(
+ array(
+ 'categories' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'course id'),
+ 'name' => new external_value(PARAM_TEXT, 'category name', VALUE_OPTIONAL),
+ 'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
+ 'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
+ 'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
+ 'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
+ 'theme' => new external_value(PARAM_THEME,
+ 'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
+ )
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Update categories
+ *
+ * @param array $categories The list of categories to update
+ * @return null
+ * @since Moodle 2.3
+ */
+ public static function update_categories($categories) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ // Validate parameters.
+ $params = self::validate_parameters(self::update_categories_parameters(), array('categories' => $categories));
+
+ $transaction = $DB->start_delegated_transaction();
+
+ foreach ($params['categories'] as $cat) {
+ if (!$category = $DB->get_record('course_categories', array('id' => $cat['id']))) {
+ throw new moodle_exception('unknowcategory');
+ }
+
+ $categorycontext = context_coursecat::instance($cat['id']);
+ self::validate_context($categorycontext);
+ require_capability('moodle/category:manage', $categorycontext);
+
+ if (!empty($cat['name'])) {
+ if (textlib::strlen($cat['name'])>255) {
+ throw new moodle_exception('categorytoolong');
+ }
+ $category->name = $cat['name'];
+ }
+ if (!empty($cat['idnumber'])) {
+ if (textlib::strlen($cat['idnumber'])>100) {
+ throw new moodle_exception('idnumbertoolong');
+ }
+ $category->idnumber = $cat['idnumber'];
+ }
+ if (!empty($cat['description'])) {
+ $category->description = $cat['description'];
+ $category->descriptionformat = external_validate_format($cat['descriptionformat']);
+ }
+ if (!empty($cat['theme'])) {
+ $category->theme = $cat['theme'];
+ }
+ if (!empty($cat['parent']) && ($category->parent != $cat['parent'])) {
+ // First check if parent exists.
+ if (!$parent_cat = $DB->get_record('course_categories', array('id' => $cat['parent']))) {
+ throw new moodle_exception('unknowcategory');
+ }
+ // Then check if we have capability.
+ self::validate_context(get_category_or_system_context((int)$cat['parent']));
+ require_capability('moodle/category:manage', get_category_or_system_context((int)$cat['parent']));
+ // Finally move the category.
+ move_category($category, $parent_cat);
+ $category->parent = $cat['parent'];
+ // Get updated path by move_category().
+ $category->path = $DB->get_field('course_categories', 'path',
+ array('id' => $category->id));
+ }
+ $DB->update_record('course_categories', $category);
+ }
+
+ $transaction->allow_commit();
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.3
+ */
+ public static function update_categories_returns() {
+ return null;
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function delete_categories_parameters() {
+ return new external_function_parameters(
+ array(
+ 'categories' => new external_multiple_structure(
+ new external_single_structure(
+ array(
+ 'id' => new external_value(PARAM_INT, 'category id to delete'),
+ 'newparent' => new external_value(PARAM_INT,
+ 'the parent category to move the contents to, if specified', VALUE_OPTIONAL),
+ 'recursive' => new external_value(PARAM_BOOL, '1: recursively delete all contents inside this
+ category, 0 (default): move contents to newparent or current parent category (except if parent is root)', VALUE_DEFAULT, 0)
+ )
+ )
+ )
+ )
+ );
+ }
+
+ /**
+ * Delete categories
+ *
+ * @param array $categories A list of category ids
+ * @return array
+ * @since Moodle 2.3
+ */
+ public static function delete_categories($categories) {
+ global $CFG, $DB;
+ require_once($CFG->dirroot . "/course/lib.php");
+
+ // Validate parameters.
+ $params = self::validate_parameters(self::delete_categories_parameters(), array('categories' => $categories));
+
+ $transaction = $DB->start_delegated_transaction();
+
+ foreach ($params['categories'] as $category) {
+ if (!$deletecat = $DB->get_record('course_categories', array('id' => $category['id']))) {
+ throw new moodle_exception('unknowcategory');
+ }
+ $context = context_coursecat::instance($deletecat->id);
+ require_capability('moodle/category:manage', $context);
+ self::validate_context($context);
+ self::validate_context(get_category_or_system_context($deletecat->parent));
+
+ if ($category['recursive']) {
+ // If recursive was specified, then we recursively delete the category's contents.
+ category_delete_full($deletecat, false);
+ } else {
+ // In this situation, we don't delete the category's contents, we either move it to newparent or parent.
+ // If the parent is the root, moving is not supported (because a course must always be inside a category).
+ // We must move to an existing category.
+ if (!empty($category['newparent'])) {
+ if (!$DB->record_exists('course_categories', array('id' => $category['newparent']))) {
+ throw new moodle_exception('unknowcategory');
+ }
+ $newparent = $category['newparent'];
+ } else {
+ $newparent = $deletecat->parent;
+ }
+
+ // This operation is not allowed. We must move contents to an existing category.
+ if ($newparent == 0) {
+ throw new moodle_exception('movecatcontentstoroot');
+ }
+
+ $parentcontext = get_category_or_system_context($newparent);
+ require_capability('moodle/category:manage', $parentcontext);
+ self::validate_context($parentcontext);
+ category_delete_move($deletecat, $newparent, false);
+ }
+ }
+
+ $transaction->allow_commit();
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.3
+ */
+ public static function delete_categories_returns() {
+ return null;
+ }
+
+}
+
+/**
+ * Deprecated course external functions
+ *
+ * @package core_course
+ * @copyright 2009 Petr Skodak
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not use this class any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external
+ */
+class moodle_course_external extends external_api {
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::get_courses_parameters()
+ */
+ public static function get_courses_parameters() {
+ return core_course_external::get_courses_parameters();
+ }
+
+ /**
+ * Get courses
+ *
+ * @param array $options
+ * @return array
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::get_courses()
+ */
+ public static function get_courses($options) {
+ return core_course_external::get_courses($options);
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::get_courses_returns()
+ */
+ public static function get_courses_returns() {
+ return core_course_external::get_courses_returns();
+ }
+
+ /**
+ * Returns description of method parameters
+ *
+ * @return external_function_parameters
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::create_courses_parameters()
+ */
+ public static function create_courses_parameters() {
+ return core_course_external::create_courses_parameters();
+ }
+
+ /**
+ * Create courses
+ *
+ * @param array $courses
+ * @return array courses (id and shortname only)
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::create_courses()
+ */
+ public static function create_courses($courses) {
+ return core_course_external::create_courses($courses);
+ }
+
+ /**
+ * Returns description of method result value
+ *
+ * @return external_description
+ * @since Moodle 2.0
+ * @deprecated Moodle 2.2 MDL-29106 - Please do not call this function any more.
+ * @todo MDL-31194 This will be deleted in Moodle 2.5.
+ * @see core_course_external::create_courses_returns()
+ */
+ public static function create_courses_returns() {
+ return core_course_external::create_courses_returns();
+ }
+
+}
diff --git a/format/README.txt b/format/README.txt
new file mode 100644
index 0000000..9bb62ab
--- /dev/null
+++ b/format/README.txt
@@ -0,0 +1,146 @@
+Course formats
+==============
+
+To create a new course format, make another folder in here.
+
+If you want a basic format, you only need to write the 'standard files' listed
+below.
+
+If you want to store information in the database for your format, or control
+access to features of your format, you need some of the optional files too.
+
+All names below assume that your format is called 'yourformat'.
+
+
+Standard files
+--------------
+
+* yourformat/format.php
+
+ Code that actually displays the course view page. See existing formats for
+ examples.
+
+* yourformat/config.php
+
+ Configuration file, mainly controlling default blocks for the format.
+ See existing formats for examples.
+
+* yourformat/lang/en/format_yourformat.php
+
+ Language file containing basic language strings for your format. Here
+ is a minimal language file:
+
+
+
+ The first string is used in the dropdown menu of course settings. The second
+ is used when editing an activity within a course of your format.
+
+ Note that existing formats store their language strings in the main
+ moodle.php, which you can also do, but this separate file is recommended
+ for contributed formats.
+
+ You can also store other strings in this file if you wish. They can be
+ accessed as follows, for example to get the section name:
+
+ get_string('nameyourformat','format_yourformat');
+
+ Of course you can have other folders as well as just English if you want
+ to provide multiple languages.
+
+
+Optional files (database access)
+--------------------------------
+
+If these files exist, Moodle will use them to set up database tables when you
+visit the admin page.
+
+* yourformat/db/install.xml
+
+ Database table definitions. Use your format name at the start of the table
+ names to increase the chance that they are unique.
+
+* yourformat/db/upgrade.php
+
+ Database upgrade instructions. Similar to other upgrade.php files, so look
+ at those for modules etc. if you want to see.
+
+ The function must look like:
+
+ function xmldb_format_yourformat_upgrade($oldversion) {
+ ...
+
+* yourformat/version.php
+
+ Required if you use database tables.
+
+ version = 2006120100; // Plugin version (update when tables change)
+ $plugin->requires = 2006092801; // Required Moodle version
+ ?>
+
+
+Optional files (backup)
+-----------------------
+
+If these files exist, backup and restore run automatically when backing up
+the course. You can't back up the course format data independently.
+
+* yourformat/backuplib.php
+
+ Similar to backup code for other plugins. Must have a function:
+
+ function yourformat_backup_format_data($bf,$preferences) {
+ ...
+
+* yourformat/restorelib.php
+
+ Similar to restore code for other plugins. Must have a function:
+
+ function yourformat_restore_format_data($restore,$data) {
+ ...
+
+ ($data is the xmlized data underneath FORMATDATA in the backup XML file.
+ Do print_object($data); while testing to see how it looks.)
+
+
+Optional file (capabilities)
+----------------------------
+
+If this file exists, Moodle refreshes your format's capabilities
+(checks that they are all included in the database) whenever you increase
+the version in yourformat/version.php.
+
+* yourformat/db/access.php
+
+ Contains capability entries similar to other access.php files.
+
+ The array definition must look like:
+
+ $format_yourformat_capabilities = array(
+ ...
+
+ Format names must look like:
+
+ format/yourformat:specialpower
+
+ Capability definitions in your language file must look like:
+
+ $string['yourformat:specialpower']='Revolutionise the world';
+
+
+
+Optional file (styles)
+----------------------
+
+* yourformat/styles.php
+
+ If this file exists it will be included in the CSS Moodle generates.
+
+
+Optional delete course hook
+---------------------------
+
+* in your yourformat/lib.php add function format_yourformat_delete_course($courseid)
\ No newline at end of file
diff --git a/format/formatlegacy.php b/format/formatlegacy.php
new file mode 100644
index 0000000..f353a4b
--- /dev/null
+++ b/format/formatlegacy.php
@@ -0,0 +1,363 @@
+.
+
+/**
+ * Course format class to allow plugins developed for Moodle 2.3 to work in the new API
+ *
+ * @package core_course
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Course format class to allow plugins developed for Moodle 2.3 to work in the new API
+ *
+ * @package core_course
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_legacy extends format_base {
+
+ /**
+ * Returns true if this course format uses sections
+ *
+ * This function calls function callback_FORMATNAME_uses_sections() if it exists
+ *
+ * @return bool
+ */
+ public function uses_sections() {
+ global $CFG;
+ // Note that lib.php in course format folder is already included by now
+ $featurefunction = 'callback_'.$this->format.'_uses_sections';
+ if (function_exists($featurefunction)) {
+ return $featurefunction();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * This function calls function callback_FORMATNAME_get_section_name() if it exists
+ *
+ * @param int|stdClass $section Section object from database or just field section.section
+ * @return string Display name that the course format prefers, e.g. "Topic 2"
+ */
+ public function get_section_name($section) {
+ // Use course formatter callback if it exists
+ $namingfunction = 'callback_'.$this->format.'_get_section_name';
+ if (function_exists($namingfunction) && ($course = $this->get_course())) {
+ return $namingfunction($course, $this->get_section($section));
+ }
+
+ // else, default behavior:
+ return parent::get_section_name($section);
+ }
+
+ /**
+ * The URL to use for the specified course (with section)
+ *
+ * This function calls function callback_FORMATNAME_get_section_url() if it exists
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if omitted the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ public function get_view_url($section, $options = array()) {
+ // Use course formatter callback if it exists
+ $featurefunction = 'callback_'.$this->format.'_get_section_url';
+ if (function_exists($featurefunction) && ($course = $this->get_course())) {
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ if ($sectionnum) {
+ $url = $featurefunction($course, $sectionnum);
+ if ($url || !empty($options['navigation'])) {
+ return $url;
+ }
+ }
+ }
+
+ // 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;
+ }
+
+ /**
+ * Returns the information about the ajax support in the given source format
+ *
+ * This function calls function callback_FORMATNAME_ajax_support() if it exists
+ *
+ * The returned object's property (boolean)capable indicates that
+ * the course format supports Moodle course ajax features.
+ * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+ *
+ * @return stdClass
+ */
+ public function supports_ajax() {
+ // set up default values
+ $ajaxsupport = parent::supports_ajax();
+
+ // get the information from the course format library
+ $featurefunction = 'callback_'.$this->format.'_ajax_support';
+ if (function_exists($featurefunction)) {
+ $formatsupport = $featurefunction();
+ if (isset($formatsupport->capable)) {
+ $ajaxsupport->capable = $formatsupport->capable;
+ }
+ if (is_array($formatsupport->testedbrowsers)) {
+ $ajaxsupport->testedbrowsers = $formatsupport->testedbrowsers;
+ }
+ }
+ return $ajaxsupport;
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * First this function calls callback_FORMATNAME_display_content() if it exists to check
+ * if the navigation should be extended at all
+ *
+ * Then it calls function callback_FORMATNAME_load_content() if it exists to actually extend
+ * navigation
+ *
+ * By default the parent method is called
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ global $PAGE;
+ // if course format displays section on separate pages and we are on course/view.php page
+ // and the section parameter is specified, make sure this section is expanded in
+ // navigation
+ if ($navigation->includesectionnum === false) {
+ $selectedsection = optional_param('section', null, PARAM_INT);
+ if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
+ $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+ $navigation->includesectionnum = $selectedsection;
+ }
+ }
+
+ // check if there are callbacks to extend course navigation
+ $displayfunc = 'callback_'.$this->format.'_display_content';
+ if (function_exists($displayfunc) && !$displayfunc()) {
+ return;
+ }
+ $featurefunction = 'callback_'.$this->format.'_load_content';
+ if (function_exists($featurefunction) && ($course = $this->get_course())) {
+ $featurefunction($navigation, $course, $node);
+ } else {
+ parent::extend_course_navigation($navigation, $node);
+ }
+ }
+
+ /**
+ * Custom action after section has been moved in AJAX mode
+ *
+ * Used in course/rest.php
+ *
+ * This function calls function callback_FORMATNAME_ajax_section_move() if it exists
+ *
+ * @return array This will be passed in ajax respose
+ */
+ function ajax_section_move() {
+ $featurefunction = 'callback_'.$this->format.'_ajax_section_move';
+ if (function_exists($featurefunction) && ($course = $this->get_course())) {
+ return $featurefunction($course);
+ } else {
+ return parent::ajax_section_move();
+ }
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * This function checks the existence of the file config.php in the course format folder.
+ * If file exists and contains the code
+ * $format['defaultblocks'] = 'leftblock1,leftblock2:rightblock1,rightblock2';
+ * these blocks are used, otherwise parent function is called
+ *
+ * @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() {
+ global $CFG;
+ $formatconfig = $CFG->dirroot.'/course/format/'.$this->format.'/config.php';
+ $format = array(); // initialize array in external file
+ if (is_readable($formatconfig)) {
+ include($formatconfig);
+ }
+ if (!empty($format['defaultblocks'])) {
+ return blocks_parse_default_blocks_list($format['defaultblocks']);
+ }
+ 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) {
+ global $DB;
+ if ($oldcourse !== null) {
+ $data = (array)$data;
+ $oldcourse = (array)$oldcourse;
+ $options = $this->course_format_options();
+ foreach ($options as $key => $unused) {
+ if (!array_key_exists($key, $data)) {
+ if (array_key_exists($key, $oldcourse)) {
+ $data[$key] = $oldcourse[$key];
+ } else if ($key === 'numsections') {
+ // If previous format does not have the field 'numsections' and this one does,
+ // and $data['numsections'] is not set fill it with the maximum section number from the DB
+ $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+ WHERE course = ?', array($this->courseid));
+ if ($maxsection) {
+ // If there are no sections, or just default 0-section, 'numsections' will be set to default
+ $data['numsections'] = $maxsection;
+ }
+ }
+ }
+ }
+ }
+ return $this->update_format_options($data);
+ }
+}
\ No newline at end of file
diff --git a/format/lib.php b/format/lib.php
new file mode 100644
index 0000000..f0ccf9b
--- /dev/null
+++ b/format/lib.php
@@ -0,0 +1,941 @@
+.
+
+/**
+ * Base class for course format plugins
+ *
+ * @package core_course
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+/**
+ * Returns an instance of format class (extending format_base) for given course
+ *
+ * @param int|stdClass $courseorid either course id or
+ * an object that has the property 'format' and may contain property 'id'
+ * @return format_base
+ */
+function course_get_format($courseorid) {
+ return format_base::instance($courseorid);
+}
+
+/**
+ * Base class for course formats
+ *
+ * Each course format must declare class
+ * class format_FORMATNAME extends format_base {}
+ * in file lib.php
+ *
+ * For each course just one instance of this class is created and it will always be returned by
+ * course_get_format($courseorid). Format may store it's specific course-dependent options in
+ * variables of this class.
+ *
+ * In rare cases instance of child class may be created just for format without course id
+ * i.e. to check if format supports AJAX.
+ *
+ * Also course formats may extend class section_info and overwrite
+ * format_base::build_section_cache() to return more information about sections.
+ *
+ * If you are upgrading from Moodle 2.3 start with copying the class format_legacy and renaming
+ * it to format_FORMATNAME, then move the code from your callback functions into
+ * appropriate functions of the class.
+ *
+ * @package core_course
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class format_base {
+ /** @var int Id of the course in this instance (maybe 0) */
+ protected $courseid;
+ /** @var string format used for this course. Please note that it can be different from
+ * course.format field if course referes to non-existing of disabled format */
+ 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();
+
+ /**
+ * Creates a new instance of class
+ *
+ * Please use {@link course_get_format($courseorid)} to get an instance of the format class
+ *
+ * @param string $format
+ * @param int $courseid
+ * @return format_base
+ */
+ protected function __construct($format, $courseid) {
+ $this->format = $format;
+ $this->courseid = $courseid;
+ }
+
+ /**
+ * Validates that course format exists and enabled and returns either itself or default format
+ *
+ * @param string $format
+ * @return string
+ */
+ protected static final function get_format_or_default($format) {
+ if ($format === 'site') {
+ return $format;
+ }
+ $plugins = get_sorted_course_formats();
+ if (in_array($format, $plugins)) {
+ return $format;
+ }
+ // Else return default format
+ $defaultformat = get_config('moodlecourse', 'format');
+ if (!in_array($defaultformat, $plugins)) {
+ // when default format is not set correctly, use the first available format
+ $defaultformat = reset($plugins);
+ }
+ static $warningprinted = array();
+ if (empty($warningprinted[$format])) {
+ debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
+ $warningprinted[$format] = true;
+ }
+ return $defaultformat;
+ }
+
+ /**
+ * Get class name for the format
+ *
+ * If course format xxx does not declare class format_xxx, format_legacy will be returned.
+ * This function also includes lib.php file from corresponding format plugin
+ *
+ * @param string $format
+ * @return string
+ */
+ protected static final function get_class_name($format) {
+ global $CFG;
+ static $classnames = array('site' => 'format_site');
+ if (!isset($classnames[$format])) {
+ $plugins = get_plugin_list('format');
+ $usedformat = self::get_format_or_default($format);
+ if (file_exists($plugins[$usedformat].'/lib.php')) {
+ require_once($plugins[$usedformat].'/lib.php');
+ }
+ $classnames[$format] = 'format_'. $usedformat;
+ if (!class_exists($classnames[$format])) {
+ require_once($CFG->dirroot.'/course/format/formatlegacy.php');
+ $classnames[$format] = 'format_legacy';
+ }
+ }
+ return $classnames[$format];
+ }
+
+ /**
+ * Returns an instance of the class
+ *
+ * @todo MDL-35727 use MUC for caching of instances, limit the number of cached instances
+ *
+ * @param int|stdClass $courseorid either course id or
+ * an object that has the property 'format' and may contain property 'id'
+ * @return format_base
+ */
+ public static final function instance($courseorid) {
+ global $DB;
+ if (!is_object($courseorid)) {
+ $courseid = (int)$courseorid;
+ if ($courseid && isset(self::$instances[$courseid]) && count(self::$instances[$courseid]) == 1) {
+ $formats = array_keys(self::$instances[$courseid]);
+ $format = reset($formats);
+ } else {
+ $format = $DB->get_field('course', 'format', array('id' => $courseid), MUST_EXIST);
+ }
+ } else {
+ $format = $courseorid->format;
+ if (isset($courseorid->id)) {
+ $courseid = clean_param($courseorid->id, PARAM_INT);
+ } else {
+ $courseid = 0;
+ }
+ }
+ // validate that format exists and enabled, use default otherwise
+ $format = self::get_format_or_default($format);
+ if (!isset(self::$instances[$courseid][$format])) {
+ $classname = self::get_class_name($format);
+ self::$instances[$courseid][$format] = new $classname($format, $courseid);
+ }
+ return self::$instances[$courseid][$format];
+ }
+
+ /**
+ * Resets cache for the course (or all caches)
+ * To be called from {@link rebuild_course_cache()}
+ *
+ * @param int $courseid
+ */
+ public static final function reset_course_cache($courseid = 0) {
+ if ($courseid) {
+ if (isset(self::$instances[$courseid])) {
+ 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]);
+ }
+ } else {
+ self::$instances = array();
+ }
+ }
+
+ /**
+ * Returns the format name used by this course
+ *
+ * @return string
+ */
+ public final function get_format() {
+ return $this->format;
+ }
+
+ /**
+ * Returns id of the course (0 if course is not specified)
+ *
+ * @return int
+ */
+ public final function get_courseid() {
+ return $this->courseid;
+ }
+
+ /**
+ * Returns a record from course database table plus additional fields
+ * that course format defines
+ *
+ * @return stdClass
+ */
+ public function get_course() {
+ global $DB;
+ if (!$this->courseid) {
+ return null;
+ }
+ 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;
+ }
+
+ /**
+ * Returns true if this course format uses sections
+ *
+ * This function may be called without specifying the course id
+ * i.e. in {@link course_format_uses_sections()}
+ *
+ * Developers, note that if course format does use sections there should be defined a language
+ * string with the name 'sectionname' defining what the section relates to in the format, i.e.
+ * $string['sectionname'] = 'Topic';
+ * or
+ * $string['sectionname'] = 'Week';
+ *
+ * @return bool
+ */
+ public function uses_sections() {
+ return false;
+ }
+
+ /**
+ * Returns a list of sections used in the course
+ *
+ * This is a shortcut to get_fast_modinfo()->get_section_info_all()
+ * @see get_fast_modinfo()
+ * @see course_modinfo::get_section_info_all()
+ *
+ * @return array of section_info objects
+ */
+ public final function get_sections() {
+ if ($course = $this->get_course()) {
+ $modinfo = get_fast_modinfo($course);
+ return $modinfo->get_section_info_all();
+ }
+ return array();
+ }
+
+ /**
+ * Returns information about section used in course
+ *
+ * @param int|stdClass $section either section number (field course_section.section) or row from course_section table
+ * @param int $strictness
+ * @return section_info
+ */
+ public final function get_section($section, $strictness = IGNORE_MISSING) {
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ $sections = $this->get_sections();
+ if (array_key_exists($sectionnum, $sections)) {
+ return $sections[$sectionnum];
+ }
+ if ($strictness == MUST_EXIST) {
+ throw new moodle_exception('sectionnotexist');
+ }
+ return null;
+ }
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * @return Display name that the course format prefers, e.g. "Topic 2"
+ */
+ public function get_section_name($section) {
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ return get_string('sectionname', 'format_'.$this->format) . ' ' . $sectionnum;
+ }
+
+ /**
+ * Returns the information about the ajax support in the given source format
+ *
+ * The returned object's property (boolean)capable indicates that
+ * the course format supports Moodle course ajax features.
+ * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+ *
+ * @return stdClass
+ */
+ public function supports_ajax() {
+ // no support by default
+ $ajaxsupport = new stdClass();
+ $ajaxsupport->capable = false;
+ $ajaxsupport->testedbrowsers = array();
+ return $ajaxsupport;
+ }
+
+ /**
+ * Custom action after section has been moved in AJAX mode
+ *
+ * Used in course/rest.php
+ *
+ * @return array This will be passed in ajax respose
+ */
+ public function ajax_section_move() {
+ return null;
+ }
+
+ /**
+ * The URL to use for the specified course (with section)
+ *
+ * Please note that course view page /course/view.php?id=COURSEID is hardcoded in many
+ * places in core and contributed modules. If course format wants to change the location
+ * of the view script, it is not enough to change just this function. Do not forget
+ * to add proper redirection.
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if null the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ public function get_view_url($section, $options = array()) {
+ $course = $this->get_course();
+ $url = new moodle_url('/course/view.php', array('id' => $course->id));
+
+ if (array_key_exists('sr', $options)) {
+ $sectionno = $options['sr'];
+ } else if (is_object($section)) {
+ $sectionno = $section->section;
+ } else {
+ $sectionno = $section;
+ }
+ 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;
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * This method is called from {@link global_navigation::load_course_sections()}
+ *
+ * By default the method {@link global_navigation::load_generic_course_sections()} is called
+ *
+ * When overwriting please note that navigationlib relies on using the correct values for
+ * arguments $type and $key in {@link navigation_node::add()}
+ *
+ * Example of code creating a section node:
+ * $sectionnode = $node->add($sectionname, $url, navigation_node::TYPE_SECTION, null, $section->id);
+ * $sectionnode->nodetype = navigation_node::NODETYPE_BRANCH;
+ *
+ * Example of code creating an activity node:
+ * $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
+ * if (global_navigation::module_extends_navigation($activity->modname)) {
+ * $activitynode->nodetype = navigation_node::NODETYPE_BRANCH;
+ * } else {
+ * $activitynode->nodetype = navigation_node::NODETYPE_LEAF;
+ * }
+ *
+ * Also note that if $navigation->includesectionnum is not null, the section with this relative
+ * number needs is expected to be loaded
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ if ($course = $this->get_course()) {
+ $navigation->load_generic_course_sections($course, $node);
+ }
+ return array();
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * @see blocks_add_default_course_blocks()
+ *
+ * @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() {
+ global $CFG;
+ if (!empty($CFG->defaultblocks)){
+ return blocks_parse_default_blocks_list($CFG->defaultblocks);
+ }
+ $blocknames = array(
+ BLOCK_POS_LEFT => array(),
+ BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
+ );
+ 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);
+ }
+
+ /**
+ * Allows course format to execute code on moodle_page::set_course()
+ *
+ * @param moodle_page $page instance of page calling set_course
+ */
+ public function page_set_course(moodle_page $page) {
+ }
+
+ /**
+ * Allows course format to execute code on moodle_page::set_cm()
+ *
+ * Current module can be accessed as $page->cm (returns instance of cm_info)
+ *
+ * @param moodle_page $page instance of page calling set_cm
+ */
+ public function page_set_cm(moodle_page $page) {
+ }
+
+ /**
+ * Course-specific information to be output on any course page (usually above navigation bar)
+ *
+ * Example of usage:
+ * define
+ * class format_FORMATNAME_XXX implements renderable {}
+ *
+ * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:
+ * class format_FORMATNAME_renderer extends plugin_renderer_base {
+ * protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {
+ * return html_writer::tag('div', 'This is my header/footer');
+ * }
+ * }
+ *
+ * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from
+ * plugin renderer will be called
+ *
+ * @return null|renderable null for no output or object with data for plugin renderer
+ */
+ public function course_header() {
+ return null;
+ }
+
+ /**
+ * Course-specific information to be output on any course page (usually in the beginning of
+ * standard footer)
+ *
+ * See {@link format_base::course_header()} for usage
+ *
+ * @return null|renderable null for no output or object with data for plugin renderer
+ */
+ public function course_footer() {
+ return null;
+ }
+
+ /**
+ * Course-specific information to be output immediately above content on any course page
+ *
+ * See {@link format_base::course_header()} for usage
+ *
+ * @return null|renderable null for no output or object with data for plugin renderer
+ */
+ public function course_content_header() {
+ return null;
+ }
+
+ /**
+ * Course-specific information to be output immediately below content on any course page
+ *
+ * See {@link format_base::course_header()} for usage
+ *
+ * @return null|renderable null for no output or object with data for plugin renderer
+ */
+ public function course_content_footer() {
+ return null;
+ }
+
+ /**
+ * Returns instance of page renderer used by this plugin
+ *
+ * @param moodle_page $page
+ * @return renderer_base
+ */
+ public function get_renderer(moodle_page $page) {
+ return $page->get_renderer('format_'. $this->get_format());
+ }
+
+ /**
+ * Returns true if the specified section is current
+ *
+ * By default we analyze $course->marker
+ *
+ * @param int|stdClass|section_info $section
+ * @return bool
+ */
+ public function is_section_current($section) {
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);
+ }
+}
+
+/**
+ * Pseudo course format used for the site main page
+ *
+ * @package core_course
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_site extends format_base {
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * @param int|stdClass $section Section object from database or just field section.section
+ * @return Display name that the course format prefers, e.g. "Topic 2"
+ */
+ function get_section_name($section) {
+ return get_string('site');
+ }
+
+ /**
+ * For this fake course referring to the whole site, the site homepage is always returned
+ * regardless of arguments
+ *
+ * @param int|stdClass $section
+ * @param array $options
+ * @return null|moodle_url
+ */
+ 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/format/renderer.php b/format/renderer.php
new file mode 100644
index 0000000..ceb3045
--- /dev/null
+++ b/format/renderer.php
@@ -0,0 +1,797 @@
+.
+
+/**
+ * Base renderer for outputting course formats.
+ *
+ * @package core
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+/**
+ * This is a convenience renderer which can be used by section based formats
+ * to reduce code duplication. It is not necessary for all course formats to
+ * use this and its likely to change in future releases.
+ *
+ * @package core
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+abstract class format_section_renderer_base extends plugin_renderer_base {
+
+ /**
+ * Generate the starting container html for a list of sections
+ * @return string HTML to output.
+ */
+ abstract protected function start_section_list();
+
+ /**
+ * Generate the closing container html for a list of sections
+ * @return string HTML to output.
+ */
+ abstract protected function end_section_list();
+
+ /**
+ * Generate the title for this section page
+ * @return string the page title
+ */
+ abstract protected function page_title();
+
+ /**
+ * Generate the section title
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @return string HTML to output.
+ */
+ public function section_title($section, $course) {
+ $title = get_section_name($course, $section);
+ $url = course_get_url($course, $section->section, array('navigation' => true));
+ if ($url) {
+ $title = html_writer::link($url, $title);
+ }
+ return $title;
+ }
+
+ /**
+ * Generate the content to displayed on the right part of a section
+ * before course modules are included
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @param bool $onsectionpage true if being printed on a section page
+ * @return string HTML to output.
+ */
+ protected function section_right_content($section, $course, $onsectionpage) {
+ $o = $this->output->spacer();
+
+ if ($section->section != 0) {
+ $controls = $this->section_edit_controls($course, $section, $onsectionpage);
+ if (!empty($controls)) {
+ $o = implode(' ', $controls);
+ }
+ }
+
+ return $o;
+ }
+
+ /**
+ * Generate the content to displayed on the left part of a section
+ * before course modules are included
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @param bool $onsectionpage true if being printed on a section page
+ * @return string HTML to output.
+ */
+ protected function section_left_content($section, $course, $onsectionpage) {
+ $o = $this->output->spacer();
+
+ if ($section->section != 0) {
+ // Only in the non-general sections.
+ if (course_get_format($course)->is_section_current($section)) {
+ $o = get_accesshide(get_string('currentsection', 'format_'.$course->format));
+ }
+ }
+
+ return $o;
+ }
+
+ /**
+ * Generate the display of the header part of a section before
+ * course modules are included
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @param bool $onsectionpage true if being printed on a single-section page
+ * @param int $sectionreturn The section to return to after an action
+ * @return string HTML to output.
+ */
+ protected function section_header($section, $course, $onsectionpage, $sectionreturn=null) {
+ global $PAGE;
+
+ $o = '';
+ $currenttext = '';
+ $sectionstyle = '';
+
+ if ($section->section != 0) {
+ // Only in the non-general sections.
+ if (!$section->visible) {
+ $sectionstyle = ' hidden';
+ } else if (course_get_format($course)->is_section_current($section)) {
+ $sectionstyle = ' current';
+ }
+ }
+
+ $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
+ 'class' => 'section main clearfix'.$sectionstyle));
+
+ $leftcontent = $this->section_left_content($section, $course, $onsectionpage);
+ $o.= html_writer::tag('div', $leftcontent, array('class' => 'left side'));
+
+ $rightcontent = $this->section_right_content($section, $course, $onsectionpage);
+ $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
+ $o.= html_writer::start_tag('div', array('class' => 'content'));
+
+ // When not on a section page, we display the section titles except the general section if null
+ $hasnamenotsecpg = (!$onsectionpage && ($section->section != 0 || !is_null($section->name)));
+
+ // When on a section page, we only display the general section title, if title is not the default one
+ $hasnamesecpg = ($onsectionpage && ($section->section == 0 && !is_null($section->name)));
+
+ if ($hasnamenotsecpg || $hasnamesecpg) {
+ $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
+ }
+
+ $o.= html_writer::start_tag('div', array('class' => 'summary'));
+ $o.= $this->format_summary_text($section);
+
+ $context = context_course::instance($course->id);
+ if ($PAGE->user_is_editing() && has_capability('moodle/course:update', $context)) {
+ $url = new moodle_url('/course/editsection.php', array('id'=>$section->id, 'sr'=>$sectionreturn));
+ $o.= html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/edit'),
+ 'class' => 'iconsmall edit', 'alt' => get_string('edit'))),
+ array('title' => get_string('editsummary')));
+ }
+ $o.= html_writer::end_tag('div');
+
+ $o .= $this->section_availability_message($section,
+ has_capability('moodle/course:viewhiddensections', $context));
+
+ return $o;
+ }
+
+ /**
+ * Generate the display of the footer part of a section
+ *
+ * @return string HTML to output.
+ */
+ protected function section_footer() {
+ $o = html_writer::end_tag('div');
+ $o.= html_writer::end_tag('li');
+
+ return $o;
+ }
+
+ /**
+ * Generate the edit controls of a section
+ *
+ * @param stdClass $course The course entry from DB
+ * @param stdClass $section The course_section entry from DB
+ * @param bool $onsectionpage true if being printed on a section page
+ * @return array of links with edit controls
+ */
+ protected function section_edit_controls($course, $section, $onsectionpage = false) {
+ global $PAGE;
+
+ if (!$PAGE->user_is_editing()) {
+ return array();
+ }
+
+ $coursecontext = context_course::instance($course->id);
+
+ if ($onsectionpage) {
+ $baseurl = course_get_url($course, $section->section);
+ } else {
+ $baseurl = course_get_url($course);
+ }
+ $baseurl->param('sesskey', sesskey());
+
+ $controls = array();
+
+ $url = clone($baseurl);
+ if (has_capability('moodle/course:sectionvisibility', $coursecontext)) {
+ if ($section->visible) { // Show the hide/show eye.
+ $strhidefromothers = get_string('hidefromothers', 'format_'.$course->format);
+ $url->param('hide', $section->section);
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/hide'),
+ 'class' => 'icon hide', 'alt' => $strhidefromothers)),
+ array('title' => $strhidefromothers, 'class' => 'editing_showhide'));
+ } else {
+ $strshowfromothers = get_string('showfromothers', 'format_'.$course->format);
+ $url->param('show', $section->section);
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/show'),
+ 'class' => 'icon hide', 'alt' => $strshowfromothers)),
+ array('title' => $strshowfromothers, 'class' => 'editing_showhide'));
+ }
+ }
+
+ if (!$onsectionpage && has_capability('moodle/course:movesections', $coursecontext)) {
+ $url = clone($baseurl);
+ if ($section->section > 1) { // Add a arrow to move section up.
+ $url->param('section', $section->section);
+ $url->param('move', -1);
+ $strmoveup = get_string('moveup');
+
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/up'),
+ 'class' => 'icon up', 'alt' => $strmoveup)),
+ array('title' => $strmoveup, 'class' => 'moveup'));
+ }
+
+ $url = clone($baseurl);
+ if ($section->section < $course->numsections) { // Add a arrow to move section down.
+ $url->param('section', $section->section);
+ $url->param('move', 1);
+ $strmovedown = get_string('movedown');
+
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/down'),
+ 'class' => 'icon down', 'alt' => $strmovedown)),
+ array('title' => $strmovedown, 'class' => 'movedown'));
+ }
+ }
+
+ return $controls;
+ }
+
+ /**
+ * Generate a summary of a section for display on the 'coruse index page'
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course The course entry from DB
+ * @param array $mods (argument not used)
+ * @return string HTML to output.
+ */
+ protected function section_summary($section, $course, $mods) {
+ $classattr = 'section main section-summary clearfix';
+ $linkclasses = '';
+
+ // If section is hidden then display grey section link
+ if (!$section->visible) {
+ $classattr .= ' hidden';
+ $linkclasses .= ' dimmed_text';
+ } else if (course_get_format($course)->is_section_current($section)) {
+ $classattr .= ' current';
+ }
+
+ $o = '';
+ $o .= html_writer::start_tag('li', array('id' => 'section-'.$section->section, 'class' => $classattr));
+
+ $o .= html_writer::tag('div', '', array('class' => 'left side'));
+ $o .= html_writer::tag('div', '', array('class' => 'right side'));
+ $o .= html_writer::start_tag('div', array('class' => 'content'));
+
+ $title = get_section_name($course, $section);
+ if ($section->uservisible) {
+ $title = html_writer::tag('a', $title,
+ array('href' => course_get_url($course, $section->section), 'class' => $linkclasses));
+ }
+ $o .= $this->output->heading($title, 3, 'section-title');
+
+ $o.= html_writer::start_tag('div', array('class' => 'summarytext'));
+ $o.= $this->format_summary_text($section);
+ $o.= html_writer::end_tag('div');
+ $o.= $this->section_activity_summary($section, $course, null);
+
+ $context = context_course::instance($course->id);
+ $o .= $this->section_availability_message($section,
+ has_capability('moodle/course:viewhiddensections', $context));
+
+ $o .= html_writer::end_tag('div');
+ $o .= html_writer::end_tag('li');
+
+ return $o;
+ }
+
+ /**
+ * Generate a summary of the activites in a section
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param stdClass $course the course record from DB
+ * @param array $mods (argument not used)
+ * @return string HTML to output.
+ */
+ private function section_activity_summary($section, $course, $mods) {
+ $modinfo = get_fast_modinfo($course);
+ if (empty($modinfo->sections[$section->section])) {
+ return '';
+ }
+
+ // Generate array with count of activities in this section:
+ $sectionmods = array();
+ $total = 0;
+ $complete = 0;
+ $cancomplete = isloggedin() && !isguestuser();
+ $completioninfo = new completion_info($course);
+ foreach ($modinfo->sections[$section->section] as $cmid) {
+ $thismod = $modinfo->cms[$cmid];
+
+ if ($thismod->modname == 'label') {
+ // Labels are special (not interesting for students)!
+ continue;
+ }
+
+ if ($thismod->uservisible) {
+ if (isset($sectionmods[$thismod->modname])) {
+ $sectionmods[$thismod->modname]['name'] = $thismod->modplural;
+ $sectionmods[$thismod->modname]['count']++;
+ } else {
+ $sectionmods[$thismod->modname]['name'] = $thismod->modfullname;
+ $sectionmods[$thismod->modname]['count'] = 1;
+ }
+ if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) {
+ $total++;
+ $completiondata = $completioninfo->get_data($thismod, true);
+ if ($completiondata->completionstate == COMPLETION_COMPLETE) {
+ $complete++;
+ }
+ }
+ }
+ }
+
+ if (empty($sectionmods)) {
+ // No sections
+ return '';
+ }
+
+ // Output section activities summary:
+ $o = '';
+ $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right'));
+ foreach ($sectionmods as $mod) {
+ $o.= html_writer::start_tag('span', array('class' => 'activity-count'));
+ $o.= $mod['name'].': '.$mod['count'];
+ $o.= html_writer::end_tag('span');
+ }
+ $o.= html_writer::end_tag('div');
+
+ // Output section completion data
+ if ($total > 0) {
+ $a = new stdClass;
+ $a->complete = $complete;
+ $a->total = $total;
+
+ $o.= html_writer::start_tag('div', array('class' => 'section-summary-activities mdl-right'));
+ $o.= html_writer::tag('span', get_string('progresstotal', 'completion', $a), array('class' => 'activity-count'));
+ $o.= html_writer::end_tag('div');
+ }
+
+ return $o;
+ }
+
+ /**
+ * If section is not visible, display the message about that ('Not available
+ * until...', that sort of thing). Otherwise, returns blank.
+ *
+ * For users with the ability to view hidden sections, it shows the
+ * information even though you can view the section and also may include
+ * slightly fuller information (so that teachers can tell when sections
+ * are going to be unavailable etc). This logic is the same as for
+ * activities.
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @param bool $canviewhidden True if user can view hidden sections
+ * @return string HTML to output
+ */
+ protected function section_availability_message($section, $canviewhidden) {
+ global $CFG;
+ $o = '';
+ if (!$section->uservisible) {
+ $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
+ // Note: We only get to this function if availableinfo is non-empty,
+ // so there is definitely something to print.
+ $o .= $section->availableinfo;
+ $o .= html_writer::end_tag('div');
+ } else if ($canviewhidden && !empty($CFG->enableavailability) && $section->visible) {
+ $ci = new condition_info_section($section);
+ $fullinfo = $ci->get_full_information();
+ if ($fullinfo) {
+ $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
+ $o .= get_string(
+ ($section->showavailability ? 'userrestriction_visible' : 'userrestriction_hidden'),
+ 'condition', $fullinfo);
+ $o .= html_writer::end_tag('div');
+ }
+ }
+ return $o;
+ }
+
+ /**
+ * Show if something is on on the course clipboard (moving around)
+ *
+ * @param stdClass $course The course entry from DB
+ * @param int $sectionno The section number in the coruse which is being dsiplayed
+ * @return string HTML to output.
+ */
+ protected function course_activity_clipboard($course, $sectionno = null) {
+ global $USER;
+
+ $o = '';
+ // If currently moving a file then show the current clipboard.
+ if (ismoving($course->id)) {
+ $url = new moodle_url('/course/mod.php',
+ array('sesskey' => sesskey(),
+ 'cancelcopy' => true,
+ 'sr' => $sectionno,
+ )
+ );
+
+ $o.= html_writer::start_tag('div', array('class' => 'clipboard'));
+ $o.= strip_tags(get_string('activityclipboard', '', $USER->activitycopyname));
+ $o.= ' ('.html_writer::link($url, get_string('cancel')).')';
+ $o.= html_writer::end_tag('div');
+ }
+
+ return $o;
+ }
+
+ /**
+ * Generate next/previous section links for naviation
+ *
+ * @param stdClass $course The course entry from DB
+ * @param array $sections The course_sections entries from the DB
+ * @param int $sectionno The section number in the coruse which is being dsiplayed
+ * @return array associative array with previous and next section link
+ */
+ 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;
+
+ $links = array('previous' => '', 'next' => '');
+ $back = $sectionno - 1;
+ while ($back > 0 and empty($links['previous'])) {
+ if ($canviewhidden || $sections[$back]->uservisible) {
+ $params = array();
+ if (!$sections[$back]->visible) {
+ $params = array('class' => 'dimmed_text');
+ }
+ $previouslink = html_writer::tag('span', $this->output->larrow(), array('class' => 'larrow'));
+ $previouslink .= get_section_name($course, $sections[$back]);
+ $links['previous'] = html_writer::link(course_get_url($course, $back), $previouslink, $params);
+ }
+ $back--;
+ }
+
+ $forward = $sectionno + 1;
+ while ($forward <= $course->numsections and empty($links['next'])) {
+ if ($canviewhidden || $sections[$forward]->uservisible) {
+ $params = array();
+ if (!$sections[$forward]->visible) {
+ $params = array('class' => 'dimmed_text');
+ }
+ $nextlink = get_section_name($course, $sections[$forward]);
+ $nextlink .= html_writer::tag('span', $this->output->rarrow(), array('class' => 'rarrow'));
+ $links['next'] = html_writer::link(course_get_url($course, $forward), $nextlink, $params);
+ }
+ $forward++;
+ }
+
+ return $links;
+ }
+
+ /**
+ * Generate the header html of a stealth section
+ *
+ * @param int $sectionno The section number in the coruse which is being dsiplayed
+ * @return string HTML to output.
+ */
+ protected function stealth_section_header($sectionno) {
+ $o = '';
+ $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix orphaned hidden'));
+ $o.= html_writer::tag('div', '', array('class' => 'left side'));
+ $o.= html_writer::tag('div', '', array('class' => 'right side'));
+ $o.= html_writer::start_tag('div', array('class' => 'content'));
+ $o.= $this->output->heading(get_string('orphanedactivities'), 3, 'sectionname');
+ return $o;
+ }
+
+ /**
+ * Generate footer html of a stealth section
+ *
+ * @return string HTML to output.
+ */
+ protected function stealth_section_footer() {
+ $o = html_writer::end_tag('div');
+ $o.= html_writer::end_tag('li');
+ return $o;
+ }
+
+ /**
+ * Generate the html for a hidden section
+ *
+ * @param int $sectionno The section number in the coruse which is being dsiplayed
+ * @return string HTML to output.
+ */
+ protected function section_hidden($sectionno) {
+ $o = '';
+ $o.= html_writer::start_tag('li', array('id' => 'section-'.$sectionno, 'class' => 'section main clearfix hidden'));
+ $o.= html_writer::tag('div', '', array('class' => 'left side'));
+ $o.= html_writer::tag('div', '', array('class' => 'right side'));
+ $o.= html_writer::start_tag('div', array('class' => 'content'));
+ $o.= get_string('notavailable');
+ $o.= html_writer::end_tag('div');
+ $o.= html_writer::end_tag('li');
+ return $o;
+ }
+
+ /**
+ * Output the html for a single section page .
+ *
+ * @param stdClass $course The course entry from DB
+ * @param array $sections (argument not used)
+ * @param array $mods (argument not used)
+ * @param array $modnames (argument not used)
+ * @param array $modnamesused (argument not used)
+ * @param int $displaysection The section number in the course which is being displayed
+ */
+ public function print_single_section_page($course, $sections, $mods, $modnames, $modnamesused, $displaysection) {
+ 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))) {
+ // This section doesn't exist
+ print_error('unknowncoursesection', 'error', null, $course->fullname);
+ return;
+ }
+
+ if (!$sectioninfo->uservisible) {
+ if (!$course->hiddensections) {
+ echo $this->start_section_list();
+ echo $this->section_hidden($displaysection);
+ echo $this->end_section_list();
+ }
+ // Can't view this section.
+ return;
+ }
+
+ // Copy activity clipboard..
+ echo $this->course_activity_clipboard($course, $displaysection);
+ $thissection = $modinfo->get_section_info(0);
+ if ($thissection->summary or !empty($modinfo->sections[0]) or $PAGE->user_is_editing()) {
+ echo $this->start_section_list();
+ echo $this->section_header($thissection, $course, true, $displaysection);
+ print_section($course, $thissection, null, null, true, "100%", false, $displaysection);
+ if ($PAGE->user_is_editing()) {
+ print_section_add_menus($course, 0, null, false, false, $displaysection);
+ }
+ echo $this->section_footer();
+ echo $this->end_section_list();
+ }
+
+ // Start single-section div
+ echo html_writer::start_tag('div', array('class' => 'single-section'));
+
+ // The requested section page.
+ $thissection = $modinfo->get_section_info($displaysection);
+
+ // Title with section navigation links.
+ $sectionnavlinks = $this->get_nav_links($course, $modinfo->get_section_info_all(), $displaysection);
+ $sectiontitle = '';
+ $sectiontitle .= html_writer::start_tag('div', array('class' => 'section-navigation header headingblock'));
+ $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
+ $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
+ // Title attributes
+ $titleattr = 'mdl-align title';
+ if (!$thissection->visible) {
+ $titleattr .= ' dimmed_text';
+ }
+ $sectiontitle .= html_writer::tag('div', get_section_name($course, $displaysection), array('class' => $titleattr));
+ $sectiontitle .= html_writer::end_tag('div');
+ echo $sectiontitle;
+
+ // Now the list of sections..
+ echo $this->start_section_list();
+
+ echo $this->section_header($thissection, $course, true, $displaysection);
+ // Show completion help icon.
+ $completioninfo = new completion_info($course);
+ echo $completioninfo->display_help_icon();
+
+ print_section($course, $thissection, null, null, true, '100%', false, $displaysection);
+ if ($PAGE->user_is_editing()) {
+ print_section_add_menus($course, $displaysection, null, false, false, $displaysection);
+ }
+ echo $this->section_footer();
+ echo $this->end_section_list();
+
+ // Display section bottom navigation.
+ $courselink = html_writer::link(course_get_url($course), get_string('returntomaincoursepage'));
+ $sectionbottomnav = '';
+ $sectionbottomnav .= html_writer::start_tag('div', array('class' => 'section-navigation mdl-bottom'));
+ $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
+ $sectionbottomnav .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
+ $sectionbottomnav .= html_writer::tag('div', $courselink, array('class' => 'mdl-align'));
+ $sectionbottomnav .= html_writer::end_tag('div');
+ echo $sectionbottomnav;
+
+ // close single-section div.
+ echo html_writer::end_tag('div');
+ }
+
+ /**
+ * Output the html for a multiple section page
+ *
+ * @param stdClass $course The course entry from DB
+ * @param array $sections (argument not used)
+ * @param array $mods (argument not used)
+ * @param array $modnames (argument not used)
+ * @param array $modnamesused (argument not used)
+ */
+ public function print_multiple_section_page($course, $sections, $mods, $modnames, $modnamesused) {
+ 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.
+ $completioninfo = new completion_info($course);
+ echo $completioninfo->display_help_icon();
+ echo $this->output->heading($this->page_title(), 2, 'accesshide');
+
+ // Copy activity clipboard..
+ echo $this->course_activity_clipboard($course, 0);
+
+ // Now the list of sections..
+ echo $this->start_section_list();
+
+ foreach ($modinfo->get_section_info_all() as $section => $thissection) {
+ if ($section == 0) {
+ // 0-section is displayed a little different then the others
+ if ($thissection->summary or !empty($modinfo->sections[0]) or $PAGE->user_is_editing()) {
+ echo $this->section_header($thissection, $course, false, 0);
+ print_section($course, $thissection, null, null, true, "100%", false, 0);
+ if ($PAGE->user_is_editing()) {
+ print_section_add_menus($course, 0, null, false, false, 0);
+ }
+ echo $this->section_footer();
+ }
+ continue;
+ }
+ if ($section > $course->numsections) {
+ // activities inside this section are 'orphaned', this section will be printed as 'stealth' below
+ continue;
+ }
+ // Show the section if the user is permitted to access it, OR if it's not available
+ // but showavailability is turned on (and there is some available info text).
+ $showsection = $thissection->uservisible ||
+ ($thissection->visible && !$thissection->available && $thissection->showavailability
+ && !empty($thissection->availableinfo));
+ if (!$showsection) {
+ // Hidden section message is overridden by 'unavailable' control
+ // (showavailability option).
+ if (!$course->hiddensections && $thissection->available) {
+ echo $this->section_hidden($section);
+ }
+
+ continue;
+ }
+
+ if (!$PAGE->user_is_editing() && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+ // Display section summary only.
+ echo $this->section_summary($thissection, $course, null);
+ } else {
+ echo $this->section_header($thissection, $course, false, 0);
+ if ($thissection->uservisible) {
+ print_section($course, $thissection, null, null, true, "100%", false, 0);
+ if ($PAGE->user_is_editing()) {
+ print_section_add_menus($course, $section, null, false, false, 0);
+ }
+ }
+ echo $this->section_footer();
+ }
+ }
+
+ if ($PAGE->user_is_editing() and has_capability('moodle/course:update', $context)) {
+ // Print stealth sections if present.
+ foreach ($modinfo->get_section_info_all() as $section => $thissection) {
+ if ($section <= $course->numsections or empty($modinfo->sections[$section])) {
+ // this is not stealth section or it is empty
+ continue;
+ }
+ echo $this->stealth_section_header($section);
+ print_section($course, $thissection, null, null, true, "100%", false, 0);
+ echo $this->stealth_section_footer();
+ }
+
+ echo $this->end_section_list();
+
+ echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
+
+ // Increase number of sections.
+ $straddsection = get_string('increasesections', 'moodle');
+ $url = new moodle_url('/course/changenumsections.php',
+ array('courseid' => $course->id,
+ 'increase' => true,
+ 'sesskey' => sesskey()));
+ $icon = $this->output->pix_icon('t/switch_plus', $straddsection);
+ echo html_writer::link($url, $icon.get_accesshide($straddsection), array('class' => 'increase-sections'));
+
+ if ($course->numsections > 0) {
+ // Reduce number of sections sections.
+ $strremovesection = get_string('reducesections', 'moodle');
+ $url = new moodle_url('/course/changenumsections.php',
+ array('courseid' => $course->id,
+ 'increase' => false,
+ 'sesskey' => sesskey()));
+ $icon = $this->output->pix_icon('t/switch_minus', $strremovesection);
+ echo html_writer::link($url, $icon.get_accesshide($strremovesection), array('class' => 'reduce-sections'));
+ }
+
+ echo html_writer::end_tag('div');
+ } else {
+ echo $this->end_section_list();
+ }
+
+ }
+
+ /**
+ * Generate html for a section summary text
+ *
+ * @param stdClass $section The course_section entry from DB
+ * @return string HTML to output.
+ */
+ protected function format_summary_text($section) {
+ $context = context_course::instance($section->course);
+ $summarytext = file_rewrite_pluginfile_urls($section->summary, 'pluginfile.php',
+ $context->id, 'course', 'section', $section->id);
+
+ $options = new stdClass();
+ $options->noclean = true;
+ $options->overflowdiv = true;
+ return format_text($summarytext, $section->summaryformat, $options);
+ }
+
+ /**
+ * Is the section passed in the current section?
+ *
+ * @deprecated since 2.4
+ * @see format_base::is_section_current()
+ *
+ * @param stdClass $course The course entry from DB
+ * @param stdClass $section The course_section entry from the DB
+ * @return bool true if the section is current
+ */
+ protected final function is_section_current($section, $course) {
+ debugging('Function format_section_renderer_base::is_section_current() is deprecated. '.
+ 'Use course_get_format($course)->is_section_current($section) instead', DEBUG_DEVELOPER);
+ return course_get_format($course)->is_section_current($section);
+ }
+}
diff --git a/format/scorm/format.php b/format/scorm/format.php
new file mode 100644
index 0000000..465f7f7
--- /dev/null
+++ b/format/scorm/format.php
@@ -0,0 +1,17 @@
+format;
+ require_once($CFG->dirroot.'/mod/'.$module.'/locallib.php');
+
+ $strgroups = get_string('groups');
+ $strgroupmy = get_string('groupmy');
+ $editing = $PAGE->user_is_editing();
+
+ $moduleformat = $module.'_course_format_display';
+ if (function_exists($moduleformat)) {
+ $moduleformat($USER,$course);
+ } else {
+ echo $OUTPUT->notification('The module '. $module. ' does not support single activity course format');
+ }
diff --git a/format/scorm/lang/en/format_scorm.php b/format/scorm/lang/en/format_scorm.php
new file mode 100644
index 0000000..8b684c1
--- /dev/null
+++ b/format/scorm/lang/en/format_scorm.php
@@ -0,0 +1,27 @@
+.
+
+/**
+ * Strings for component 'format_scorm', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package format_scorm
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['sectionname'] = 'SCORM';
+$string['pluginname'] = 'SCORM format';
diff --git a/format/scorm/lib.php b/format/scorm/lib.php
new file mode 100644
index 0000000..60e9a26
--- /dev/null
+++ b/format/scorm/lib.php
@@ -0,0 +1,101 @@
+.
+
+/**
+ * This file contains main class for the course format SCORM
+ *
+ * @since 2.0
+ * @package format_scorm
+ * @copyright 2009 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
+
+/**
+ * Main class for the Scorm course format
+ *
+ * @package format_scorm
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_scorm extends format_base {
+
+ /**
+ * The URL to use for the specified course
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if null the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ public function get_view_url($section, $options = array()) {
+ if (!empty($options['navigation']) && $section !== null) {
+ return null;
+ }
+ return new moodle_url('/course/view.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ // Scorm course format does not extend course navigation
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * @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 array(
+ BLOCK_POS_LEFT => array(),
+ BLOCK_POS_RIGHT => array('news_items', 'recent_activity', 'calendar_upcoming')
+ );
+ }
+
+ /**
+ * Allows course format to execute code on moodle_page::set_course()
+ *
+ * If user is on course view page and there is no scorm module added to the course
+ * and the user has 'moodle/course:update' capability, redirect to create module
+ * form. This function is executed before the output starts
+ *
+ * @param moodle_page $page instance of page calling set_course
+ */
+ public function page_set_course(moodle_page $page) {
+ global $PAGE;
+ if ($PAGE == $page && $page->has_set_url() &&
+ $page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+ $modinfo = get_fast_modinfo($this->courseid);
+ if (empty($modinfo->instances['scorm'])
+ && has_capability('moodle/course:update', context_course::instance($this->courseid))) {
+ // Redirect to create a new activity
+ $url = new moodle_url('/course/modedit.php',
+ array('course' => $this->courseid, 'section' => 0, 'add' => 'scorm'));
+ redirect($url);
+ }
+ }
+ }
+}
diff --git a/format/scorm/version.php b/format/scorm/version.php
new file mode 100644
index 0000000..d1641c9
--- /dev/null
+++ b/format/scorm/version.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Version details
+ *
+ * @package format
+ * @subpackage scorm
+ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2012112900; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2012112900; // Requires this Moodle version
+$plugin->component = 'format_scorm'; // Full name of the plugin (used for diagnostics)
diff --git a/format/social/format.php b/format/social/format.php
new file mode 100644
index 0000000..b661c22
--- /dev/null
+++ b/format/social/format.php
@@ -0,0 +1,35 @@
+user_is_editing();
+
+ if ($forum = forum_get_course_forum($course->id, 'social')) {
+
+ $cm = get_coursemodule_from_instance('forum', $forum->id);
+ $context = context_module::instance($cm->id);
+
+ /// Print forum intro above posts MDL-18483
+ if (trim($forum->intro) != '') {
+ $options = new stdClass();
+ $options->para = false;
+ $introcontent = format_module_intro('forum', $forum, $cm->id);
+
+ if ($PAGE->user_is_editing() && has_capability('moodle/course:update', $context)) {
+ $streditsummary = get_string('editsummary');
+ $introcontent .= '
';
+ forum_print_latest_discussions($course, $forum, 10, 'plain', '', false);
+
+ } else {
+ echo $OUTPUT->notification('Could not find or create a social forum here');
+ }
diff --git a/format/social/lang/en/format_social.php b/format/social/lang/en/format_social.php
new file mode 100644
index 0000000..0316353
--- /dev/null
+++ b/format/social/lang/en/format_social.php
@@ -0,0 +1,27 @@
+.
+
+/**
+ * Strings for component 'format_social', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package format_social
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['sectionname'] = 'section';
+$string['pluginname'] = 'Social format';
diff --git a/format/social/lib.php b/format/social/lib.php
new file mode 100644
index 0000000..3c84a15
--- /dev/null
+++ b/format/social/lib.php
@@ -0,0 +1,78 @@
+.
+
+/**
+ * This file contains main class for the course format Social
+ *
+ * @since 2.0
+ * @package format_social
+ * @copyright 2009 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
+
+/**
+ * Main class for the Social course format
+ *
+ * @package format_social
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_social extends format_base {
+
+ /**
+ * The URL to use for the specified course
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if null the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ public function get_view_url($section, $options = array()) {
+ if (!empty($options['navigation']) && $section !== null) {
+ return null;
+ }
+ return new moodle_url('/course/view.php', array('id' => $this->courseid));
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ // Social course format does not extend navigation, it uses social_activities block instead
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * @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 array(
+ BLOCK_POS_LEFT => array(),
+ BLOCK_POS_RIGHT => array('search_forums', 'calendar_upcoming', 'social_activities',
+ 'recent_activity', 'course_list')
+ );
+ }
+}
diff --git a/format/social/version.php b/format/social/version.php
new file mode 100644
index 0000000..645f02a
--- /dev/null
+++ b/format/social/version.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Version details
+ *
+ * @package format
+ * @subpackage social
+ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2012112900; // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires = 2012112900; // Requires this Moodle version
+$plugin->component = 'format_social'; // Full name of the plugin (used for diagnostics)
diff --git a/format/topics/format.js b/format/topics/format.js
new file mode 100644
index 0000000..172d1d9
--- /dev/null
+++ b/format/topics/format.js
@@ -0,0 +1,73 @@
+// Javascript functions for Topics course format
+
+M.course = M.course || {};
+
+M.course.format = M.course.format || {};
+
+/**
+ * Get sections config for this format
+ *
+ * The section structure is:
+ *
+ *
...
+ *
...
+ * ...
+ *
+ *
+ * @return {object} section list configuration
+ */
+M.course.format.get_config = function() {
+ return {
+ container_node : 'ul',
+ container_class : 'topics',
+ section_node : 'li',
+ section_class : 'section'
+ };
+}
+
+/**
+ * Swap section
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {string} node1 node to swap to
+ * @param {string} node2 node to swap with
+ * @return {NodeList} section list
+ */
+M.course.format.swap_sections = function(Y, node1, node2) {
+ var CSS = {
+ COURSECONTENT : 'course-content',
+ SECTIONADDMENUS : 'section_add_menus'
+ };
+
+ var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
+ // Swap menus.
+ sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
+}
+
+/**
+ * Process sections after ajax response
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.course.format.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+ var CSS = {
+ SECTIONNAME : 'sectionname'
+ };
+
+ if (response.action == 'move') {
+ // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+ if (sectionfrom > sectionto) {
+ var temp = sectionto;
+ sectionto = sectionfrom;
+ sectionfrom = temp;
+ }
+ // Update titles in all affected sections.
+ for (var i = sectionfrom; i <= sectionto; i++) {
+ sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+ }
+ }
+}
diff --git a/format/topics/format.php b/format/topics/format.php
new file mode 100644
index 0000000..cf2c052
--- /dev/null
+++ b/format/topics/format.php
@@ -0,0 +1,60 @@
+.
+
+/**
+ * Topics course format. Display the whole course as "topics" made of modules.
+ *
+ * @package format_topics
+ * @copyright 2006 The Open University
+ * @author N.D.Freear@open.ac.uk, and others.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/filelib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+// Horrible backwards compatible parameter aliasing..
+if ($topic = optional_param('topic', 0, PARAM_INT)) {
+ $url = $PAGE->url;
+ $url->param('section', $topic);
+ debugging('Outdated topic param passed to course/view.php', DEBUG_DEVELOPER);
+ redirect($url);
+}
+// End backwards-compatible aliasing..
+
+$context = context_course::instance($course->id);
+
+if (($marker >=0) && has_capability('moodle/course:setcurrentsection', $context) && confirm_sesskey()) {
+ $course->marker = $marker;
+ course_set_marker($course->id, $marker);
+}
+
+// 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');
+
+if (!empty($displaysection)) {
+ $renderer->print_single_section_page($course, null, null, null, null, $displaysection);
+} else {
+ $renderer->print_multiple_section_page($course, null, null, null, null);
+}
+
+// Include course format js module
+$PAGE->requires->js('/course/format/topics/format.js');
diff --git a/format/topics/lang/en/format_topics.php b/format/topics/lang/en/format_topics.php
new file mode 100644
index 0000000..f9766d9
--- /dev/null
+++ b/format/topics/lang/en/format_topics.php
@@ -0,0 +1,33 @@
+.
+
+/**
+ * Strings for component 'format_topics', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package format_topics
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['currentsection'] = 'This topic';
+$string['sectionname'] = 'Topic';
+$string['pluginname'] = 'Topics format';
+$string['section0name'] = 'General';
+$string['page-course-view-topics'] = 'Any course main page in topics format';
+$string['page-course-view-topics-x'] = 'Any course page in topics format';
+$string['hidefromothers'] = 'Hide topic';
+$string['showfromothers'] = 'Show topic';
diff --git a/format/topics/lib.php b/format/topics/lib.php
new file mode 100644
index 0000000..debe045
--- /dev/null
+++ b/format/topics/lib.php
@@ -0,0 +1,299 @@
+.
+
+/**
+ * This file contains main class for the course format Topic
+ *
+ * @since 2.0
+ * @package format_topics
+ * @copyright 2009 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
+
+/**
+ * Main class for the Topics course format
+ *
+ * @package format_topics
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_topics extends format_base {
+
+ /**
+ * Returns true if this course format uses sections
+ *
+ * @return bool
+ */
+ public function uses_sections() {
+ return true;
+ }
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * Use section name is specified by user. Otherwise use default ("Topic #")
+ *
+ * @param int|stdClass $section Section object from database or just field section.section
+ * @return string Display name that the course format prefers, e.g. "Topic 2"
+ */
+ public function get_section_name($section) {
+ $section = $this->get_section($section);
+ if ((string)$section->name !== '') {
+ return format_string($section->name, true,
+ array('context' => context_course::instance($this->courseid)));
+ } else if ($section->section == 0) {
+ return get_string('section0name', 'format_topics');
+ } else {
+ return get_string('topic').' '.$section->section;
+ }
+ }
+
+ /**
+ * The URL to use for the specified course (with section)
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if omitted the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ 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 = $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;
+ }
+
+ /**
+ * Returns the information about the ajax support in the given source format
+ *
+ * The returned object's property (boolean)capable indicates that
+ * the course format supports Moodle course ajax features.
+ * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+ *
+ * @return stdClass
+ */
+ public function supports_ajax() {
+ $ajaxsupport = new stdClass();
+ $ajaxsupport->capable = true;
+ $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
+ return $ajaxsupport;
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ global $PAGE;
+ // if section is specified in course/view.php, make sure it is expanded in navigation
+ if ($navigation->includesectionnum === false) {
+ $selectedsection = optional_param('section', null, PARAM_INT);
+ if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
+ $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+ $navigation->includesectionnum = $selectedsection;
+ }
+ }
+
+ // check if there are callbacks to extend course navigation
+ parent::extend_course_navigation($navigation, $node);
+ }
+
+ /**
+ * Custom action after section has been moved in AJAX mode
+ *
+ * Used in course/rest.php
+ *
+ * @return array This will be passed in ajax respose
+ */
+ function ajax_section_move() {
+ global $PAGE;
+ $titles = array();
+ $course = $this->get_course();
+ $modinfo = get_fast_modinfo($course);
+ $renderer = $this->get_renderer($PAGE);
+ if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+ foreach ($sections as $number => $section) {
+ $titles[$number] = $renderer->section_title($section, $course);
+ }
+ }
+ return array('sectiontitles' => $titles, 'action' => 'move');
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * @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 array(
+ BLOCK_POS_LEFT => array(),
+ BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
+ );
+ }
+
+ /**
+ * Definitions of the additional options that this course format uses for course
+ *
+ * Topics format uses the following options:
+ * - 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');
+ $max = $courseconfig->maxsections;
+ if (!isset($max) || !is_numeric($max)) {
+ $max = 52;
+ }
+ $sectionmenu = array();
+ for ($i = 0; $i <= $max; $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
+ *
+ * In case if course format was changed to 'topics', we try to copy options
+ * 'coursedisplay', 'numsections' and 'hiddensections' from the previous format.
+ * If previous course format did not have 'numsections' option, we populate it with the
+ * current number of sections
+ *
+ * @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) {
+ global $DB;
+ if ($oldcourse !== null) {
+ $data = (array)$data;
+ $oldcourse = (array)$oldcourse;
+ $options = $this->course_format_options();
+ foreach ($options as $key => $unused) {
+ if (!array_key_exists($key, $data)) {
+ if (array_key_exists($key, $oldcourse)) {
+ $data[$key] = $oldcourse[$key];
+ } else if ($key === 'numsections') {
+ // If previous format does not have the field 'numsections'
+ // and $data['numsections'] is not set,
+ // we fill it with the maximum section number from the DB
+ $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+ WHERE course = ?', array($this->courseid));
+ if ($maxsection) {
+ // If there are no sections, or just default 0-section, 'numsections' will be set to default
+ $data['numsections'] = $maxsection;
+ }
+ }
+ }
+ }
+ }
+ return $this->update_format_options($data);
+ }
+}
diff --git a/format/topics/renderer.php b/format/topics/renderer.php
new file mode 100644
index 0000000..e293a9d
--- /dev/null
+++ b/format/topics/renderer.php
@@ -0,0 +1,105 @@
+.
+
+/**
+ * Renderer for outputting the topics course format.
+ *
+ * @package format_topics
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot.'/course/format/renderer.php');
+
+/**
+ * Basic renderer for topics format.
+ *
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_topics_renderer extends format_section_renderer_base {
+
+ /**
+ * Generate the starting container html for a list of sections
+ * @return string HTML to output.
+ */
+ protected function start_section_list() {
+ return html_writer::start_tag('ul', array('class' => 'topics'));
+ }
+
+ /**
+ * Generate the closing container html for a list of sections
+ * @return string HTML to output.
+ */
+ protected function end_section_list() {
+ return html_writer::end_tag('ul');
+ }
+
+ /**
+ * Generate the title for this section page
+ * @return string the page title
+ */
+ protected function page_title() {
+ return get_string('topicoutline');
+ }
+
+ /**
+ * Generate the edit controls of a section
+ *
+ * @param stdClass $course The course entry from DB
+ * @param stdClass $section The course_section entry from DB
+ * @param bool $onsectionpage true if being printed on a section page
+ * @return array of links with edit controls
+ */
+ protected function section_edit_controls($course, $section, $onsectionpage = false) {
+ global $PAGE;
+
+ if (!$PAGE->user_is_editing()) {
+ return array();
+ }
+
+ $coursecontext = context_course::instance($course->id);
+
+ if ($onsectionpage) {
+ $url = course_get_url($course, $section->section);
+ } else {
+ $url = course_get_url($course);
+ }
+ $url->param('sesskey', sesskey());
+
+ $controls = array();
+ if (has_capability('moodle/course:setcurrentsection', $coursecontext)) {
+ if ($course->marker == $section->section) { // Show the "light globe" on/off.
+ $url->param('marker', 0);
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marked'),
+ 'class' => 'icon ', 'alt' => get_string('markedthistopic'))),
+ array('title' => get_string('markedthistopic'), 'class' => 'editing_highlight'));
+ } else {
+ $url->param('marker', $section->section);
+ $controls[] = html_writer::link($url,
+ html_writer::empty_tag('img', array('src' => $this->output->pix_url('i/marker'),
+ 'class' => 'icon', 'alt' => get_string('markthistopic'))),
+ array('title' => get_string('markthistopic'), 'class' => 'editing_highlight'));
+ }
+ }
+
+ return array_merge($controls, parent::section_edit_controls($course, $section, $onsectionpage));
+ }
+}
diff --git a/format/topics/styles.css b/format/topics/styles.css
new file mode 100644
index 0000000..432ad96
--- /dev/null
+++ b/format/topics/styles.css
@@ -0,0 +1,10 @@
+.course-content ul.topics {margin:0;}
+.course-content ul.topics li.section {list-style: none;margin:0 0 5px 0;padding:0;}
+.course-content ul.topics li.section .content {margin:0 40px;}
+.course-content ul.topics li.section .left {float:left;}
+.course-content ul.topics li.section .right {float:right;}
+.course-content ul.topics li.section .left,
+.course-content ul.topics li.section .right {width:40px;text-align:center;padding: 6px 0;}
+.course-content ul.topics li.section .right img.icon { padding: 0 0 4px 0;}
+.course-content ul.topics li.section .left .section-handle img.icon { padding:0; vertical-align: baseline; }
+.jumpmenu {text-align:center;}
diff --git a/format/topics/version.php b/format/topics/version.php
new file mode 100644
index 0000000..dff32bf
--- /dev/null
+++ b/format/topics/version.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Version details
+ *
+ * @package format
+ * @subpackage topics
+ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2012112900; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2012112900; // Requires this Moodle version.
+$plugin->component = 'format_topics'; // Full name of the plugin (used for diagnostics).
diff --git a/format/upgrade.txt b/format/upgrade.txt
new file mode 100644
index 0000000..4c5d131
--- /dev/null
+++ b/format/upgrade.txt
@@ -0,0 +1,29 @@
+This files describes API changes for course formats
+
+Overview of this plugin type at http://docs.moodle.org/dev/Course_formats
+
+=== 2.4 ===
+
+Course format API has been changed significantly. Instead of implementing callbacks course formats
+may overwrite the class format_base. See format_legacy class for a template for upgrading course
+format.
+
+* Function settings_navigation::add_course_editing_links() is completely removed, course format
+ functions callback_XXXX_request_key() are no longer used (where XXXX is the course format name)
+* functions get_generic_section_name(), get_all_sections(), add_mod_to_section(), get_all_mods()
+ are deprecated. See their phpdocs in lib/deprecatedlib.php on how to replace them
+* Course formats may now have their settings.php file as the most of other plugin types
+* Function format_section_renderer_base::is_section_current() is deprecated, overwrite/use
+ function is_section_current in format class
+
+=== 2.3 ===
+
+* The new $course->coursedisplay option was introduced, users can now choose to display
+ a section at a time if the course formats support it:
+ - COURSE_DISPLAY_SINGLEPAGE indicates the teacher has chosen to display all sections on one page
+ - COURSE_DISPLAY_MULTIPAGE indicates the teacher has chose to have seperate pages with each section.
+
+* The parameter for 'currently active section' was standardised in core:
+ - The course format is passed the currently live section through the $displaysection varaible to format.php
+ - A 'section' paramter is the standardised way to pass around the current section in a course
+ - Navigation no longer looks for custom parameters defined in callback_format_request_key
diff --git a/format/weeks/format.js b/format/weeks/format.js
new file mode 100644
index 0000000..6433702
--- /dev/null
+++ b/format/weeks/format.js
@@ -0,0 +1,73 @@
+// Javascript functions for Weeks course format
+
+M.course = M.course || {};
+
+M.course.format = M.course.format || {};
+
+/**
+ * Get sections config for this format
+ *
+ * The section structure is:
+ *
+ *
...
+ *
...
+ * ...
+ *
+ *
+ * @return {object} section list configuration
+ */
+M.course.format.get_config = function() {
+ return {
+ container_node : 'ul',
+ container_class : 'weeks',
+ section_node : 'li',
+ section_class : 'section'
+ };
+}
+
+/**
+ * Swap section
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {string} node1 node to swap to
+ * @param {string} node2 node to swap with
+ * @return {NodeList} section list
+ */
+M.course.format.swap_sections = function(Y, node1, node2) {
+ var CSS = {
+ COURSECONTENT : 'course-content',
+ SECTIONADDMENUS : 'section_add_menus'
+ };
+
+ var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
+ // Swap menus.
+ sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
+}
+
+/**
+ * Process sections after ajax response
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.course.format.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+ var CSS = {
+ SECTIONNAME : 'sectionname'
+ };
+
+ if (response.action == 'move') {
+ // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+ if (sectionfrom > sectionto) {
+ var temp = sectionto;
+ sectionto = sectionfrom;
+ sectionfrom = temp;
+ }
+ // Update titles in all affected sections.
+ for (var i = sectionfrom; i <= sectionto; i++) {
+ sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+ }
+ }
+}
diff --git a/format/weeks/format.php b/format/weeks/format.php
new file mode 100644
index 0000000..2f98855
--- /dev/null
+++ b/format/weeks/format.php
@@ -0,0 +1,52 @@
+.
+
+/**
+ * Weeks course format. Display the whole course as "weeks" made of modules.
+ *
+ * @package format_weeks
+ * @copyright 2006 The Open University
+ * @author N.D.Freear@open.ac.uk, and others.
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir.'/filelib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+// Horrible backwards compatible parameter aliasing..
+if ($week = optional_param('week', 0, PARAM_INT)) {
+ $url = $PAGE->url;
+ $url->param('section', $week);
+ debugging('Outdated week param passed to course/view.php', DEBUG_DEVELOPER);
+ redirect($url);
+}
+// 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');
+
+if (!empty($displaysection)) {
+ $renderer->print_single_section_page($course, null, null, null, null, $displaysection);
+} else {
+ $renderer->print_multiple_section_page($course, null, null, null, null);
+}
+
+$PAGE->requires->js('/course/format/weeks/format.js');
diff --git a/format/weeks/lang/en/format_weeks.php b/format/weeks/lang/en/format_weeks.php
new file mode 100644
index 0000000..fd6c24d
--- /dev/null
+++ b/format/weeks/lang/en/format_weeks.php
@@ -0,0 +1,33 @@
+.
+
+/**
+ * Strings for component 'format_weeks', language 'en', branch 'MOODLE_20_STABLE'
+ *
+ * @package format_weeks
+ * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['currentsection'] = 'This week';
+$string['sectionname'] = 'Week';
+$string['pluginname'] = 'Weekly format';
+$string['section0name'] = 'General';
+$string['page-course-view-weeks'] = 'Any course main page in weeks format';
+$string['page-course-view-weeks-x'] = 'Any course page in weeks format';
+$string['hidefromothers'] = 'Hide week';
+$string['showfromothers'] = 'Show week';
diff --git a/format/weeks/lib.php b/format/weeks/lib.php
new file mode 100644
index 0000000..40686d4
--- /dev/null
+++ b/format/weeks/lib.php
@@ -0,0 +1,349 @@
+.
+
+/**
+ * This file contains main class for the course format Weeks
+ *
+ * @since 2.0
+ * @package format_weeks
+ * @copyright 2009 Sam Hemelryk
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
+
+/**
+ * Main class for the Weeks course format
+ *
+ * @package format_weeks
+ * @copyright 2012 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_weeks extends format_base {
+
+ /**
+ * Returns true if this course format uses sections
+ *
+ * @return bool
+ */
+ public function uses_sections() {
+ return true;
+ }
+
+ /**
+ * Returns the display name of the given section that the course prefers.
+ *
+ * @param int|stdClass $section Section object from database or just field section.section
+ * @return string Display name that the course format prefers, e.g. "Topic 2"
+ */
+ public function get_section_name($section) {
+ $section = $this->get_section($section);
+ if ((string)$section->name !== '') {
+ // Return the name the user set.
+ return format_string($section->name, true, array('context' => context_course::instance($this->courseid)));
+ } else if ($section->section == 0) {
+ // Return the general section.
+ return get_string('section0name', 'format_weeks');
+ } else {
+ $dates = $this->get_section_dates($section);
+
+ // We subtract 24 hours for display purposes.
+ $dates->end = ($dates->end - 86400);
+
+ $dateformat = ' '.get_string('strftimedateshort');
+ $weekday = userdate($dates->start, $dateformat);
+ $endweekday = userdate($dates->end, $dateformat);
+ return $weekday.' - '.$endweekday;
+ }
+ }
+
+ /**
+ * The URL to use for the specified course (with section)
+ *
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * if omitted the course view page is returned
+ * @param array $options options for view URL. At the moment core uses:
+ * 'navigation' (bool) if true and section has no separate page, the function returns null
+ * 'sr' (int) used by multipage formats to specify to which section to return
+ * @return null|moodle_url
+ */
+ 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 = $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;
+ }
+
+ /**
+ * Returns the information about the ajax support in the given source format
+ *
+ * The returned object's property (boolean)capable indicates that
+ * the course format supports Moodle course ajax features.
+ * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+ *
+ * @return stdClass
+ */
+ public function supports_ajax() {
+ $ajaxsupport = new stdClass();
+ $ajaxsupport->capable = true;
+ $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
+ return $ajaxsupport;
+ }
+
+ /**
+ * Loads all of the course sections into the navigation
+ *
+ * @param global_navigation $navigation
+ * @param navigation_node $node The course node within the navigation
+ */
+ public function extend_course_navigation($navigation, navigation_node $node) {
+ global $PAGE;
+ // if section is specified in course/view.php, make sure it is expanded in navigation
+ if ($navigation->includesectionnum === false) {
+ $selectedsection = optional_param('section', null, PARAM_INT);
+ if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
+ $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+ $navigation->includesectionnum = $selectedsection;
+ }
+ }
+ parent::extend_course_navigation($navigation, $node);
+ }
+
+ /**
+ * Custom action after section has been moved in AJAX mode
+ *
+ * Used in course/rest.php
+ *
+ * @return array This will be passed in ajax respose
+ */
+ function ajax_section_move() {
+ global $PAGE;
+ $titles = array();
+ $course = $this->get_course();
+ $modinfo = get_fast_modinfo($course);
+ $renderer = $this->get_renderer($PAGE);
+ if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+ foreach ($sections as $number => $section) {
+ $titles[$number] = $renderer->section_title($section, $course);
+ }
+ }
+ return array('sectiontitles' => $titles, 'action' => 'move');
+ }
+
+ /**
+ * Returns the list of blocks to be automatically added for the newly created course
+ *
+ * @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 array(
+ BLOCK_POS_LEFT => array(),
+ BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
+ );
+ }
+
+ /**
+ * Definitions of the additional options that this course format uses for course
+ *
+ * Weeks format uses the following options:
+ * - 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();
+ $max = $courseconfig->maxsections;
+ if (!isset($max) || !is_numeric($max)) {
+ $max = 52;
+ }
+ for ($i = 0; $i <= $max; $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
+ *
+ * In case if course format was changed to 'weeks', we try to copy options
+ * 'coursedisplay', 'numsections' and 'hiddensections' from the previous format.
+ * If previous course format did not have 'numsections' option, we populate it with the
+ * current number of sections
+ *
+ * @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) {
+ global $DB;
+ if ($oldcourse !== null) {
+ $data = (array)$data;
+ $oldcourse = (array)$oldcourse;
+ $options = $this->course_format_options();
+ foreach ($options as $key => $unused) {
+ if (!array_key_exists($key, $data)) {
+ if (array_key_exists($key, $oldcourse)) {
+ $data[$key] = $oldcourse[$key];
+ } else if ($key === 'numsections') {
+ // If previous format does not have the field 'numsections'
+ // and $data['numsections'] is not set,
+ // we fill it with the maximum section number from the DB
+ $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+ WHERE course = ?', array($this->courseid));
+ if ($maxsection) {
+ // If there are no sections, or just default 0-section, 'numsections' will be set to default
+ $data['numsections'] = $maxsection;
+ }
+ }
+ }
+ }
+ }
+ return $this->update_format_options($data);
+ }
+
+ /**
+ * Return the start and end date of the passed section
+ *
+ * @param int|stdClass|section_info $section section to get the dates for
+ * @return stdClass property start for startdate, property end for enddate
+ */
+ public function get_section_dates($section) {
+ $course = $this->get_course();
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ $oneweekseconds = 604800;
+ // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
+ // savings and the date changes.
+ $startdate = $course->startdate + 7200;
+
+ $dates = new stdClass();
+ $dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1));
+ $dates->end = $dates->start + $oneweekseconds;
+
+ return $dates;
+ }
+
+ /**
+ * Returns true if the specified week is current
+ *
+ * @param int|stdClass|section_info $section
+ * @return bool
+ */
+ public function is_section_current($section) {
+ if (is_object($section)) {
+ $sectionnum = $section->section;
+ } else {
+ $sectionnum = $section;
+ }
+ if ($sectionnum < 1) {
+ return false;
+ }
+ $timenow = time();
+ $dates = $this->get_section_dates($section);
+ return (($timenow >= $dates->start) && ($timenow < $dates->end));
+ }
+}
diff --git a/format/weeks/renderer.php b/format/weeks/renderer.php
new file mode 100644
index 0000000..33590fd
--- /dev/null
+++ b/format/weeks/renderer.php
@@ -0,0 +1,62 @@
+.
+
+/**
+ * Renderer for outputting the weeks course format.
+ *
+ * @package format_weeks
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot.'/course/format/renderer.php');
+require_once($CFG->dirroot.'/course/format/weeks/lib.php');
+
+
+/**
+ * Basic renderer for weeks format.
+ *
+ * @copyright 2012 Dan Poltawski
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class format_weeks_renderer extends format_section_renderer_base {
+ /**
+ * Generate the starting container html for a list of sections
+ * @return string HTML to output.
+ */
+ protected function start_section_list() {
+ return html_writer::start_tag('ul', array('class' => 'weeks'));
+ }
+
+ /**
+ * Generate the closing container html for a list of sections
+ * @return string HTML to output.
+ */
+ protected function end_section_list() {
+ return html_writer::end_tag('ul');
+ }
+
+ /**
+ * Generate the title for this section page
+ * @return string the page title
+ */
+ protected function page_title() {
+ return get_string('weeklyoutline');
+ }
+}
diff --git a/format/weeks/styles.css b/format/weeks/styles.css
new file mode 100644
index 0000000..80d3479
--- /dev/null
+++ b/format/weeks/styles.css
@@ -0,0 +1,10 @@
+.course-content ul.weeks {margin:0;}
+.course-content ul.weeks li.section {list-style: none;margin:0 0 5px 0;padding:0;}
+.course-content ul.weeks li.section .content {margin:0 40px;}
+.course-content ul.weeks li.section .left {float:left;}
+.course-content ul.weeks li.section .right {float:right;}
+.course-content ul.weeks li.section .left,
+.course-content ul.weeks li.section .right {width:40px;text-align:center;padding: 6px 0;}
+.course-content ul.weeks li.section .right img.icon { padding: 0 0 4px 0;}
+.course-content ul.weeks li.section .left .section-handle img.icon { padding:0; vertical-align: baseline; }
+.jumpmenu {text-align:center;}
\ No newline at end of file
diff --git a/format/weeks/version.php b/format/weeks/version.php
new file mode 100644
index 0000000..16971a6
--- /dev/null
+++ b/format/weeks/version.php
@@ -0,0 +1,30 @@
+.
+
+/**
+ * Version details
+ *
+ * @package format
+ * @subpackage weeks
+ * @copyright 1999 onwards Martin Dougiamas (http://dougiamas.com)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version = 2012112900; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires = 2012112900; // Requires this Moodle version.
+$plugin->component = 'format_weeks'; // Full name of the plugin (used for diagnostics).
diff --git a/index.php b/index.php
new file mode 100644
index 0000000..7253d44
--- /dev/null
+++ b/index.php
@@ -0,0 +1,398 @@
+.
+
+/**
+ * For most people, just lists the course categories
+ * Allows the admin to create, delete and rename course categories
+ *
+ * @copyright 1999 Martin Dougiamas http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require_once("../config.php");
+require_once("lib.php");
+
+$categoryedit = optional_param('categoryedit', -1,PARAM_BOOL);
+$delete = optional_param('delete',0,PARAM_INT);
+$hide = optional_param('hide',0,PARAM_INT);
+$show = optional_param('show',0,PARAM_INT);
+$move = optional_param('move',0,PARAM_INT);
+$moveto = optional_param('moveto',-1,PARAM_INT);
+$moveup = optional_param('moveup',0,PARAM_INT);
+$movedown = optional_param('movedown',0,PARAM_INT);
+
+$site = get_site();
+
+$systemcontext = context_system::instance();
+
+$PAGE->set_url('/course/index.php');
+$PAGE->set_context($systemcontext);
+$PAGE->set_pagelayout('admin');
+
+if (can_edit_in_category()) {
+ if ($categoryedit !== -1) {
+ $USER->editing = $categoryedit;
+ }
+ require_login();
+ $adminediting = $PAGE->user_is_editing();
+} else {
+ if ($CFG->forcelogin) {
+ require_login();
+ }
+ $adminediting = false;
+}
+
+$stradministration = get_string('administration');
+$strcategories = get_string('categories');
+$strcategory = get_string('category');
+$strcourses = get_string('courses');
+$stredit = get_string('edit');
+$strdelete = get_string('delete');
+$straction = get_string('action');
+$strfulllistofcourses = get_string('fulllistofcourses');
+
+
+// Unless it's an editing admin, just print the regular listing of courses/categories.
+if (!$adminediting) {
+ $showaddcoursebutton = true;
+ // Print form for creating new categories.
+ $countcategories = $DB->count_records('course_categories');
+ if ($countcategories > 1 || ($countcategories == 1 && $DB->count_records('course') > 200)) {
+ $strcourses = get_string('courses');
+ $strcategories = get_string('categories');
+
+ $PAGE->navbar->add($strcategories);
+ $PAGE->set_title("$site->shortname: $strcategories");
+ $PAGE->set_heading($COURSE->fullname);
+ $PAGE->set_button(update_category_button());
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading($strcategories);
+ echo $OUTPUT->skip_link_target();
+ echo $OUTPUT->box_start('categorybox');
+ print_whole_category_list();
+ echo $OUTPUT->box_end();
+ print_course_search();
+ } else {
+ $PAGE->navbar->add($strfulllistofcourses);
+ $PAGE->set_title("$site->shortname: $strfulllistofcourses");
+ $PAGE->set_heading($COURSE->fullname);
+ $PAGE->set_button(update_category_button());
+ echo $OUTPUT->header();
+ echo $OUTPUT->skip_link_target();
+ echo $OUTPUT->box_start('courseboxes');
+ $showaddcoursebutton = print_courses(0);
+ echo $OUTPUT->box_end();
+ }
+
+ echo $OUTPUT->container_start('buttons');
+ if (has_capability('moodle/course:create', $systemcontext) && $showaddcoursebutton) {
+ // Print link to create a new course, for the 1st available category.
+ $options = array('category' => $CFG->defaultrequestcategory);
+ echo $OUTPUT->single_button(new moodle_url('edit.php', $options), get_string('addnewcourse'), 'get');
+ }
+ print_course_request_buttons($systemcontext);
+ echo $OUTPUT->container_end();
+ echo $OUTPUT->footer();
+ exit;
+}
+/// Everything else is editing on mode.
+require_once($CFG->libdir.'/adminlib.php');
+admin_externalpage_setup('coursemgmt');
+
+/// Delete a category.
+if (!empty($delete) and confirm_sesskey()) {
+ if (!$deletecat = $DB->get_record('course_categories', array('id'=>$delete))) {
+ print_error('invalidcategoryid');
+ }
+ $context = context_coursecat::instance($delete);
+ require_capability('moodle/category:manage', $context);
+ require_capability('moodle/category:manage', get_category_or_system_context($deletecat->parent));
+
+ $heading = get_string('deletecategory', 'moodle', format_string($deletecat->name, true, array('context' => $context)));
+ require_once('delete_category_form.php');
+ $mform = new delete_category_form(null, $deletecat);
+ $mform->set_data(array('delete'=>$delete));
+
+ if ($mform->is_cancelled()) {
+ redirect('index.php');
+
+ } else if (!$data= $mform->get_data()) {
+ require_once($CFG->libdir . '/questionlib.php');
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading($heading);
+ $mform->display();
+ echo $OUTPUT->footer();
+ exit();
+ }
+
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading($heading);
+
+ if ($data->fulldelete) {
+ $deletedcourses = category_delete_full($deletecat, true);
+
+ foreach($deletedcourses as $course) {
+ echo $OUTPUT->notification(get_string('coursedeleted', '', $course->shortname), 'notifysuccess');
+ }
+ echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($deletecat->name, true, array('context' => $context))), 'notifysuccess');
+
+ } else {
+ category_delete_move($deletecat, $data->newparent, true);
+ }
+
+ // If we deleted $CFG->defaultrequestcategory, make it point somewhere else.
+ if ($delete == $CFG->defaultrequestcategory) {
+ set_config('defaultrequestcategory', $DB->get_field('course_categories', 'MIN(id)', array('parent'=>0)));
+ }
+
+ echo $OUTPUT->continue_button('index.php');
+
+ echo $OUTPUT->footer();
+ die;
+}
+
+/// Create a default category if necessary
+if (!$categories = get_categories()) { /// No category yet!
+ // Try and make one
+ $tempcat = new stdClass();
+ $tempcat->name = get_string('miscellaneous');
+ $tempcat->id = $DB->insert_record('course_categories', $tempcat);
+ $tempcat->context = context_coursecat::instance($tempcat->id);
+ mark_context_dirty('/'.SYSCONTEXTID);
+ fix_course_sortorder(); // Required to build course_categories.depth and .path.
+ set_config('defaultrequestcategory', $tempcat->id);
+}
+
+/// Move a category to a new parent if required
+if (!empty($move) and ($moveto >= 0) and confirm_sesskey()) {
+ if ($cattomove = $DB->get_record('course_categories', array('id'=>$move))) {
+ require_capability('moodle/category:manage', get_category_or_system_context($cattomove->parent));
+ if ($cattomove->parent != $moveto) {
+ $newparent = $DB->get_record('course_categories', array('id'=>$moveto));
+ require_capability('moodle/category:manage', get_category_or_system_context($moveto));
+ move_category($cattomove, $newparent);
+ }
+ }
+}
+
+/// Hide or show a category
+if ($hide and confirm_sesskey()) {
+ if ($tempcat = $DB->get_record('course_categories', array('id'=>$hide))) {
+ require_capability('moodle/category:manage', get_category_or_system_context($tempcat->parent));
+ if ($tempcat->visible == 1) {
+ course_category_hide($tempcat);
+ }
+ }
+} else if ($show and confirm_sesskey()) {
+ if ($tempcat = $DB->get_record('course_categories', array('id'=>$show))) {
+ require_capability('moodle/category:manage', get_category_or_system_context($tempcat->parent));
+ if ($tempcat->visible == 0) {
+ course_category_show($tempcat);
+ }
+ }
+}
+
+/// Move a category up or down
+if ((!empty($moveup) or !empty($movedown)) and confirm_sesskey()) {
+ fix_course_sortorder();
+ $swapcategory = NULL;
+
+ if (!empty($moveup)) {
+ require_capability('moodle/category:manage', context_coursecat::instance($moveup));
+ if ($movecategory = $DB->get_record('course_categories', array('id'=>$moveup))) {
+ if ($swapcategory = $DB->get_records_select('course_categories', "sortorder AND parent=?", array($movecategory->sortorder, $movecategory->parent), 'sortorder DESC', '*', 0, 1)) {
+ $swapcategory = reset($swapcategory);
+ }
+ }
+ } else {
+ require_capability('moodle/category:manage', context_coursecat::instance($movedown));
+ if ($movecategory = $DB->get_record('course_categories', array('id'=>$movedown))) {
+ if ($swapcategory = $DB->get_records_select('course_categories', "sortorder>? AND parent=?", array($movecategory->sortorder, $movecategory->parent), 'sortorder ASC', '*', 0, 1)) {
+ $swapcategory = reset($swapcategory);
+ }
+ }
+ }
+ if ($swapcategory and $movecategory) {
+ $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id'=>$movecategory->id));
+ $DB->set_field('course_categories', 'sortorder', $movecategory->sortorder, array('id'=>$swapcategory->id));
+ add_to_log(SITEID, "category", "move", "editcategory.php?id=$movecategory->id", $movecategory->id);
+ }
+
+ // finally reorder courses
+ fix_course_sortorder();
+}
+
+/// Print headings
+echo $OUTPUT->header();
+echo $OUTPUT->heading($strcategories);
+
+/// Print out the categories with all the knobs
+$strcategories = get_string('categories');
+$strcourses = get_string('courses');
+$strmovecategoryto = get_string('movecategoryto');
+$stredit = get_string('edit');
+
+$displaylist = array();
+$parentlist = array();
+
+$displaylist[0] = get_string('top');
+make_categories_list($displaylist, $parentlist);
+
+echo '
\n";
+
+ $content = false;
+
+/// Firstly, have there been any new enrolments?
+
+ $users = get_recent_enrolments($course->id, $timestart);
+
+ //Accessibility: new users now appear in an list.
+ if ($users) {
+ echo '
\n";
+ }
+
+/// Next, have there been any modifications to the course structure?
+
+ $modinfo = get_fast_modinfo($course);
+
+ $changelist = array();
+
+ $logs = $DB->get_records_select('log', "time > ? AND course = ? AND
+ module = 'course' AND
+ (action = 'add mod' OR action = 'update mod' OR action = 'delete mod')",
+ array($timestart, $course->id), "id ASC");
+
+ if ($logs) {
+ $actions = array('add mod', 'update mod', 'delete mod');
+ $newgones = array(); // added and later deleted items
+ foreach ($logs as $key => $log) {
+ if (!in_array($log->action, $actions)) {
+ continue;
+ }
+ $info = explode(' ', $log->info);
+
+ // note: in most cases I replaced hardcoding of label with use of
+ // $cm->has_view() but it was not possible to do this here because
+ // we don't necessarily have the $cm for it
+ if ($info[0] == 'label') { // Labels are ignored in recent activity
+ continue;
+ }
+
+ if (count($info) != 2) {
+ debugging("Incorrect log entry info: id = ".$log->id, DEBUG_DEVELOPER);
+ continue;
+ }
+
+ $modname = $info[0];
+ $instanceid = $info[1];
+
+ if ($log->action == 'delete mod') {
+ // unfortunately we do not know if the mod was visible
+ if (!array_key_exists($log->info, $newgones)) {
+ $strdeleted = get_string('deletedactivity', 'moodle', get_string('modulename', $modname));
+ $changelist[$log->info] = array ('operation' => 'delete', 'text' => $strdeleted);
+ }
+ } else {
+ if (!isset($modinfo->instances[$modname][$instanceid])) {
+ if ($log->action == 'add mod') {
+ // do not display added and later deleted activities
+ $newgones[$log->info] = true;
+ }
+ continue;
+ }
+ $cm = $modinfo->instances[$modname][$instanceid];
+ if (!$cm->uservisible) {
+ continue;
+ }
+
+ if ($log->action == 'add mod') {
+ $stradded = get_string('added', 'moodle', get_string('modulename', $modname));
+ $changelist[$log->info] = array('operation' => 'add', 'text' => "$stradded: wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."");
+
+ } else if ($log->action == 'update mod' and empty($changelist[$log->info])) {
+ $strupdated = get_string('updated', 'moodle', get_string('modulename', $modname));
+ $changelist[$log->info] = array('operation' => 'update', 'text' => "$strupdated: wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."");
+ }
+ }
+ }
+ }
+
+ if (!empty($changelist)) {
+ echo $OUTPUT->heading(get_string("courseupdates").':', 3);
+ $content = true;
+ foreach ($changelist as $changeinfo => $change) {
+ echo '
'.$change['text'].'
';
+ }
+ }
+
+/// Now display new things from each module
+
+ $usedmodules = array();
+ foreach($modinfo->cms as $cm) {
+ if (isset($usedmodules[$cm->modname])) {
+ continue;
+ }
+ if (!$cm->uservisible) {
+ continue;
+ }
+ $usedmodules[$cm->modname] = $cm->modname;
+ }
+
+ foreach ($usedmodules as $modname) { // Each module gets it's own logs and prints them
+ if (file_exists($CFG->dirroot.'/mod/'.$modname.'/lib.php')) {
+ include_once($CFG->dirroot.'/mod/'.$modname.'/lib.php');
+ $print_recent_activity = $modname.'_print_recent_activity';
+ if (function_exists($print_recent_activity)) {
+ // NOTE: original $isteacher (second parameter below) was replaced with $viewfullnames!
+ $content = $print_recent_activity($course, $viewfullnames, $timestart) || $content;
+ }
+ } else {
+ debugging("Missing lib.php in lib/{$modname} - please reinstall files or uninstall the module");
+ }
+ }
+
+ if (! $content) {
+ echo '
'.get_string('nothingnew').'
';
+ }
+}
+
+/**
+ * For a given course, returns an array of course activity objects
+ * Each item in the array contains he following properties:
+ */
+function get_array_of_activities($courseid) {
+// cm - course module id
+// mod - name of the module (eg forum)
+// section - the number of the section (eg week or topic)
+// name - the name of the instance
+// visible - is the instance visible or not
+// groupingid - grouping id
+// groupmembersonly - is this instance visible to group members only
+// extra - contains extra string to include in any link
+ global $CFG, $DB;
+ if(!empty($CFG->enableavailability)) {
+ require_once($CFG->libdir.'/conditionlib.php');
+ }
+
+ $course = $DB->get_record('course', array('id'=>$courseid));
+
+ if (empty($course)) {
+ throw new moodle_exception('courseidnotfound');
+ }
+
+ $mod = array();
+
+ $rawmods = get_course_mods($courseid);
+ if (empty($rawmods)) {
+ return $mod; // always return array
+ }
+
+ if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
+ foreach ($sections as $section) {
+ if (!empty($section->sequence)) {
+ $sequence = explode(",", $section->sequence);
+ foreach ($sequence as $seq) {
+ if (empty($rawmods[$seq])) {
+ continue;
+ }
+ $mod[$seq] = new stdClass();
+ $mod[$seq]->id = $rawmods[$seq]->instance;
+ $mod[$seq]->cm = $rawmods[$seq]->id;
+ $mod[$seq]->mod = $rawmods[$seq]->modname;
+
+ // Oh dear. Inconsistent names left here for backward compatibility.
+ $mod[$seq]->section = $section->section;
+ $mod[$seq]->sectionid = $rawmods[$seq]->section;
+
+ $mod[$seq]->module = $rawmods[$seq]->module;
+ $mod[$seq]->added = $rawmods[$seq]->added;
+ $mod[$seq]->score = $rawmods[$seq]->score;
+ $mod[$seq]->idnumber = $rawmods[$seq]->idnumber;
+ $mod[$seq]->visible = $rawmods[$seq]->visible;
+ $mod[$seq]->visibleold = $rawmods[$seq]->visibleold;
+ $mod[$seq]->groupmode = $rawmods[$seq]->groupmode;
+ $mod[$seq]->groupingid = $rawmods[$seq]->groupingid;
+ $mod[$seq]->groupmembersonly = $rawmods[$seq]->groupmembersonly;
+ $mod[$seq]->indent = $rawmods[$seq]->indent;
+ $mod[$seq]->completion = $rawmods[$seq]->completion;
+ $mod[$seq]->extra = "";
+ $mod[$seq]->completiongradeitemnumber =
+ $rawmods[$seq]->completiongradeitemnumber;
+ $mod[$seq]->completionview = $rawmods[$seq]->completionview;
+ $mod[$seq]->completionexpected = $rawmods[$seq]->completionexpected;
+ $mod[$seq]->availablefrom = $rawmods[$seq]->availablefrom;
+ $mod[$seq]->availableuntil = $rawmods[$seq]->availableuntil;
+ $mod[$seq]->showavailability = $rawmods[$seq]->showavailability;
+ $mod[$seq]->showdescription = $rawmods[$seq]->showdescription;
+ if (!empty($CFG->enableavailability)) {
+ condition_info::fill_availability_conditions($rawmods[$seq]);
+ $mod[$seq]->conditionscompletion = $rawmods[$seq]->conditionscompletion;
+ $mod[$seq]->conditionsgrade = $rawmods[$seq]->conditionsgrade;
+ $mod[$seq]->conditionsfield = $rawmods[$seq]->conditionsfield;
+ }
+
+ $modname = $mod[$seq]->mod;
+ $functionname = $modname."_get_coursemodule_info";
+
+ if (!file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
+ continue;
+ }
+
+ include_once("$CFG->dirroot/mod/$modname/lib.php");
+
+ if ($hasfunction = function_exists($functionname)) {
+ if ($info = $functionname($rawmods[$seq])) {
+ if (!empty($info->icon)) {
+ $mod[$seq]->icon = $info->icon;
+ }
+ if (!empty($info->iconcomponent)) {
+ $mod[$seq]->iconcomponent = $info->iconcomponent;
+ }
+ if (!empty($info->name)) {
+ $mod[$seq]->name = $info->name;
+ }
+ if ($info instanceof cached_cm_info) {
+ // When using cached_cm_info you can include three new fields
+ // that aren't available for legacy code
+ if (!empty($info->content)) {
+ $mod[$seq]->content = $info->content;
+ }
+ if (!empty($info->extraclasses)) {
+ $mod[$seq]->extraclasses = $info->extraclasses;
+ }
+ if (!empty($info->iconurl)) {
+ $mod[$seq]->iconurl = $info->iconurl;
+ }
+ if (!empty($info->onclick)) {
+ $mod[$seq]->onclick = $info->onclick;
+ }
+ if (!empty($info->customdata)) {
+ $mod[$seq]->customdata = $info->customdata;
+ }
+ } else {
+ // When using a stdclass, the (horrible) deprecated ->extra field
+ // is available for BC
+ if (!empty($info->extra)) {
+ $mod[$seq]->extra = $info->extra;
+ }
+ }
+ }
+ }
+ // When there is no modname_get_coursemodule_info function,
+ // but showdescriptions is enabled, then we use the 'intro'
+ // and 'introformat' fields in the module table
+ if (!$hasfunction && $rawmods[$seq]->showdescription) {
+ if ($modvalues = $DB->get_record($rawmods[$seq]->modname,
+ array('id' => $rawmods[$seq]->instance), 'name, intro, introformat')) {
+ // Set content from intro and introformat. Filters are disabled
+ // because we filter it with format_text at display time
+ $mod[$seq]->content = format_module_intro($rawmods[$seq]->modname,
+ $modvalues, $rawmods[$seq]->id, false);
+
+ // To save making another query just below, put name in here
+ $mod[$seq]->name = $modvalues->name;
+ }
+ }
+ if (!isset($mod[$seq]->name)) {
+ $mod[$seq]->name = $DB->get_field($rawmods[$seq]->modname, "name", array("id"=>$rawmods[$seq]->instance));
+ }
+
+ // Minimise the database size by unsetting default options when they are
+ // 'empty'. This list corresponds to code in the cm_info constructor.
+ foreach (array('idnumber', 'groupmode', 'groupingid', 'groupmembersonly',
+ 'indent', 'completion', 'extra', 'extraclasses', 'iconurl', 'onclick', 'content',
+ 'icon', 'iconcomponent', 'customdata', 'showavailability', 'availablefrom',
+ 'availableuntil', 'conditionscompletion', 'conditionsgrade',
+ 'completionview', 'completionexpected', 'score', 'showdescription')
+ as $property) {
+ if (property_exists($mod[$seq], $property) &&
+ empty($mod[$seq]->{$property})) {
+ unset($mod[$seq]->{$property});
+ }
+ }
+ // Special case: this value is usually set to null, but may be 0
+ if (property_exists($mod[$seq], 'completiongradeitemnumber') &&
+ is_null($mod[$seq]->completiongradeitemnumber)) {
+ unset($mod[$seq]->completiongradeitemnumber);
+ }
+ }
+ }
+ }
+ }
+ return $mod;
+}
+
+/**
+ * Returns the localised human-readable names of all used modules
+ *
+ * @param bool $plural if true returns the plural forms of the names
+ * @return array where key is the module name (component name without 'mod_') and
+ * the value is the human-readable string. Array sorted alphabetically by value
+ */
+function get_module_types_names($plural = false) {
+ static $modnames = null;
+ global $DB, $CFG;
+ if ($modnames === null) {
+ $modnames = array(0 => array(), 1 => array());
+ if ($allmods = $DB->get_records("modules")) {
+ foreach ($allmods as $mod) {
+ if (file_exists("$CFG->dirroot/mod/$mod->name/lib.php") && $mod->visible) {
+ $modnames[0][$mod->name] = get_string("modulename", "$mod->name");
+ $modnames[1][$mod->name] = get_string("modulenameplural", "$mod->name");
+ }
+ }
+ collatorlib::asort($modnames[0]);
+ collatorlib::asort($modnames[1]);
+ }
+ }
+ return $modnames[(int)$plural];
+}
+
+/**
+ * Set highlighted section. Only one section can be highlighted at the time.
+ *
+ * @param int $courseid course id
+ * @param int $marker highlight section with this number, 0 means remove higlightin
+ * @return void
+ */
+function course_set_marker($courseid, $marker) {
+ global $DB;
+ $DB->set_field("course", "marker", $marker, array('id' => $courseid));
+ format_base::reset_course_cache($courseid);
+}
+
+/**
+ * For a given course section, marks it visible or hidden,
+ * and does the same for every activity in that section
+ *
+ * @param int $courseid course id
+ * @param int $sectionnumber The section number to adjust
+ * @param int $visibility The new visibility
+ * @return array A list of resources which were hidden in the section
+ */
+function set_section_visible($courseid, $sectionnumber, $visibility) {
+ global $DB;
+
+ $resourcestotoggle = array();
+ if ($section = $DB->get_record("course_sections", array("course"=>$courseid, "section"=>$sectionnumber))) {
+ $DB->set_field("course_sections", "visible", "$visibility", array("id"=>$section->id));
+ if (!empty($section->sequence)) {
+ $modules = explode(",", $section->sequence);
+ foreach ($modules as $moduleid) {
+ if ($cm = $DB->get_record('course_modules', array('id' => $moduleid), 'visible, visibleold')) {
+ if ($visibility) {
+ // As we unhide the section, we use the previously saved visibility stored in visibleold.
+ set_coursemodule_visible($moduleid, $cm->visibleold);
+ } else {
+ // We hide the section, so we hide the module but we store the original state in visibleold.
+ set_coursemodule_visible($moduleid, 0);
+ $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
+ }
+ }
+ }
+ }
+ rebuild_course_cache($courseid, true);
+
+ // Determine which modules are visible for AJAX update
+ if (!empty($modules)) {
+ list($insql, $params) = $DB->get_in_or_equal($modules);
+ $select = 'id ' . $insql . ' AND visible = ?';
+ array_push($params, $visibility);
+ if (!$visibility) {
+ $select .= ' AND visibleold = 1';
+ }
+ $resourcestotoggle = $DB->get_fieldset_select('course_modules', 'id', $select, $params);
+ }
+ }
+ return $resourcestotoggle;
+}
+
+/**
+ * Obtains shared data that is used in print_section when displaying a
+ * course-module entry.
+ *
+ * Calls format_text or format_string as appropriate, and obtains the correct icon.
+ *
+ * This data is also used in other areas of the code.
+ * @param cm_info $cm Course-module data (must come from get_fast_modinfo)
+ * @param object $course Moodle course object
+ * @return array An array with the following values in this order:
+ * $content (optional extra content for after link),
+ * $instancename (text of link)
+ */
+function get_print_section_cm_text(cm_info $cm, $course) {
+ global $OUTPUT;
+
+ // Get content from modinfo if specified. Content displays either
+ // in addition to the standard link (below), or replaces it if
+ // the link is turned off by setting ->url to null.
+ if (($content = $cm->get_content()) !== '') {
+ // Improve filter performance by preloading filter setttings for all
+ // activities on the course (this does nothing if called multiple
+ // times)
+ filter_preload_activities($cm->get_modinfo());
+
+ // Get module context
+ $modulecontext = context_module::instance($cm->id);
+ $labelformatoptions = new stdClass();
+ $labelformatoptions->noclean = true;
+ $labelformatoptions->overflowdiv = true;
+ $labelformatoptions->context = $modulecontext;
+ $content = format_text($content, FORMAT_HTML, $labelformatoptions);
+ } else {
+ $content = '';
+ }
+
+ // Get course context
+ $coursecontext = context_course::instance($course->id);
+ $stringoptions = new stdClass;
+ $stringoptions->context = $coursecontext;
+ $instancename = format_string($cm->name, true, $stringoptions);
+ return array($content, $instancename);
+}
+
+/**
+ * Prints a section full of activity modules
+ *
+ * @param stdClass $course The course
+ * @param stdClass|section_info $section The section object containing properties id and section
+ * @param array $mods (argument not used)
+ * @param array $modnamesused (argument not used)
+ * @param bool $absolute All links are absolute
+ * @param string $width Width of the container
+ * @param bool $hidecompletion Hide completion status
+ * @param int $sectionreturn The section to return to
+ * @return void
+ */
+function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn=null) {
+ global $CFG, $USER, $DB, $PAGE, $OUTPUT;
+
+ static $initialised;
+
+ static $groupbuttons;
+ static $groupbuttonslink;
+ static $isediting;
+ static $ismoving;
+ static $strmovehere;
+ static $strmovefull;
+ static $strunreadpostsone;
+
+ if (!isset($initialised)) {
+ $groupbuttons = ($course->groupmode or (!$course->groupmodeforce));
+ $groupbuttonslink = (!$course->groupmodeforce);
+ $isediting = $PAGE->user_is_editing();
+ $ismoving = $isediting && ismoving($course->id);
+ if ($ismoving) {
+ $strmovehere = get_string("movehere");
+ $strmovefull = strip_tags(get_string("movefull", "", "'$USER->activitycopyname'"));
+ }
+ $initialised = true;
+ }
+
+ $modinfo = get_fast_modinfo($course);
+ $completioninfo = new completion_info($course);
+
+ //Accessibility: replace table with list
\n";
+
+ foreach ($modinfo->sections[$section->section] as $modnumber) {
+ $mod = $modinfo->cms[$modnumber];
+
+ if ($ismoving and $mod->id == $USER->activitycopy) {
+ // do not display moving mod
+ continue;
+ }
+
+ // We can continue (because it will not be displayed at all)
+ // if:
+ // 1) The activity is not visible to users
+ // and
+ // 2a) The 'showavailability' option is not set (if that is set,
+ // we need to display the activity so we can show
+ // availability info)
+ // or
+ // 2b) The 'availableinfo' is empty, i.e. the activity was
+ // hidden in a way that leaves no info, such as using the
+ // eye icon.
+ if (!$mod->uservisible &&
+ (empty($mod->showavailability) ||
+ empty($mod->availableinfo))) {
+ // visibility shortcut
+ continue;
+ }
+
+ // In some cases the activity is visible to user, but it is
+ // dimmed. This is done if viewhiddenactivities is true and if:
+ // 1. the activity is not visible, or
+ // 2. the activity has dates set which do not include current, or
+ // 3. the activity has any other conditions set (regardless of whether
+ // current user meets them)
+ $modcontext = context_module::instance($mod->id);
+ $canviewhidden = has_capability('moodle/course:viewhiddenactivities', $modcontext);
+ $accessiblebutdim = false;
+ $conditionalhidden = false;
+ if ($canviewhidden) {
+ $accessiblebutdim = !$mod->visible;
+ if (!empty($CFG->enableavailability)) {
+ $conditionalhidden = $mod->availablefrom > time() ||
+ ($mod->availableuntil && $mod->availableuntil < time()) ||
+ count($mod->conditionsgrade) > 0 ||
+ count($mod->conditionscompletion) > 0;
+ }
+ $accessiblebutdim = $conditionalhidden || $accessiblebutdim;
+ }
+
+ $liclasses = array();
+ $liclasses[] = 'activity';
+ $liclasses[] = $mod->modname;
+ $liclasses[] = 'modtype_'.$mod->modname;
+ $extraclasses = $mod->get_extra_classes();
+ if ($extraclasses) {
+ $liclasses = array_merge($liclasses, explode(' ', $extraclasses));
+ }
+ echo html_writer::start_tag('li', array('class'=>join(' ', $liclasses), 'id'=>'module-'.$modnumber));
+ if ($ismoving) {
+ echo ''.
+ '
+ ';
+ }
+
+ $classes = array('mod-indent');
+ if (!empty($mod->indent)) {
+ $classes[] = 'mod-indent-'.$mod->indent;
+ if ($mod->indent > 15) {
+ $classes[] = 'mod-indent-huge';
+ }
+ }
+ echo html_writer::start_tag('div', array('class'=>join(' ', $classes)));
+
+ // Get data about this course-module
+ list($content, $instancename) =
+ get_print_section_cm_text($modinfo->cms[$modnumber], $course);
+
+ //Accessibility: for files get description via icon, this is very ugly hack!
+ $altname = '';
+ $altname = $mod->modfullname;
+ // Avoid unnecessary duplication: if e.g. a forum name already
+ // includes the word forum (or Forum, etc) then it is unhelpful
+ // to include that in the accessible description that is added.
+ if (false !== strpos(textlib::strtolower($instancename),
+ textlib::strtolower($altname))) {
+ $altname = '';
+ }
+ // File type after name, for alphabetic lists (screen reader).
+ if ($altname) {
+ $altname = get_accesshide(' '.$altname);
+ }
+
+ // Start the div for the activity title, excluding the edit icons.
+ echo html_writer::start_tag('div', array('class' => 'activityinstance'));
+
+ // We may be displaying this just in order to show information
+ // about visibility, without the actual link
+ $contentpart = '';
+ if ($mod->uservisible) {
+ // Nope - in this case the link is fully working for user
+ $linkclasses = '';
+ $textclasses = '';
+ if ($accessiblebutdim) {
+ $linkclasses .= ' dimmed';
+ $textclasses .= ' dimmed_text';
+ if ($conditionalhidden) {
+ $linkclasses .= ' conditionalhidden';
+ $textclasses .= ' conditionalhidden';
+ }
+ $accesstext = get_accesshide(get_string('hiddenfromstudents').': ');
+ } else {
+ $accesstext = '';
+ }
+ if ($linkclasses) {
+ $linkcss = trim($linkclasses) . ' ';
+ } else {
+ $linkcss = '';
+ }
+ if ($textclasses) {
+ $textcss = trim($textclasses) . ' ';
+ } else {
+ $textcss = '';
+ }
+
+ // Get on-click attribute value if specified and decode the onclick - it
+ // has already been encoded for display (puke).
+ $onclick = htmlspecialchars_decode($mod->get_on_click(), ENT_QUOTES);
+
+ $groupinglabel = '';
+ if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
+ $groupings = groups_get_all_groupings($course->id);
+ $groupinglabel = html_writer::tag('span', '('.format_string($groupings[$mod->groupingid]->name).')',
+ array('class' => 'groupinglabel'));
+ }
+
+ if ($url = $mod->get_url()) {
+ // Display link itself.
+ $activitylink = html_writer::empty_tag('img', array('src' => $mod->get_icon_url(),
+ 'class' => 'iconlarge activityicon', 'alt' => $mod->modfullname)) . $accesstext .
+ html_writer::tag('span', $instancename . $altname, array('class' => 'instancename'));
+ echo html_writer::link($url, $activitylink, array('class' => $linkcss, 'onclick' => $onclick)) .
+ $groupinglabel;
+
+ // If specified, display extra content after link.
+ if ($content) {
+ $contentpart = html_writer::tag('div', $content, array('class' =>
+ trim('contentafterlink ' . $textclasses)));
+ }
+ } else {
+ // No link, so display only content.
+ $contentpart = html_writer::tag('div', $accesstext . $content, array('class' => $textcss));
+ }
+
+ } else {
+ $textclasses = $extraclasses;
+ $textclasses .= ' dimmed_text';
+ if ($textclasses) {
+ $textcss = 'class="' . trim($textclasses) . '" ';
+ } else {
+ $textcss = '';
+ }
+ $accesstext = '' .
+ get_string('notavailableyet', 'condition') .
+ ': ';
+
+ if ($url = $mod->get_url()) {
+ // Display greyed-out text of link
+ echo '
';
+
+ // Do not display content after link when it is greyed out like this.
+ } else {
+ // No link, so display only content (also greyed)
+ $contentpart = '
extra . '>' .
+ $accesstext . $content . '
';
+ }
+ }
+
+ // Module can put text after the link (e.g. forum unread)
+ echo $mod->get_after_link();
+
+ // Closing the tag which contains everything but edit icons. $contentpart should not be part of this.
+ echo html_writer::end_tag('div');
+
+ // If there is content but NO link (eg label), then display the
+ // content here (BEFORE any icons). In this case cons must be
+ // displayed after the content so that it makes more sense visually
+ // and for accessibility reasons, e.g. if you have a one-line label
+ // it should work similarly (at least in terms of ordering) to an
+ // activity.
+ if (empty($url)) {
+ echo $contentpart;
+ }
+
+ if ($isediting) {
+ if ($groupbuttons and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
+ if (! $mod->groupmodelink = $groupbuttonslink) {
+ $mod->groupmode = $course->groupmode;
+ }
+
+ } else {
+ $mod->groupmode = false;
+ }
+ echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
+ echo $mod->get_after_edit_icons();
+ }
+
+ // Completion
+ $completion = $hidecompletion
+ ? COMPLETION_TRACKING_NONE
+ : $completioninfo->is_enabled($mod);
+ if ($completion!=COMPLETION_TRACKING_NONE && isloggedin() &&
+ !isguestuser() && $mod->uservisible) {
+ $completiondata = $completioninfo->get_data($mod,true);
+ $completionicon = '';
+ if ($isediting) {
+ switch ($completion) {
+ case COMPLETION_TRACKING_MANUAL :
+ $completionicon = 'manual-enabled'; break;
+ case COMPLETION_TRACKING_AUTOMATIC :
+ $completionicon = 'auto-enabled'; break;
+ default: // wtf
+ }
+ } else if ($completion==COMPLETION_TRACKING_MANUAL) {
+ switch($completiondata->completionstate) {
+ case COMPLETION_INCOMPLETE:
+ $completionicon = 'manual-n'; break;
+ case COMPLETION_COMPLETE:
+ $completionicon = 'manual-y'; break;
+ }
+ } else { // Automatic
+ switch($completiondata->completionstate) {
+ case COMPLETION_INCOMPLETE:
+ $completionicon = 'auto-n'; break;
+ case COMPLETION_COMPLETE:
+ $completionicon = 'auto-y'; break;
+ case COMPLETION_COMPLETE_PASS:
+ $completionicon = 'auto-pass'; break;
+ case COMPLETION_COMPLETE_FAIL:
+ $completionicon = 'auto-fail'; break;
+ }
+ }
+ if ($completionicon) {
+ $imgsrc = $OUTPUT->pix_url('i/completion-'.$completionicon);
+ $formattedname = format_string($mod->name, true, array('context' => $modcontext));
+ $imgalt = get_string('completion-alt-' . $completionicon, 'completion', $formattedname);
+ if ($completion == COMPLETION_TRACKING_MANUAL && !$isediting) {
+ $imgtitle = get_string('completion-title-' . $completionicon, 'completion', $formattedname);
+ $newstate =
+ $completiondata->completionstate==COMPLETION_COMPLETE
+ ? COMPLETION_INCOMPLETE
+ : COMPLETION_COMPLETE;
+ // In manual mode the icon is a toggle form...
+
+ // If this completion state is used by the
+ // conditional activities system, we need to turn
+ // off the JS.
+ if (!empty($CFG->enableavailability) &&
+ condition_info::completion_value_used_as_condition($course, $mod)) {
+ $extraclass = ' preventjs';
+ } else {
+ $extraclass = '';
+ }
+ echo html_writer::start_tag('form', array(
+ 'class' => 'togglecompletion' . $extraclass,
+ 'method' => 'post',
+ 'action' => $CFG->wwwroot . '/course/togglecompletion.php'));
+ echo html_writer::start_tag('div');
+ echo html_writer::empty_tag('input', array(
+ 'type' => 'hidden', 'name' => 'id', 'value' => $mod->id));
+ echo html_writer::empty_tag('input', array(
+ 'type' => 'hidden', 'name' => 'modulename',
+ 'value' => $mod->name));
+ echo html_writer::empty_tag('input', array(
+ 'type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
+ echo html_writer::empty_tag('input', array(
+ 'type' => 'hidden', 'name' => 'completionstate',
+ 'value' => $newstate));
+ echo html_writer::empty_tag('input', array(
+ 'type' => 'image', 'src' => $imgsrc, 'alt' => $imgalt, 'title' => $imgtitle));
+ echo html_writer::end_tag('div');
+ echo html_writer::end_tag('form');
+ } else {
+ // In auto mode, or when editing, the icon is just an image
+ echo "";
+ echo "";
+ }
+ }
+ }
+
+ // If there is content AND a link, then display the content here
+ // (AFTER any icons). Otherwise it was displayed before
+ if (!empty($url)) {
+ echo $contentpart;
+ }
+
+ // Show availability information (for someone who isn't allowed to
+ // see the activity itself, or for staff)
+ if (!$mod->uservisible) {
+ echo '
'.$mod->availableinfo.'
';
+ } else if ($canviewhidden && !empty($CFG->enableavailability)) {
+ // Don't add availability information if user is not editing and activity is hidden.
+ if ($mod->visible || $PAGE->user_is_editing()) {
+ $hidinfoclass = '';
+ if (!$mod->visible) {
+ $hidinfoclass = 'hide';
+ }
+ $ci = new condition_info($mod);
+ $fullinfo = $ci->get_full_information();
+ if($fullinfo) {
+ echo '
\n\n";
+ }
+}
+
+/**
+ * Prints the menus to add activities and resources.
+ *
+ * @param stdClass $course The course
+ * @param int $section relative section number (field course_sections.section)
+ * @param null|array $modnames An array containing the list of modules and their names
+ * if omitted will be taken from get_module_types_names()
+ * @param bool $vertical Vertical orientation
+ * @param bool $return Return the menus or send them to output
+ * @param int $sectionreturn The section to link back to
+ * @return void|string depending on $return
+ */
+function print_section_add_menus($course, $section, $modnames = null, $vertical=false, $return=false, $sectionreturn=null) {
+ global $CFG, $OUTPUT;
+
+ if ($modnames === null) {
+ $modnames = get_module_types_names();
+ }
+
+ // check to see if user can add menus and there are modules to add
+ if (!has_capability('moodle/course:manageactivities', context_course::instance($course->id))
+ || empty($modnames)) {
+ if ($return) {
+ return '';
+ } else {
+ return false;
+ }
+ }
+
+ // Retrieve all modules with associated metadata
+ $modules = get_module_metadata($course, $modnames, $sectionreturn);
+
+ // We'll sort resources and activities into two lists
+ $resources = array();
+ $activities = array();
+
+ // We need to add the section section to the link for each module
+ $sectionlink = '§ion=' . $section . '&sr=' . $sectionreturn;
+
+ foreach ($modules as $module) {
+ if (isset($module->types)) {
+ // This module has a subtype
+ // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
+ $subtypes = array();
+ foreach ($module->types as $subtype) {
+ $subtypes[$subtype->link . $sectionlink] = $subtype->title;
+ }
+
+ // Sort module subtypes into the list
+ if (!empty($module->title)) {
+ // This grouping has a name
+ if ($module->archetype == MOD_CLASS_RESOURCE) {
+ $resources[] = array($module->title=>$subtypes);
+ } else {
+ $activities[] = array($module->title=>$subtypes);
+ }
+ } else {
+ // This grouping does not have a name
+ if ($module->archetype == MOD_CLASS_RESOURCE) {
+ $resources = array_merge($resources, $subtypes);
+ } else {
+ $activities = array_merge($activities, $subtypes);
+ }
+ }
+ } else {
+ // This module has no subtypes
+ if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
+ $resources[$module->link . $sectionlink] = $module->title;
+ } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
+ // System modules cannot be added by user, do not add to dropdown
+ } else {
+ $activities[$module->link . $sectionlink] = $module->title;
+ }
+ }
+ }
+
+ $straddactivity = get_string('addactivity');
+ $straddresource = get_string('addresource');
+ $sectionname = get_section_name($course, $section);
+ $strresourcelabel = get_string('addresourcetosection', null, $sectionname);
+ $stractivitylabel = get_string('addactivitytosection', null, $sectionname);
+
+ $output = html_writer::start_tag('div', array('class' => 'section_add_menus', 'id' => 'add_menus-section-' . $section));
+
+ if (!$vertical) {
+ $output .= html_writer::start_tag('div', array('class' => 'horizontal'));
+ }
+
+ if (!empty($resources)) {
+ $select = new url_select($resources, '', array(''=>$straddresource), "ressection$section");
+ $select->set_help_icon('resources');
+ $select->set_label($strresourcelabel, array('class' => 'accesshide'));
+ $output .= $OUTPUT->render($select);
+ }
+
+ if (!empty($activities)) {
+ $select = new url_select($activities, '', array(''=>$straddactivity), "section$section");
+ $select->set_help_icon('activities');
+ $select->set_label($stractivitylabel, array('class' => 'accesshide'));
+ $output .= $OUTPUT->render($select);
+ }
+
+ if (!$vertical) {
+ $output .= html_writer::end_tag('div');
+ }
+
+ $output .= html_writer::end_tag('div');
+
+ if (course_ajax_enabled($course)) {
+ $straddeither = get_string('addresourceoractivity');
+ // The module chooser link
+ $modchooser = html_writer::start_tag('div', array('class' => 'mdl-right'));
+ $modchooser.= html_writer::start_tag('div', array('class' => 'section-modchooser'));
+ $icon = $OUTPUT->pix_icon('t/add', '');
+ $span = html_writer::tag('span', $straddeither, array('class' => 'section-modchooser-text'));
+ $modchooser .= html_writer::tag('span', $icon . $span, array('class' => 'section-modchooser-link'));
+ $modchooser.= html_writer::end_tag('div');
+ $modchooser.= html_writer::end_tag('div');
+
+ // Wrap the normal output in a noscript div
+ $usemodchooser = get_user_preferences('usemodchooser', $CFG->modchooserdefault);
+ if ($usemodchooser) {
+ $output = html_writer::tag('div', $output, array('class' => 'hiddenifjs addresourcedropdown'));
+ $modchooser = html_writer::tag('div', $modchooser, array('class' => 'visibleifjs addresourcemodchooser'));
+ } else {
+ // If the module chooser is disabled, we need to ensure that the dropdowns are shown even if javascript is disabled
+ $output = html_writer::tag('div', $output, array('class' => 'show addresourcedropdown'));
+ $modchooser = html_writer::tag('div', $modchooser, array('class' => 'hide addresourcemodchooser'));
+ }
+ $output = $modchooser . $output;
+ }
+
+ if ($return) {
+ return $output;
+ } else {
+ echo $output;
+ }
+}
+
+/**
+ * Retrieve all metadata for the requested modules
+ *
+ * @param object $course The Course
+ * @param array $modnames An array containing the list of modules and their
+ * names
+ * @param int $sectionreturn The section to return to
+ * @return array A list of stdClass objects containing metadata about each
+ * module
+ */
+function get_module_metadata($course, $modnames, $sectionreturn = null) {
+ global $CFG, $OUTPUT;
+
+ // get_module_metadata will be called once per section on the page and courses may show
+ // different modules to one another
+ static $modlist = array();
+ if (!isset($modlist[$course->id])) {
+ $modlist[$course->id] = array();
+ }
+
+ $return = array();
+ $urlbase = "/course/mod.php?id=$course->id&sesskey=".sesskey().'&sr='.$sectionreturn.'&add=';
+ foreach($modnames as $modname => $modnamestr) {
+ if (!course_allowed_module($course, $modname)) {
+ continue;
+ }
+ if (isset($modlist[$course->id][$modname])) {
+ // This module is already cached
+ $return[$modname] = $modlist[$course->id][$modname];
+ continue;
+ }
+
+ // Include the module lib
+ $libfile = "$CFG->dirroot/mod/$modname/lib.php";
+ if (!file_exists($libfile)) {
+ continue;
+ }
+ include_once($libfile);
+
+ // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
+ $gettypesfunc = $modname.'_get_types';
+ if (function_exists($gettypesfunc)) {
+ $types = $gettypesfunc();
+ if (is_array($types) && count($types) > 0) {
+ $group = new stdClass();
+ $group->name = $modname;
+ $group->icon = $OUTPUT->pix_icon('icon', '', $modname, array('class' => 'icon'));
+ foreach($types as $type) {
+ if ($type->typestr === '--') {
+ continue;
+ }
+ if (strpos($type->typestr, '--') === 0) {
+ $group->title = str_replace('--', '', $type->typestr);
+ continue;
+ }
+ // Set the Sub Type metadata
+ $subtype = new stdClass();
+ $subtype->title = $type->typestr;
+ $subtype->type = str_replace('&', '&', $type->type);
+ $subtype->name = preg_replace('/.*type=/', '', $subtype->type);
+ $subtype->archetype = $type->modclass;
+
+ // The group archetype should match the subtype archetypes and all subtypes
+ // should have the same archetype
+ $group->archetype = $subtype->archetype;
+
+ if (get_string_manager()->string_exists('help' . $subtype->name, $modname)) {
+ $subtype->help = get_string('help' . $subtype->name, $modname);
+ }
+ $subtype->link = $urlbase . $subtype->type;
+ $group->types[] = $subtype;
+ }
+ $modlist[$course->id][$modname] = $group;
+ }
+ } else {
+ $module = new stdClass();
+ $module->title = get_string('modulename', $modname);
+ $module->name = $modname;
+ $module->link = $urlbase . $modname;
+ $module->icon = $OUTPUT->pix_icon('icon', '', $module->name, array('class' => 'icon'));
+ $sm = get_string_manager();
+ if ($sm->string_exists('modulename_help', $modname)) {
+ $module->help = get_string('modulename_help', $modname);
+ if ($sm->string_exists('modulename_link', $modname)) { // Link to further info in Moodle docs
+ $link = get_string('modulename_link', $modname);
+ $linktext = get_string('morehelp');
+ $module->help .= html_writer::tag('div', $OUTPUT->doc_link($link, $linktext, true), array('class' => 'helpdoclink'));
+ }
+ }
+ $module->archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
+ $modlist[$course->id][$modname] = $module;
+ }
+ if (isset($modlist[$course->id][$modname])) {
+ $return[$modname] = $modlist[$course->id][$modname];
+ } else {
+ debugging("Invalid module metadata configuration for {$modname}");
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Return the course category context for the category with id $categoryid, except
+ * that if $categoryid is 0, return the system context.
+ *
+ * @param integer $categoryid a category id or 0.
+ * @return object the corresponding context
+ */
+function get_category_or_system_context($categoryid) {
+ if ($categoryid) {
+ return context_coursecat::instance($categoryid, IGNORE_MISSING);
+ } else {
+ return context_system::instance();
+ }
+}
+
+/**
+ * Gets the child categories of a given courses category. Uses a static cache
+ * to make repeat calls efficient.
+ *
+ * @param int $parentid the id of a course category.
+ * @return array all the child course categories.
+ */
+function get_child_categories($parentid) {
+ static $allcategories = null;
+
+ // only fill in this variable the first time
+ if (null == $allcategories) {
+ $allcategories = array();
+
+ $categories = get_categories();
+ foreach ($categories as $category) {
+ if (empty($allcategories[$category->parent])) {
+ $allcategories[$category->parent] = array();
+ }
+ $allcategories[$category->parent][] = $category;
+ }
+ }
+
+ if (empty($allcategories[$parentid])) {
+ return array();
+ } else {
+ return $allcategories[$parentid];
+ }
+}
+
+/**
+ * This function recursively travels the categories, building up a nice list
+ * for display. It also makes an array that list all the parents for each
+ * category.
+ *
+ * For example, if you have a tree of categories like:
+ * Miscellaneous (id = 1)
+ * Subcategory (id = 2)
+ * Sub-subcategory (id = 4)
+ * Other category (id = 3)
+ * Then after calling this function you will have
+ * $list = array(1 => 'Miscellaneous', 2 => 'Miscellaneous / Subcategory',
+ * 4 => 'Miscellaneous / Subcategory / Sub-subcategory',
+ * 3 => 'Other category');
+ * $parents = array(2 => array(1), 4 => array(1, 2));
+ *
+ * If you specify $requiredcapability, then only categories where the current
+ * user has that capability will be added to $list, although all categories
+ * will still be added to $parents, and if you only have $requiredcapability
+ * in a child category, not the parent, then the child catgegory will still be
+ * included.
+ *
+ * If you specify the option $excluded, then that category, and all its children,
+ * are omitted from the tree. This is useful when you are doing something like
+ * moving categories, where you do not want to allow people to move a category
+ * to be the child of itself.
+ *
+ * @param array $list For output, accumulates an array categoryid => full category path name
+ * @param array $parents For output, accumulates an array categoryid => list of parent category ids.
+ * @param string/array $requiredcapability if given, only categories where the current
+ * user has this capability will be added to $list. Can also be an array of capabilities,
+ * in which case they are all required.
+ * @param integer $excludeid Omit this category and its children from the lists built.
+ * @param object $category Build the tree starting at this category - otherwise starts at the top level.
+ * @param string $path For internal use, as part of recursive calls.
+ */
+function make_categories_list(&$list, &$parents, $requiredcapability = '',
+ $excludeid = 0, $category = NULL, $path = "") {
+
+ // initialize the arrays if needed
+ if (!is_array($list)) {
+ $list = array();
+ }
+ if (!is_array($parents)) {
+ $parents = array();
+ }
+
+ if (empty($category)) {
+ // Start at the top level.
+ $category = new stdClass;
+ $category->id = 0;
+ } else {
+ // This is the excluded category, don't include it.
+ if ($excludeid > 0 && $excludeid == $category->id) {
+ return;
+ }
+
+ $context = context_coursecat::instance($category->id);
+ $categoryname = format_string($category->name, true, array('context' => $context));
+
+ // Update $path.
+ if ($path) {
+ $path = $path.' / '.$categoryname;
+ } else {
+ $path = $categoryname;
+ }
+
+ // Add this category to $list, if the permissions check out.
+ if (empty($requiredcapability)) {
+ $list[$category->id] = $path;
+
+ } else {
+ $requiredcapability = (array)$requiredcapability;
+ if (has_all_capabilities($requiredcapability, $context)) {
+ $list[$category->id] = $path;
+ }
+ }
+ }
+
+ // Add all the children recursively, while updating the parents array.
+ if ($categories = get_child_categories($category->id)) {
+ foreach ($categories as $cat) {
+ if (!empty($category->id)) {
+ if (isset($parents[$category->id])) {
+ $parents[$cat->id] = $parents[$category->id];
+ }
+ $parents[$cat->id][] = $category->id;
+ }
+ make_categories_list($list, $parents, $requiredcapability, $excludeid, $cat, $path);
+ }
+ }
+}
+
+/**
+ * This function generates a structured array of courses and categories.
+ *
+ * The depth of categories is limited by $CFG->maxcategorydepth however there
+ * is no limit on the number of courses!
+ *
+ * Suitable for use with the course renderers course_category_tree method:
+ * $renderer = $PAGE->get_renderer('core','course');
+ * echo $renderer->course_category_tree(get_course_category_tree());
+ *
+ * @global moodle_database $DB
+ * @param int $id
+ * @param int $depth
+ */
+function get_course_category_tree($id = 0, $depth = 0) {
+ global $DB, $CFG;
+ $viewhiddencats = has_capability('moodle/category:viewhiddencategories', context_system::instance());
+ $categories = get_child_categories($id);
+ $categoryids = array();
+ foreach ($categories as $key => &$category) {
+ if (!$category->visible && !$viewhiddencats) {
+ unset($categories[$key]);
+ continue;
+ }
+ $categoryids[$category->id] = $category;
+ if (empty($CFG->maxcategorydepth) || $depth <= $CFG->maxcategorydepth) {
+ list($category->categories, $subcategories) = get_course_category_tree($category->id, $depth+1);
+ foreach ($subcategories as $subid=>$subcat) {
+ $categoryids[$subid] = $subcat;
+ }
+ $category->courses = array();
+ }
+ }
+
+ if ($depth > 0) {
+ // This is a recursive call so return the required array
+ return array($categories, $categoryids);
+ }
+
+ if (empty($categoryids)) {
+ // No categories available (probably all hidden).
+ return array();
+ }
+
+ // The depth is 0 this function has just been called so we can finish it off
+
+ list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+ list($catsql, $catparams) = $DB->get_in_or_equal(array_keys($categoryids));
+ $sql = "SELECT
+ c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary,c.category
+ $ccselect
+ FROM {course} c
+ $ccjoin
+ WHERE c.category $catsql ORDER BY c.sortorder ASC";
+ if ($courses = $DB->get_records_sql($sql, $catparams)) {
+ // loop throught them
+ foreach ($courses as $course) {
+ if ($course->id == SITEID) {
+ continue;
+ }
+ context_instance_preload($course);
+ if (!empty($course->visible) || has_capability('moodle/course:viewhiddencourses', context_course::instance($course->id))) {
+ $categoryids[$course->category]->courses[$course->id] = $course;
+ }
+ }
+ }
+ return $categories;
+}
+
+/**
+ * Recursive function to print out all the categories in a nice format
+ * with or without courses included
+ */
+function print_whole_category_list($category=NULL, $displaylist=NULL, $parentslist=NULL, $depth=-1, $showcourses = true) {
+ global $CFG;
+
+ // maxcategorydepth == 0 meant no limit
+ if (!empty($CFG->maxcategorydepth) && $depth >= $CFG->maxcategorydepth) {
+ return;
+ }
+
+ if (!$displaylist) {
+ make_categories_list($displaylist, $parentslist);
+ }
+
+ if ($category) {
+ if ($category->visible or has_capability('moodle/category:viewhiddencategories', context_system::instance())) {
+ print_category_info($category, $depth, $showcourses);
+ } else {
+ return; // Don't bother printing children of invisible categories
+ }
+
+ } else {
+ $category = new stdClass();
+ $category->id = "0";
+ }
+
+ if ($categories = get_child_categories($category->id)) { // Print all the children recursively
+ $countcats = count($categories);
+ $count = 0;
+ $first = true;
+ $last = false;
+ foreach ($categories as $cat) {
+ $count++;
+ if ($count == $countcats) {
+ $last = true;
+ }
+ $up = $first ? false : true;
+ $down = $last ? false : true;
+ $first = false;
+
+ print_whole_category_list($cat, $displaylist, $parentslist, $depth + 1, $showcourses);
+ }
+ }
+}
+
+/**
+ * This function will return $options array for html_writer::select(), with whitespace to denote nesting.
+ */
+function make_categories_options() {
+ make_categories_list($cats,$parents);
+ foreach ($cats as $key => $value) {
+ if (array_key_exists($key,$parents)) {
+ if ($indent = count($parents[$key])) {
+ for ($i = 0; $i < $indent; $i++) {
+ $cats[$key] = ' '.$cats[$key];
+ }
+ }
+ }
+ }
+ return $cats;
+}
+
+/**
+ * Prints the category info in indented fashion
+ * This function is only used by print_whole_category_list() above
+ */
+function print_category_info($category, $depth=0, $showcourses = false) {
+ global $CFG, $DB, $OUTPUT;
+
+ $strsummary = get_string('summary');
+
+ $catlinkcss = null;
+ if (!$category->visible) {
+ $catlinkcss = array('class'=>'dimmed');
+ }
+ static $coursecount = null;
+ if (null === $coursecount) {
+ // only need to check this once
+ $coursecount = $DB->count_records('course') <= FRONTPAGECOURSELIMIT;
+ }
+
+ if ($showcourses and $coursecount) {
+ $catimage = '';
+ } else {
+ $catimage = " ";
+ }
+
+ $courses = get_courses($category->id, 'c.sortorder ASC', 'c.id,c.sortorder,c.visible,c.fullname,c.shortname,c.summary');
+ $context = context_coursecat::instance($category->id);
+ $fullname = format_string($category->name, true, array('context' => $context));
+
+ if ($showcourses and $coursecount) {
+ echo '
";
+
+print_course_search($search);
+
+echo $OUTPUT->footer();
+
+/**
+ * Print a list navigation bar
+ * Display page numbers, and a link for displaying all entries
+ * @param int $totalcount number of entry to display
+ * @param int $page page number
+ * @param int $perpage number of entry per page
+ * @param string $encodedsearch
+ * @param string $modulelink module name
+ */
+function print_navigation_bar($totalcount, $page, $perpage, $encodedsearch, $modulelink) {
+ global $OUTPUT;
+ echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "search.php?search=$encodedsearch".$modulelink."&perpage=$perpage");
+
+ // display
+ if ($perpage != 99999 && $totalcount > $perpage) {
+ echo "
');
+ resources.addClass(CSS.SECTION);
+ sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
+ }
+ // Define empty ul as droptarget, so that item could be moved to empty list
+ var tar = new Y.DD.Drop({
+ node: resources,
+ groups: this.groups,
+ padding: '20 0 20 0'
+ });
+
+ // Initialise each resource/activity in this section
+ this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
+ }, this);
+ },
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to resource(s)
+ *
+ * @param baseselector The CSS selector or node to limit scope to
+ * @return void
+ */
+ setup_for_resource : function(baseselector) {
+ Y.Node.all(baseselector).each(function(resourcesnode) {
+ // Replace move icons
+ var move = resourcesnode.one('a.'+CSS.EDITINGMOVE);
+ if (move) {
+ move.replace(this.resourcedraghandle.cloneNode(true));
+ }
+ }, this);
+ },
+
+ get_section_id : function(node) {
+ return Number(node.get('id').replace(/section-/i, ''));
+ },
+
+ get_resource_id : function(node) {
+ return Number(node.get('id').replace(/module-/i, ''));
+ },
+
+ drag_start : function(e) {
+ // Get our drag object
+ var drag = e.target;
+ drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
+ drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
+ },
+
+ drag_dropmiss : function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ drop_hit : function(e) {
+ var drag = e.drag;
+ // Get a reference to our drag node
+ var dragnode = drag.get('node');
+ var dropnode = e.drop.get('node');
+
+ // Add spinner if it not there
+ var spinner = M.util.add_spinner(Y, dragnode.one(CSS.COMMANDSPAN));
+
+ var params = {};
+
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ for (varname in pageparams) {
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseId = this.get('courseid');
+ params['class'] = 'resource';
+ params.field = 'move';
+ params.id = Number(this.get_resource_id(dragnode));
+ params.sectionId = this.get_section_id(dropnode.ancestor(M.course.format.get_section_wrapper(Y), true));
+
+ if (dragnode.next()) {
+ params.beforeId = Number(this.get_resource_id(dragnode.next()));
+ }
+
+ // Do AJAX request
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start : function(tid) {
+ this.lock_drag_handle(drag, CSS.EDITINGMOVE);
+ spinner.show();
+ },
+ success: function(tid, response) {
+ var responsetext = Y.JSON.parse(response.responseText);
+ var params = {element: dragnode, visible: responsetext.visible};
+ M.course.coursebase.invoke_function('set_visibility_resource_ui', params);
+ this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
+ window.setTimeout(function(e) {
+ spinner.hide();
+ }, 250);
+ },
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
+ spinner.hide();
+ // TODO: revert nodes location
+ }
+ },
+ context:this
+ });
+ }
+ }, {
+ NAME : 'course-dragdrop-resource',
+ ATTRS : {
+ courseid : {
+ value : null
+ },
+ ajaxurl : {
+ 'value' : 0
+ },
+ config : {
+ 'value' : 0
+ }
+ }
+ });
+
+ M.course = M.course || {};
+ M.course.init_resource_dragdrop = function(params) {
+ new DRAGRESOURCE(params);
+ }
+ M.course.init_section_dragdrop = function(params) {
+ new DRAGSECTION(params);
+ }
+}, '@VERSION@', {requires:['base', 'node', 'io', 'dom', 'dd', 'dd-scroll', 'moodle-core-dragdrop', 'moodle-core-notification', 'moodle-course-coursebase']});
diff --git a/yui/formatchooser/formatchooser.js b/yui/formatchooser/formatchooser.js
new file mode 100644
index 0000000..0597c4b
--- /dev/null
+++ b/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/yui/modchooser/modchooser.js b/yui/modchooser/modchooser.js
new file mode 100644
index 0000000..10fdded
--- /dev/null
+++ b/yui/modchooser/modchooser.js
@@ -0,0 +1,166 @@
+YUI.add('moodle-course-modchooser', function(Y) {
+ var CSS = {
+ PAGECONTENT : 'div#page-content',
+ SECTION : 'li.section',
+ SECTIONMODCHOOSER : 'span.section-modchooser-link',
+ SITEMENU : 'div.block_site_main_menu',
+ SITETOPIC : 'div.sitetopic'
+ };
+
+ var MODCHOOSERNAME = 'course-modchooser';
+
+ var MODCHOOSER = function() {
+ MODCHOOSER.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(MODCHOOSER, M.core.chooserdialogue, {
+ // The current section ID
+ sectionid : null,
+
+ // The hidden element holding the jump param
+ jumplink : null,
+
+ initializer : function(config) {
+ var dialogue = Y.one('.chooserdialoguebody');
+ var header = Y.one('.choosertitle');
+ var params = {};
+ this.setup_chooser_dialogue(dialogue, header, params);
+
+ // Initialize existing sections and register for dynamically created sections
+ this.setup_for_section();
+ M.course.coursebase.register_module(this);
+
+ // Catch the page toggle
+ Y.all('.block_settings #settingsnav .type_course .modchoosertoggle a').on('click', this.toggle_mod_chooser, this);
+ },
+ /**
+ * Update any section areas within the scope of the specified
+ * selector with AJAX equivalents
+ *
+ * @param baseselector The selector to limit scope to
+ * @return void
+ */
+ setup_for_section : function(baseselector) {
+ if (!baseselector) {
+ var baseselector = CSS.PAGECONTENT;
+ }
+
+ // Setup for site topics
+ Y.one(baseselector).all(CSS.SITETOPIC).each(function(section) {
+ this._setup_for_section(section);
+ }, this);
+
+ // Setup for standard course topics
+ Y.one(baseselector).all(CSS.SECTION).each(function(section) {
+ this._setup_for_section(section);
+ }, this);
+
+ // Setup for the block site menu
+ Y.one(baseselector).all(CSS.SITEMENU).each(function(section) {
+ this._setup_for_section(section);
+ }, this);
+ },
+ _setup_for_section : function(section, sectionid) {
+ var chooserspan = section.one(CSS.SECTIONMODCHOOSER);
+ if (!chooserspan) {
+ return;
+ }
+ var chooserlink = Y.Node.create("");
+ chooserspan.get('children').each(function(node) {
+ chooserlink.appendChild(node);
+ });
+ chooserspan.insertBefore(chooserlink);
+ chooserlink.on('click', this.display_mod_chooser, this);
+ },
+ /**
+ * Display the module chooser
+ *
+ * @param e Event Triggering Event
+ * @param secitonid integer The ID of the section triggering the dialogue
+ * @return void
+ */
+ display_mod_chooser : function (e) {
+ // Set the section for this version of the dialogue
+ if (e.target.ancestor(CSS.SITETOPIC)) {
+ // The site topic has a sectionid of 1
+ this.sectionid = 1;
+ } else if (e.target.ancestor(CSS.SECTION)) {
+ var section = e.target.ancestor(CSS.SECTION);
+ this.sectionid = section.get('id').replace('section-', '');
+ } else if (e.target.ancestor(CSS.SITEMENU)) {
+ // The block site menu has a sectionid of 0
+ this.sectionid = 0;
+ }
+ this.display_chooser(e);
+ },
+ toggle_mod_chooser : function(e) {
+ // Get the add section link
+ var modchooserlinks = Y.all('div.addresourcemodchooser');
+
+ // Get the dropdowns
+ var dropdowns = Y.all('div.addresourcedropdown');
+
+ if (modchooserlinks.size() == 0) {
+ // Continue with non-js action if there are no modchoosers to add
+ return;
+ }
+
+ // We need to update the text and link
+ var togglelink = Y.one('.block_settings #settingsnav .type_course .modchoosertoggle a');
+
+ // The actual text is in the last child
+ var toggletext = togglelink.get('lastChild');
+
+ var usemodchooser;
+ // Determine whether they're currently hidden
+ if (modchooserlinks.item(0).hasClass('visibleifjs')) {
+ // The modchooser is currently visible, hide it
+ usemodchooser = 0;
+ modchooserlinks
+ .removeClass('visibleifjs')
+ .addClass('hiddenifjs');
+ dropdowns
+ .addClass('visibleifjs')
+ .removeClass('hiddenifjs');
+ toggletext.set('data', M.util.get_string('modchooserenable', 'moodle'));
+ togglelink.set('href', togglelink.get('href').replace('off', 'on'));
+ } else {
+ // The modchooser is currently not visible, show it
+ usemodchooser = 1;
+ modchooserlinks
+ .addClass('visibleifjs')
+ .removeClass('hiddenifjs');
+ dropdowns
+ .removeClass('visibleifjs')
+ .addClass('hiddenifjs');
+ toggletext.set('data', M.util.get_string('modchooserdisable', 'moodle'));
+ togglelink.set('href', togglelink.get('href').replace('on', 'off'));
+ }
+
+ M.util.set_user_preference('usemodchooser', usemodchooser);
+
+ // Prevent the page from reloading
+ e.preventDefault();
+ },
+ option_selected : function(thisoption) {
+ // Add the sectionid to the URL
+ this.jumplink.set('value', thisoption.get('value') + '§ion=' + this.sectionid);
+ }
+ },
+ {
+ NAME : MODCHOOSERNAME,
+ ATTRS : {
+ maxheight : {
+ value : 800
+ }
+ }
+ });
+ M.course = M.course || {};
+ M.course.init_chooser = function(config) {
+ return new MODCHOOSER(config);
+ }
+},
+'@VERSION@', {
+ requires:['base', 'overlay', 'moodle-core-chooserdialogue', 'transition']
+}
+);
diff --git a/yui/toolboxes/toolboxes.js b/yui/toolboxes/toolboxes.js
new file mode 100644
index 0000000..4e6ad15
--- /dev/null
+++ b/yui/toolboxes/toolboxes.js
@@ -0,0 +1,800 @@
+YUI.add('moodle-course-toolboxes', function(Y) {
+ WAITICON = {'pix':"i/loading_small",'component':'moodle'};
+ // The CSS selectors we use
+ var CSS = {
+ ACTIVITYLI : 'li.activity',
+ COMMANDSPAN : 'span.commands',
+ SPINNERCOMMANDSPAN : 'span.commands',
+ CONTENTAFTERLINK : 'div.contentafterlink',
+ DELETE : 'a.editing_delete',
+ DIMCLASS : 'dimmed',
+ DIMMEDTEXT : 'dimmed_text',
+ EDITTITLE : 'a.editing_title',
+ EDITTITLECLASS : 'edittitle',
+ GENERICICONCLASS : 'iconsmall',
+ GROUPSNONE : 'a.editing_groupsnone',
+ GROUPSSEPARATE : 'a.editing_groupsseparate',
+ GROUPSVISIBLE : 'a.editing_groupsvisible',
+ HASLABEL : 'label',
+ HIDE : 'a.editing_hide',
+ HIGHLIGHT : 'a.editing_highlight',
+ INSTANCENAME : 'span.instancename',
+ LIGHTBOX : 'lightbox',
+ MODINDENTCOUNT : 'mod-indent-',
+ MODINDENTDIV : 'div.mod-indent',
+ MODINDENTHUGE : 'mod-indent-huge',
+ MODULEIDPREFIX : 'module-',
+ MOVELEFT : 'a.editing_moveleft',
+ MOVELEFTCLASS : 'editing_moveleft',
+ MOVERIGHT : 'a.editing_moveright',
+ PAGECONTENT : 'div#page-content',
+ RIGHTSIDE : '.right',
+ SECTIONHIDDENCLASS : 'hidden',
+ SECTIONIDPREFIX : 'section-',
+ SECTIONLI : 'li.section',
+ SHOW : 'a.editing_show',
+ SHOWHIDE : 'a.editing_showhide',
+ CONDITIONALHIDDEN : 'conditionalhidden',
+ AVAILABILITYINFODIV : 'div.availabilityinfo',
+ SHOWCLASS : 'editing_show',
+ HIDECLASS : 'hide'
+ };
+
+ /**
+ * The toolbox classes
+ *
+ * TOOLBOX is a generic class which should never be directly instantiated
+ * RESOURCETOOLBOX is a class extending TOOLBOX containing code specific to resources
+ * SECTIONTOOLBOX is a class extending TOOLBOX containing code specific to sections
+ */
+ var TOOLBOX = function() {
+ TOOLBOX.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(TOOLBOX, Y.Base, {
+ /**
+ * Toggle the visibility and availability for the specified
+ * resource show/hide button
+ */
+ toggle_hide_resource_ui : function(button) {
+ var element = button.ancestor(CSS.ACTIVITYLI);
+ var hideicon = button.one('img');
+
+ var dimarea;
+ var toggle_class;
+ if (this.is_label(element)) {
+ toggle_class = CSS.DIMMEDTEXT;
+ dimarea = element.all(CSS.MODINDENTDIV + ' > div').item(1);
+ } else {
+ toggle_class = CSS.DIMCLASS;
+ dimarea = element.one('a');
+ }
+
+ var status = '';
+ var value;
+ if (button.hasClass(CSS.SHOWCLASS)) {
+ status = 'hide';
+ value = 1;
+ } else {
+ status = 'show';
+ value = 0;
+ }
+ // Update button info.
+ var newstring = M.util.get_string(status, 'moodle');
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('t/' + status)
+ });
+ button.set('title', newstring);
+ button.set('className', 'editing_'+status);
+
+ // If activity is conditionally hidden, then don't toggle.
+ if (!dimarea.hasClass(CSS.CONDITIONALHIDDEN)) {
+ // Change the UI.
+ dimarea.toggleClass(toggle_class);
+ // We need to toggle dimming on the description too.
+ element.all(CSS.CONTENTAFTERLINK).toggleClass(CSS.DIMMEDTEXT);
+ }
+ // Toggle availablity info for conditional activities.
+ var availabilityinfo = element.one(CSS.AVAILABILITYINFODIV);
+
+ if (availabilityinfo) {
+ availabilityinfo.toggleClass(CSS.HIDECLASS);
+ }
+ return value;
+ },
+ /**
+ * Send a request using the REST API
+ *
+ * @param data The data to submit
+ * @param statusspinner (optional) A statusspinner which may contain a section loader
+ * @param optionalconfig (optional) Any additional configuration to submit
+ * @return response responseText field from responce
+ */
+ send_request : function(data, statusspinner, optionalconfig) {
+ // Default data structure
+ if (!data) {
+ data = {};
+ }
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ for (varname in pageparams) {
+ data[varname] = pageparams[varname];
+ }
+
+ data.sesskey = M.cfg.sesskey;
+ data.courseId = this.get('courseid');
+
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ // Define the configuration to send with the request
+ var responsetext = [];
+ var config = {
+ method: 'POST',
+ data: data,
+ on: {
+ success: function(tid, response) {
+ try {
+ responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ } catch (e) {}
+ if (statusspinner) {
+ window.setTimeout(function(e) {
+ statusspinner.hide();
+ }, 400);
+ }
+ },
+ failure : function(tid, response) {
+ if (statusspinner) {
+ statusspinner.hide();
+ }
+ new M.core.ajaxException(response);
+ }
+ },
+ context: this,
+ sync: true
+ }
+
+ // Apply optional config
+ if (optionalconfig) {
+ for (varname in optionalconfig) {
+ config[varname] = optionalconfig[varname];
+ }
+ }
+
+ if (statusspinner) {
+ statusspinner.show();
+ }
+
+ // Send the request
+ Y.io(uri, config);
+ return responsetext;
+ },
+ is_label : function(target) {
+ return target.hasClass(CSS.HASLABEL);
+ },
+ /**
+ * Return the module ID for the specified element
+ *
+ * @param element The
element to determine a module-id number for
+ * @return string The module ID
+ */
+ get_element_id : function(element) {
+ return element.get('id').replace(CSS.MODULEIDPREFIX, '');
+ },
+ /**
+ * Return the module ID for the specified element
+ *
+ * @param element The
element to determine a module-id number for
+ * @return string The module ID
+ */
+ get_section_id : function(section) {
+ return section.get('id').replace(CSS.SECTIONIDPREFIX, '');
+ }
+ },
+ {
+ NAME : 'course-toolbox',
+ ATTRS : {
+ // The ID of the current course
+ courseid : {
+ 'value' : 0
+ },
+ ajaxurl : {
+ 'value' : 0
+ },
+ config : {
+ 'value' : 0
+ }
+ }
+ }
+ );
+
+
+ var RESOURCETOOLBOX = function() {
+ RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
+ // Variables
+ GROUPS_NONE : 0,
+ GROUPS_SEPARATE : 1,
+ GROUPS_VISIBLE : 2,
+
+ /**
+ * Initialize the resource toolbox
+ *
+ * Updates all span.commands with relevant handlers and other required changes
+ */
+ initializer : function(config) {
+ this.setup_for_resource();
+ M.course.coursebase.register_module(this);
+
+ var prefix = CSS.ACTIVITYLI + ' ' + CSS.COMMANDSPAN + ' ';
+ Y.delegate('click', this.edit_resource_title, CSS.PAGECONTENT, prefix + CSS.EDITTITLE, this);
+ Y.delegate('click', this.move_left, CSS.PAGECONTENT, prefix + CSS.MOVELEFT, this);
+ Y.delegate('click', this.move_right, CSS.PAGECONTENT, prefix + CSS.MOVERIGHT, this);
+ Y.delegate('click', this.delete_resource, CSS.PAGECONTENT, prefix + CSS.DELETE, this);
+ Y.delegate('click', this.toggle_hide_resource, CSS.PAGECONTENT, prefix + CSS.HIDE, this);
+ Y.delegate('click', this.toggle_hide_resource, CSS.PAGECONTENT, prefix + CSS.SHOW, this);
+ Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSNONE, this);
+ Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSSEPARATE, this);
+ Y.delegate('click', this.toggle_groupmode, CSS.PAGECONTENT, prefix + CSS.GROUPSVISIBLE, this);
+ },
+
+ /**
+ * Update any span.commands within the scope of the specified
+ * selector with AJAX equivelants
+ *
+ * @param baseselector The selector to limit scope to
+ * @return void
+ */
+ setup_for_resource : function(baseselector) {
+ if (!baseselector) {
+ var baseselector = CSS.PAGECONTENT + ' ' + CSS.ACTIVITYLI;
+ }
+
+ Y.all(baseselector).each(this._setup_for_resource, this);
+ },
+ _setup_for_resource : function(toolboxtarget) {
+ toolboxtarget = Y.one(toolboxtarget);
+
+ // Set groupmode attribute for use by this.toggle_groupmode()
+ var groups;
+ groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSNONE);
+ groups.setAttribute('groupmode', this.GROUPS_NONE);
+
+ groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSSEPARATE);
+ groups.setAttribute('groupmode', this.GROUPS_SEPARATE);
+
+ groups = toolboxtarget.all(CSS.COMMANDSPAN + ' ' + CSS.GROUPSVISIBLE);
+ groups.setAttribute('groupmode', this.GROUPS_VISIBLE);
+ },
+ move_left : function(e) {
+ this.move_leftright(e, -1);
+ },
+ move_right : function(e) {
+ this.move_leftright(e, 1);
+ },
+ move_leftright : function(e, direction) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.ACTIVITYLI);
+
+ // And we need to determine the current and new indent level
+ var indentdiv = element.one(CSS.MODINDENTDIV);
+ var indent = indentdiv.getAttribute('class').match(/mod-indent-(\d{1,})/);
+
+ if (indent) {
+ var oldindent = parseInt(indent[1]);
+ var newindent = Math.max(0, (oldindent + parseInt(direction)));
+ indentdiv.removeClass(indent[0]);
+ } else {
+ var oldindent = 0;
+ var newindent = 1;
+ }
+
+ // Perform the move
+ indentdiv.addClass(CSS.MODINDENTCOUNT + newindent);
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'indent',
+ 'value' : newindent,
+ 'id' : this.get_element_id(element)
+ };
+ var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+ this.send_request(data, spinner);
+
+ // Handle removal/addition of the moveleft button
+ if (newindent == 0) {
+ element.one(CSS.MOVELEFT).remove();
+ } else if (newindent == 1 && oldindent == 0) {
+ this.add_moveleft(element);
+ }
+
+ // Handle massive indentation to match non-ajax display
+ var hashugeclass = indentdiv.hasClass(CSS.MODINDENTHUGE);
+ if (newindent > 15 && !hashugeclass) {
+ indentdiv.addClass(CSS.MODINDENTHUGE);
+ } else if (newindent <= 15 && hashugeclass) {
+ indentdiv.removeClass(CSS.MODINDENTHUGE);
+ }
+ },
+ delete_resource : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.ACTIVITYLI);
+
+ var confirmstring = '';
+ if (this.is_label(element)) {
+ // Labels are slightly different to other activities
+ var plugindata = {
+ type : M.util.get_string('pluginname', 'label')
+ }
+ confirmstring = M.util.get_string('deletechecktype', 'moodle', plugindata)
+ } else {
+ var plugindata = {
+ type : M.util.get_string('pluginname', element.getAttribute('class').match(/modtype_([^\s]*)/)[1]),
+ name : element.one(CSS.INSTANCENAME).get('firstChild').get('data')
+ }
+ confirmstring = M.util.get_string('deletechecktypename', 'moodle', plugindata);
+ }
+
+ // Confirm element removal
+ if (!confirm(confirmstring)) {
+ return false;
+ }
+
+ // Actually remove the element
+ element.remove();
+ var data = {
+ 'class' : 'resource',
+ 'action' : 'DELETE',
+ 'id' : this.get_element_id(element)
+ };
+ this.send_request(data);
+ },
+ toggle_hide_resource : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Return early if the current section is hidden
+ var section = e.target.ancestor(M.course.format.get_section_selector(Y));
+ if (section && section.hasClass(CSS.SECTIONHIDDENCLASS)) {
+ return;
+ }
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.ACTIVITYLI);
+
+ var button = e.target.ancestor('a', true);
+
+ var value = this.toggle_hide_resource_ui(button);
+
+ // Send the request
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'visible',
+ 'value' : value,
+ 'id' : this.get_element_id(element)
+ };
+ var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+ this.send_request(data, spinner);
+ return false; // Need to return false to stop the delegate for the new state firing
+ },
+ toggle_groupmode : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.ACTIVITYLI);
+
+ var button = e.target.ancestor('a', true);
+ var icon = button.one('img');
+
+ // Current Mode
+ var groupmode = button.getAttribute('groupmode');
+ groupmode++;
+ if (groupmode > 2) {
+ groupmode = 0;
+ }
+ button.setAttribute('groupmode', groupmode);
+
+ var newtitle = '';
+ var iconsrc = '';
+ switch (groupmode) {
+ case this.GROUPS_NONE:
+ newtitle = 'groupsnone';
+ iconsrc = M.util.image_url('t/groupn');
+ break;
+ case this.GROUPS_SEPARATE:
+ newtitle = 'groupsseparate';
+ iconsrc = M.util.image_url('t/groups');
+ break;
+ case this.GROUPS_VISIBLE:
+ newtitle = 'groupsvisible';
+ iconsrc = M.util.image_url('t/groupv');
+ break;
+ }
+ newtitle = M.util.get_string('clicktochangeinbrackets', 'moodle',
+ M.util.get_string(newtitle, 'moodle'));
+
+ // Change the UI
+ icon.setAttrs({
+ 'alt' : newtitle,
+ 'src' : iconsrc
+ });
+ button.setAttribute('title', newtitle);
+
+ // And send the request
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'groupmode',
+ 'value' : groupmode,
+ 'id' : this.get_element_id(element)
+ };
+ var spinner = M.util.add_spinner(Y, element.one(CSS.SPINNERCOMMANDSPAN));
+ this.send_request(data, spinner);
+ return false; // Need to return false to stop the delegate for the new state firing
+ },
+ /**
+ * Add the moveleft button
+ * This is required after moving left from an initial position of 0
+ *
+ * @param target The encapsulating
element
+ */
+ add_moveleft : function(target) {
+ var left_string = M.util.get_string('moveleft', 'moodle');
+ var moveimage = 't/left'; // ltr mode
+ if ( Y.one(document.body).hasClass('dir-rtl') ) {
+ moveimage = 't/right';
+ } else {
+ moveimage = 't/left';
+ }
+ var newicon = Y.Node.create('')
+ .addClass(CSS.GENERICICONCLASS)
+ .setAttrs({
+ 'src' : M.util.image_url(moveimage, 'moodle'),
+ 'alt' : left_string
+ });
+ var moveright = target.one(CSS.MOVERIGHT);
+ var newlink = moveright.getAttribute('href').replace('indent=1', 'indent=-1');
+ var anchor = new Y.Node.create('')
+ .setStyle('cursor', 'pointer')
+ .addClass(CSS.MOVELEFTCLASS)
+ .setAttribute('href', newlink)
+ .setAttribute('title', left_string);
+ anchor.appendChild(newicon);
+ moveright.insert(anchor, 'before');
+ },
+ /**
+ * Edit the title for the resource
+ */
+ edit_resource_title : function(e) {
+ // Get the element we're working on
+ var element = e.target.ancestor(CSS.ACTIVITYLI);
+ var elementdiv = element.one('div');
+ var instancename = element.one(CSS.INSTANCENAME);
+ var currenttitle = instancename.get('firstChild');
+ var oldtitle = currenttitle.get('data');
+ var titletext = oldtitle;
+ var editbutton = element.one('a.' + CSS.EDITTITLECLASS + ' img');
+
+ // Handle events for edit_resource_title
+ var listenevents = [];
+ var thisevent;
+
+ // Grab the anchor so that we can swap it with the edit form
+ var anchor = instancename.ancestor('a');
+
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'gettitle',
+ 'id' : this.get_element_id(element)
+ };
+
+ // Try to retrieve the existing string from the server
+ var response = this.send_request(data, editbutton);
+ if (response.instancename) {
+ titletext = response.instancename;
+ }
+
+ // Create the editor and submit button
+ var editor = Y.Node.create('')
+ .setAttrs({
+ 'name' : 'title',
+ 'value' : titletext,
+ 'autocomplete' : 'off'
+ })
+ .addClass('titleeditor');
+ var editform = Y.Node.create('')
+ .addClass('activityinstance')
+ .setAttribute('action', '#');
+ var editinstructions = Y.Node.create('')
+ .addClass('editinstructions')
+ .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+ var activityicon = element.one('img.activityicon').cloneNode();
+
+ // Clear the existing content and put the editor in
+ currenttitle.set('data', '');
+ editform.appendChild(activityicon);
+ editform.appendChild(editor);
+ anchor.replace(editform);
+ elementdiv.appendChild(editinstructions);
+ e.preventDefault();
+
+ // Focus and select the editor text
+ editor.focus().select();
+
+ // Handle removal of the editor
+ var clear_edittitle = function() {
+ // Detach all listen events to prevent duplicate triggers
+ var thisevent;
+ while (thisevent = listenevents.shift()) {
+ thisevent.detach();
+ }
+
+ if (editinstructions) {
+ // Convert back to anchor and remove instructions
+ editform.replace(anchor);
+ editinstructions.remove();
+ editinstructions = null;
+ }
+ }
+
+ // Handle cancellation of the editor
+ var cancel_edittitle = function(e) {
+ clear_edittitle();
+
+ // Set the title and anchor back to their previous settings
+ currenttitle.set('data', oldtitle);
+ };
+
+ // Cancel the edit if we lose focus or the escape key is pressed
+ thisevent = editor.on('blur', cancel_edittitle);
+ listenevents.push(thisevent);
+ thisevent = Y.one('document').on('keydown', function(e) {
+ if (e.keyCode === 27) {
+ e.preventDefault();
+ cancel_edittitle(e);
+ }
+ });
+ listenevents.push(thisevent);
+
+ // Handle form submission
+ thisevent = editform.on('submit', function(e) {
+ // We don't actually want to submit anything
+ e.preventDefault();
+
+ // Clear the edit title boxes
+ clear_edittitle();
+
+ // We only accept strings which have valid content
+ var newtitle = Y.Lang.trim(editor.get('value'));
+ if (newtitle != null && newtitle != "" && newtitle != titletext) {
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'updatetitle',
+ 'title' : newtitle,
+ 'id' : this.get_element_id(element)
+ };
+ var response = this.send_request(data, editbutton);
+ if (response.instancename) {
+ currenttitle.set('data', response.instancename);
+ }
+ } else {
+ // Invalid content. Set the title back to it's original contents
+ currenttitle.set('data', oldtitle);
+ }
+ }, this);
+ listenevents.push(thisevent);
+ },
+ /**
+ * Set the visibility of the current resource (identified by the element)
+ * to match the hidden parameter (this is not a toggle).
+ * Only changes the visibility in the browser (no ajax update).
+ * @param args An object with 'element' being the A node containing the resource
+ * and 'visible' being the state that the visiblity should be set to.
+ * @return void
+ */
+ set_visibility_resource_ui: function(args) {
+ var element = args.element;
+ var shouldbevisible = args.visible;
+ var buttonnode = element.one(CSS.SHOW);
+ var visible = (buttonnode === null);
+ if (visible) {
+ buttonnode = element.one(CSS.HIDE);
+ }
+ if (visible != shouldbevisible) {
+ this.toggle_hide_resource_ui(buttonnode);
+ }
+ }
+ }, {
+ NAME : 'course-resource-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ format : {
+ 'value' : 'topics'
+ }
+ }
+ });
+
+ var SECTIONTOOLBOX = function() {
+ SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
+ }
+
+ Y.extend(SECTIONTOOLBOX, TOOLBOX, {
+ /**
+ * Initialize the toolboxes module
+ *
+ * Updates all span.commands with relevant handlers and other required changes
+ */
+ initializer : function(config) {
+ this.setup_for_section();
+ M.course.coursebase.register_module(this);
+
+ // Section Highlighting
+ Y.delegate('click', this.toggle_highlight, CSS.PAGECONTENT, CSS.SECTIONLI + ' ' + CSS.HIGHLIGHT, this);
+ // Section Visibility
+ Y.delegate('click', this.toggle_hide_section, CSS.PAGECONTENT, CSS.SECTIONLI + ' ' + CSS.SHOWHIDE, this);
+ },
+ /**
+ * Update any section areas within the scope of the specified
+ * selector with AJAX equivelants
+ *
+ * @param baseselector The selector to limit scope to
+ * @return void
+ */
+ setup_for_section : function(baseselector) {
+ // Left here for potential future use - not currently needed due to YUI delegation in initializer()
+ /*if (!baseselector) {
+ var baseselector = CSS.PAGECONTENT;
+ }
+
+ Y.all(baseselector).each(this._setup_for_section, this);*/
+ },
+ _setup_for_section : function(toolboxtarget) {
+ // Left here for potential future use - not currently needed due to YUI delegation in initializer()
+ },
+ toggle_hide_section : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the section we're working on
+ var section = e.target.ancestor(M.course.format.get_section_selector(Y));
+ var button = e.target.ancestor('a', true);
+ var hideicon = button.one('img');
+
+ // The value to submit
+ var value;
+ // The status text for strings and images
+ var status;
+
+ if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
+ section.addClass(CSS.SECTIONHIDDENCLASS);
+ value = 0;
+ status = 'show';
+
+ } else {
+ section.removeClass(CSS.SECTIONHIDDENCLASS);
+ value = 1;
+ status = 'hide';
+ }
+
+ var newstring = M.util.get_string(status + 'fromothers', 'format_' + this.get('format'));
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('i/' + status)
+ });
+ button.set('title', newstring);
+
+ // Change the highlight status
+ var data = {
+ 'class' : 'section',
+ 'field' : 'visible',
+ 'id' : this.get_section_id(section.ancestor(M.course.format.get_section_wrapper(Y), true)),
+ 'value' : value
+ };
+
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+
+ var response = this.send_request(data, lightbox);
+
+ var activities = section.all(CSS.ACTIVITYLI);
+ activities.each(function(node) {
+ if (node.one(CSS.SHOW)) {
+ var button = node.one(CSS.SHOW);
+ } else {
+ var button = node.one(CSS.HIDE);
+ }
+ var activityid = this.get_element_id(node);
+
+ if (Y.Array.indexOf(response.resourcestotoggle, activityid) != -1) {
+ this.toggle_hide_resource_ui(button);
+ }
+ }, this);
+ },
+ toggle_highlight : function(e) {
+ // Prevent the default button action
+ e.preventDefault();
+
+ // Get the section we're working on
+ var section = e.target.ancestor(M.course.format.get_section_selector(Y));
+ var button = e.target.ancestor('a', true);
+ var buttonicon = button.one('img');
+
+ // Determine whether the marker is currently set
+ var togglestatus = section.hasClass('current');
+ var value = 0;
+
+ // Set the current highlighted item text
+ var old_string = M.util.get_string('markthistopic', 'moodle');
+ Y.one(CSS.PAGECONTENT)
+ .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT)
+ .set('title', old_string);
+ Y.one(CSS.PAGECONTENT)
+ .all(M.course.format.get_section_selector(Y) + '.current ' + CSS.HIGHLIGHT + ' img')
+ .set('alt', old_string)
+ .set('src', M.util.image_url('i/marker'));
+
+ // Remove the highlighting from all sections
+ var allsections = Y.one(CSS.PAGECONTENT).all(M.course.format.get_section_selector(Y))
+ .removeClass('current');
+
+ // Then add it if required to the selected section
+ if (!togglestatus) {
+ section.addClass('current');
+ value = this.get_section_id(section.ancestor(M.course.format.get_section_wrapper(Y), true));
+ var new_string = M.util.get_string('markedthistopic', 'moodle');
+ button
+ .set('title', new_string);
+ buttonicon
+ .set('alt', new_string)
+ .set('src', M.util.image_url('i/marked'));
+ }
+
+ // Change the highlight status
+ var data = {
+ 'class' : 'course',
+ 'field' : 'marker',
+ 'value' : value
+ };
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+ this.send_request(data, lightbox);
+ }
+ }, {
+ NAME : 'course-section-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ format : {
+ 'value' : 'topics'
+ }
+ }
+ });
+
+ M.course = M.course || {};
+
+ M.course.init_resource_toolbox = function(config) {
+ return new RESOURCETOOLBOX(config);
+ };
+
+ M.course.init_section_toolbox = function(config) {
+ return new SECTIONTOOLBOX(config);
+ };
+
+},
+'@VERSION@', {
+ requires : ['base', 'node', 'io', 'moodle-course-coursebase']
+}
+);