]> git.parisson.com Git - teleforma-moodle-course.git/commitdiff
init
authorguillaume pellerin <parisson@campusicp.fr>
Mon, 16 Sep 2013 14:41:32 +0000 (16:41 +0200)
committerguillaume pellerin <parisson@campusicp.fr>
Mon, 16 Sep 2013 14:41:32 +0000 (16:41 +0200)
88 files changed:
category.php [new file with mode: 0644]
changenumsections.php [new file with mode: 0644]
completion.js [new file with mode: 0644]
completion.php [new file with mode: 0644]
completion_form.php [new file with mode: 0644]
delete.php [new file with mode: 0644]
delete_category_form.php [new file with mode: 0644]
dndupload.js [new file with mode: 0644]
dndupload.php [new file with mode: 0644]
dnduploadlib.php [new file with mode: 0644]
edit.php [new file with mode: 0644]
edit_form.php [new file with mode: 0644]
editcategory.php [new file with mode: 0644]
editcategory_form.php [new file with mode: 0644]
editsection.php [new file with mode: 0644]
editsection_form.php [new file with mode: 0644]
enrol.php [new file with mode: 0644]
externallib.php [new file with mode: 0644]
format/README.txt [new file with mode: 0644]
format/formatlegacy.php [new file with mode: 0644]
format/lib.php [new file with mode: 0644]
format/renderer.php [new file with mode: 0644]
format/scorm/format.php [new file with mode: 0644]
format/scorm/lang/en/format_scorm.php [new file with mode: 0644]
format/scorm/lib.php [new file with mode: 0644]
format/scorm/version.php [new file with mode: 0644]
format/social/format.php [new file with mode: 0644]
format/social/lang/en/format_social.php [new file with mode: 0644]
format/social/lib.php [new file with mode: 0644]
format/social/version.php [new file with mode: 0644]
format/topics/format.js [new file with mode: 0644]
format/topics/format.php [new file with mode: 0644]
format/topics/lang/en/format_topics.php [new file with mode: 0644]
format/topics/lib.php [new file with mode: 0644]
format/topics/renderer.php [new file with mode: 0644]
format/topics/styles.css [new file with mode: 0644]
format/topics/version.php [new file with mode: 0644]
format/upgrade.txt [new file with mode: 0644]
format/weeks/format.js [new file with mode: 0644]
format/weeks/format.php [new file with mode: 0644]
format/weeks/lang/en/format_weeks.php [new file with mode: 0644]
format/weeks/lib.php [new file with mode: 0644]
format/weeks/renderer.php [new file with mode: 0644]
format/weeks/styles.css [new file with mode: 0644]
format/weeks/version.php [new file with mode: 0644]
index.php [new file with mode: 0644]
info.php [new file with mode: 0644]
jumpto.php [new file with mode: 0644]
lib.php [new file with mode: 0644]
loginas.php [new file with mode: 0644]
mod.php [new file with mode: 0644]
modduplicate.php [new file with mode: 0644]
modedit.php [new file with mode: 0644]
moodleform_mod.php [new file with mode: 0644]
pending.php [new file with mode: 0644]
publish/backup.php [new file with mode: 0644]
publish/forms.php [new file with mode: 0644]
publish/hubselector.php [new file with mode: 0644]
publish/index.php [new file with mode: 0644]
publish/lib.php [new file with mode: 0644]
publish/metadata.php [new file with mode: 0644]
publish/renderer.php [new file with mode: 0644]
recent.php [new file with mode: 0644]
recent_form.php [new file with mode: 0644]
renderer.php [new file with mode: 0644]
report.php [new file with mode: 0644]
report/lib.php [new file with mode: 0644]
request.php [new file with mode: 0644]
request_form.php [new file with mode: 0644]
reset.php [new file with mode: 0644]
reset_form.php [new file with mode: 0644]
resources.php [new file with mode: 0644]
rest.php [new file with mode: 0644]
scales.php [new file with mode: 0644]
search.php [new file with mode: 0644]
style.css [new file with mode: 0644]
switchrole.php [new file with mode: 0644]
tests/courselib_test.php [new file with mode: 0644]
tests/courserequest_test.php [new file with mode: 0644]
tests/externallib_test.php [new file with mode: 0644]
togglecompletion.php [new file with mode: 0644]
user.php [new file with mode: 0644]
view.php [new file with mode: 0644]
yui/coursebase/coursebase.js [new file with mode: 0644]
yui/dragdrop/dragdrop.js [new file with mode: 0644]
yui/formatchooser/formatchooser.js [new file with mode: 0644]
yui/modchooser/modchooser.js [new file with mode: 0644]
yui/toolboxes/toolboxes.js [new file with mode: 0644]

diff --git a/category.php b/category.php
new file mode 100644 (file)
index 0000000..71ea312
--- /dev/null
@@ -0,0 +1,479 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Displays the top level category or all courses
+ * In editing mode, allows the admin to edit a category,
+ * and rearrange courses
+ *
+ * @package    core
+ * @subpackage course
+ * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once("../config.php");
+require_once($CFG->dirroot.'/course/lib.php');
+require_once($CFG->libdir.'/textlib.class.php');
+
+$id = required_param('id', PARAM_INT); // Category id
+$page = optional_param('page', 0, PARAM_INT); // which page to show
+$categoryedit = optional_param('categoryedit', -1, PARAM_BOOL);
+$hide = optional_param('hide', 0, PARAM_INT);
+$show = optional_param('show', 0, PARAM_INT);
+$moveup = optional_param('moveup', 0, PARAM_INT);
+$movedown = optional_param('movedown', 0, PARAM_INT);
+$moveto = optional_param('moveto', 0, PARAM_INT);
+$resort = optional_param('resort', 0, PARAM_BOOL);
+$sesskey = optional_param('sesskey', '', PARAM_RAW);
+
+// MDL-27824 - This is a temporary fix until we have the proper
+// way to check/initialize $CFG value.
+// @todo MDL-35138 remove this temporary solution
+if (!empty($CFG->coursesperpage)) {
+    $defaultperpage =  $CFG->coursesperpage;
+} else {
+    $defaultperpage = 20;
+}
+$perpage = optional_param('perpage', $defaultperpage, PARAM_INT); // how many per page
+
+if (empty($id)) {
+    print_error("unknowcategory");
+}
+
+$PAGE->set_category_by_id($id);
+$PAGE->set_url(new moodle_url('/course/category.php', array('id' => $id)));
+// This is sure to be the category context
+$context = $PAGE->context;
+// And the object has been loaded for us no need for another DB call
+$category = $PAGE->category;
+
+$canedit = can_edit_in_category($category->id);
+if ($canedit) {
+    if ($categoryedit !== -1) {
+        $USER->editing = $categoryedit;
+    }
+    require_login();
+    $editingon = $PAGE->user_is_editing();
+} else {
+    if ($CFG->forcelogin) {
+        require_login();
+    }
+    $editingon = false;
+}
+
+if (!$category->visible) {
+    require_capability('moodle/category:viewhiddencategories', $context);
+}
+
+$canmanage = has_capability('moodle/category:manage', $context);
+$sesskeyprovided = !empty($sesskey) && confirm_sesskey($sesskey);
+
+// Process any category actions.
+if ($canmanage && $resort && $sesskeyprovided) {
+    // Resort the category if requested
+    if ($courses = get_courses($category->id, '', 'c.id,c.fullname,c.sortorder')) {
+        collatorlib::asort_objects_by_property($courses, 'fullname', collatorlib::SORT_NATURAL);
+        $i = 1;
+        foreach ($courses as $course) {
+            $DB->set_field('course', 'sortorder', $category->sortorder+$i, array('id'=>$course->id));
+            $i++;
+        }
+        fix_course_sortorder(); // should not be needed
+    }
+}
+
+// Process any course actions.
+if ($editingon && $sesskeyprovided) {
+
+    // Move a specified course to a new category
+    if (!empty($moveto) and $data = data_submitted()) {
+        // Some courses are being moved
+        // user must have category update in both cats to perform this
+        require_capability('moodle/category:manage', $context);
+        require_capability('moodle/category:manage', context_coursecat::instance($moveto));
+
+        if (!$destcategory = $DB->get_record('course_categories', array('id' => $data->moveto))) {
+            print_error('cannotfindcategory', '', '', $data->moveto);
+        }
+
+        $courses = array();
+        foreach ($data as $key => $value) {
+            if (preg_match('/^c\d+$/', $key)) {
+                $courseid = substr($key, 1);
+                array_push($courses, $courseid);
+
+                // check this course's category
+                if ($movingcourse = $DB->get_record('course', array('id'=>$courseid))) {
+                    if ($movingcourse->category != $id ) {
+                        print_error('coursedoesnotbelongtocategory');
+                    }
+                } else {
+                    print_error('cannotfindcourse');
+                }
+            }
+        }
+        move_courses($courses, $data->moveto);
+    }
+
+    // Hide or show a course
+    if (!empty($hide) or !empty($show)) {
+        if (!empty($hide)) {
+            $course = $DB->get_record('course', array('id' => $hide));
+            $visible = 0;
+        } else {
+            $course = $DB->get_record('course', array('id' => $show));
+            $visible = 1;
+        }
+
+        if ($course) {
+            $coursecontext = context_course::instance($course->id);
+            require_capability('moodle/course:visibility', $coursecontext);
+            // Set the visibility of the course. we set the old flag when user manually changes visibility of course.
+            $DB->update_record('course', array('id' => $course->id, 'visible' => $visible, 'visibleold' => $visible, 'timemodified' => time()));
+            add_to_log($course->id, "course", ($visible ? 'show' : 'hide'), "edit.php?id=$course->id", $course->id);
+        }
+    }
+
+
+    // Move a course up or down
+    if (!empty($moveup) or !empty($movedown)) {
+        require_capability('moodle/category:manage', $context);
+
+        // Ensure the course order has continuous ordering
+        fix_course_sortorder();
+        $swapcourse = NULL;
+
+        if (!empty($moveup)) {
+            if ($movecourse = $DB->get_record('course', array('id' => $moveup))) {
+                $swapcourse = $DB->get_record('course', array('sortorder' => $movecourse->sortorder - 1));
+            }
+        } else {
+            if ($movecourse = $DB->get_record('course', array('id' => $movedown))) {
+                $swapcourse = $DB->get_record('course', array('sortorder' => $movecourse->sortorder + 1));
+            }
+        }
+        if ($swapcourse and $movecourse) {
+            // check course's category
+            if ($movecourse->category != $id) {
+                print_error('coursedoesnotbelongtocategory');
+            }
+            $DB->set_field('course', 'sortorder', $swapcourse->sortorder, array('id' => $movecourse->id));
+            $DB->set_field('course', 'sortorder', $movecourse->sortorder, array('id' => $swapcourse->id));
+            add_to_log($movecourse->id, "course", "move", "edit.php?id=$movecourse->id", $movecourse->id);
+        }
+    }
+
+} // End of editing stuff
+
+// Prepare the standard URL params for this page. We'll need them later.
+$urlparams = array('id' => $id);
+if ($page) {
+    $urlparams['page'] = $page;
+}
+if ($perpage) {
+    $urlparams['perpage'] = $perpage;
+}
+
+// Begin output
+if ($editingon && can_edit_in_category()) {
+    // Integrate into the admin tree only if the user can edit categories at the top level,
+    // otherwise the admin block does not appear to this user, and you get an error.
+    require_once($CFG->libdir . '/adminlib.php');
+    navigation_node::override_active_url(new moodle_url('/course/category.php', array('id' => $id)));
+    admin_externalpage_setup('coursemgmt', '', $urlparams, $CFG->wwwroot . '/course/category.php');
+    $PAGE->set_context($context);   // Ensure that we are actually showing blocks etc for the cat context
+
+    $settingsnode = $PAGE->settingsnav->find_active_node();
+    if ($settingsnode) {
+        $settingsnode->make_inactive();
+        $settingsnode->force_open();
+        $PAGE->navbar->add($settingsnode->text, $settingsnode->action);
+    }
+    echo $OUTPUT->header();
+} else {
+    $site = get_site();
+    $PAGE->set_title("$site->shortname: $category->name");
+    $PAGE->set_heading($site->fullname);
+    $PAGE->set_button(print_course_search('', true, 'navbar'));
+    $PAGE->set_pagelayout('coursecategory');
+    echo $OUTPUT->header();
+}
+
+/// Print the category selector
+$displaylist = array();
+$notused = array();
+make_categories_list($displaylist, $notused);
+
+echo '<div class="categorypicker">';
+$select = new single_select(new moodle_url('/course/category.php'), 'id', $displaylist, $category->id, null, 'switchcategory');
+$select->set_label(get_string('categories').':');
+echo $OUTPUT->render($select);
+echo '</div>';
+
+/// 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 '<form id="movecourses" action="category.php" method="post"><div>';
+    echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
+    echo '<table border="0" cellspacing="2" cellpadding="4" class="generalbox boxaligncenter"><tr>';
+    echo '<th class="header" scope="col">'.get_string('courses').'</th>';
+    if ($editingon) {
+        echo '<th class="header" scope="col">'.get_string('edit').'</th>';
+        echo '<th class="header" scope="col">'.get_string('select').'</th>';
+    } else {
+        echo '<th class="header" scope="col">&nbsp;</th>';
+    }
+    echo '</tr>';
+
+    $count = 0;
+    $abletomovecourses = false;  // for now
+
+    // Checking if we are at the first or at the last page, to allow courses to
+    // be moved up and down beyond the paging border
+    if ($totalcount > $perpage) {
+        $atfirstpage = ($page == 0);
+        if ($perpage > 0) {
+            $atlastpage = (($page + 1) == ceil($totalcount / $perpage));
+        } else {
+            $atlastpage = true;
+        }
+    } else {
+        $atfirstpage = true;
+        $atlastpage = true;
+    }
+
+    $baseurl = new moodle_url('/course/category.php', $urlparams + array('sesskey' => sesskey()));
+    foreach ($courses as $acourse) {
+        $coursecontext = context_course::instance($acourse->id);
+
+        $count++;
+        $up = ($count > 1 || !$atfirstpage);
+        $down = ($count < $numcourses || !$atlastpage);
+
+        $linkcss = $acourse->visible ? '' : ' class="dimmed" ';
+        echo '<tr>';
+        $coursename = get_course_display_name_for_list($acourse);
+        echo '<td><a '.$linkcss.' href="view.php?id='.$acourse->id.'">'. format_string($coursename) .'</a></td>';
+        if ($editingon) {
+            echo '<td>';
+            if (has_capability('moodle/course:update', $coursecontext)) {
+                $url = new moodle_url('/course/edit.php', array('id' => $acourse->id, 'category' => $id, 'returnto' => 'category'));
+                echo $OUTPUT->action_icon($url, new pix_icon('t/edit', get_string('settings')));
+            }
+
+            // role assignment link
+            if (has_capability('moodle/course:enrolreview', $coursecontext)) {
+                $url = new moodle_url('/enrol/users.php', array('id' => $acourse->id));
+                echo $OUTPUT->action_icon($url, new pix_icon('t/enrolusers', get_string('enrolledusers', 'enrol')));
+            }
+
+            if (can_delete_course($acourse->id)) {
+                $url = new moodle_url('/course/delete.php', array('id' => $acourse->id));
+                echo $OUTPUT->action_icon($url, new pix_icon('t/delete', get_string('delete')));
+            }
+
+            // MDL-8885, users with no capability to view hidden courses, should not be able to lock themselves out
+            if (has_capability('moodle/course:visibility', $coursecontext) && has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+                if (!empty($acourse->visible)) {
+                    $url = new moodle_url($baseurl, array('hide' => $acourse->id));
+                    echo $OUTPUT->action_icon($url, new pix_icon('t/hide', get_string('hide')));
+                } else {
+                    $url = new moodle_url($baseurl, array('show' => $acourse->id));
+                    echo $OUTPUT->action_icon($url, new pix_icon('t/show', get_string('show')));
+                }
+            }
+
+            if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
+                $url = new moodle_url('/backup/backup.php', array('id' => $acourse->id));
+                echo $OUTPUT->action_icon($url, new pix_icon('t/backup', get_string('backup')));
+            }
+
+            if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
+                $url = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id));
+                echo $OUTPUT->action_icon($url, new pix_icon('t/restore', get_string('restore')));
+            }
+
+            if ($canmanage) {
+                if ($up) {
+                    $url = new moodle_url($baseurl, array('moveup' => $acourse->id));
+                    echo $OUTPUT->action_icon($url, new pix_icon('t/up', get_string('moveup')));
+                }
+
+                if ($down) {
+                    $url = new moodle_url($baseurl, array('movedown' => $acourse->id));
+                    echo $OUTPUT->action_icon($url, new pix_icon('t/down', get_string('movedown')));
+                }
+                $abletomovecourses = true;
+            }
+
+            echo '</td>';
+            echo '<td align="center">';
+            echo '<input type="checkbox" name="c'.$acourse->id.'" />';
+            echo '</td>';
+        } else {
+            echo '<td align="right">';
+            // print enrol info
+            if ($icons = enrol_get_course_info_icons($acourse)) {
+                foreach ($icons as $pix_icon) {
+                    echo $OUTPUT->render($pix_icon);
+                }
+            }
+            if (!empty($acourse->summary)) {
+                $url = new moodle_url("/course/info.php?id=$acourse->id");
+                echo $OUTPUT->action_link($url, '<img alt="'.get_string('info').'" class="icon" src="'.$OUTPUT->pix_url('i/info') . '" />',
+                    new popup_action('click', $url, 'courseinfo'), array('title'=>get_string('summary')));
+            }
+            echo "</td>";
+        }
+        echo "</tr>";
+    }
+
+    if ($abletomovecourses) {
+        $movetocategories = array();
+        $notused = array();
+        make_categories_list($movetocategories, $notused, 'moodle/category:manage');
+        $movetocategories[$category->id] = get_string('moveselectedcoursesto');
+        echo '<tr><td colspan="3" align="right">';
+        echo html_writer::label(get_string('moveselectedcoursesto'), 'movetoid', false, array('class' => 'accesshide'));
+        echo html_writer::select($movetocategories, 'moveto', $category->id, null, array('id'=>'movetoid', 'class' => 'autosubmit'));
+        $PAGE->requires->yui_module('moodle-core-formautosubmit',
+            'M.core.init_formautosubmit',
+            array(array('selectid' => 'movetoid', 'nothing' => $category->id))
+        );
+        echo '<input type="hidden" name="id" value="'.$category->id.'" />';
+        echo '</td></tr>';
+    }
+
+    echo '</table>';
+    echo '</div></form>';
+    echo '<br />';
+}
+
+echo '<div class="buttons">';
+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 '</div>';
+
+print_course_search();
+
+echo $OUTPUT->footer();
diff --git a/changenumsections.php b/changenumsections.php
new file mode 100644 (file)
index 0000000..deed367
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..4d9542a
--- /dev/null
@@ -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<inputs.length; i++) {
+            switch (inputs[i].name) {
+                 case 'id':
+                     cmid = inputs[i].value;
+                     break;
+                  case 'completionstate':
+                     completionstate = inputs[i].value;
+                     state = Y.one(inputs[i]);
+                     break;
+                  case 'modulename':
+                     modulename = Y.one(inputs[i]);
+                     break;
+            }
+            if (inputs[i].type == 'image') {
+                image = Y.one(inputs[i]);
+            }
+        }
+
+        // start spinning the ajax indicator
+        var ajax = Y.Node.create('<div class="ajaxworking" />');
+        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 (file)
index 0000000..559ccdb
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program 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 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program 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:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+// Edit course completion settings
+
+require_once('../config.php');
+require_once('lib.php');
+require_once($CFG->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 (file)
index 0000000..1f68101
--- /dev/null
@@ -0,0 +1,222 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.com                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program 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 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program 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:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->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 (file)
index 0000000..a47822b
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+      // Admin-only code to delete a course utterly
+
+    require_once(dirname(__FILE__) . '/../config.php');
+    require_once($CFG->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<br /><br />" . 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 (file)
index 0000000..8c32405
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->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 = '<ul>';
+            if ($containscategories) {
+                $contents .= '<li>' . get_string('subcategories') . '</li>';
+            }
+            if ($containscourses) {
+                $contents .= '<li>' . get_string('courses') . '</li>';
+            }
+            if ($containsquestions) {
+                $contents .= '<li>' . get_string('questionsinthequestionbank') . '</li>';
+            }
+            $contents .= '</ul>';
+            $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 (file)
index 0000000..1ee8395
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+/**
+ * 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<this.handlers.types.length; i++) {
+            switch (this.handlers.types[i].identifier) {
+            case 'text':
+            case 'text/html':
+                handletext = true;
+                break;
+            case 'url':
+                handlelink = true;
+                break;
+            }
+        }
+        $msgident = 'dndworking';
+        if (handlefile) {
+            $msgident += 'file';
+        }
+        if (handletext) {
+            $msgident += 'text';
+        }
+        if (handlelink) {
+            $msgident += 'link';
+        }
+        div.setContent(M.util.get_string($msgident, 'moodle'));
+
+        var fadeanim = new Y.Anim({
+            node: '#dndupload-status',
+            from: {
+                opacity: 0.0,
+                top: '-30px'
+            },
+
+            to: {
+                opacity: 1.0,
+                top: '0px'
+            },
+            duration: 0.5
+        });
+        fadeanim.once('end', function(e) {
+            this.set('reverse', 1);
+            Y.later(3000, this, 'run', null, false);
+        });
+        fadeanim.run();
+    },
+
+    /**
+     * Check the browser has the required functionality
+     * @return true if browser supports drag/drop upload
+     */
+    browser_supported: function() {
+        if (typeof FileReader == 'undefined') {
+            return false;
+        }
+        if (typeof FormData == 'undefined') {
+            return false;
+        }
+        return true;
+    },
+
+    /**
+     * Initialise drag events on node container, all events need
+     * to be processed for drag and drop to work
+     * @param el the element to add events to
+     */
+    init_events: function(el) {
+        this.Y.on('dragenter', this.drag_enter, el, this);
+        this.Y.on('dragleave', this.drag_leave, el, this);
+        this.Y.on('dragover',  this.drag_over,  el, this);
+        this.Y.on('drop',      this.drop,       el, this);
+    },
+
+    /**
+     * Work out which course section a given element is in
+     * @param el the child DOM element within the section
+     * @return the DOM element representing the section
+     */
+    get_section: function(el) {
+        var sectionclasses = this.sectionclasses;
+        return el.ancestor( function(test) {
+            var i;
+            for (i=0; i<sectionclasses.length; i++) {
+                if (!test.hasClass(sectionclasses[i])) {
+                    return false;
+                }
+                return true;
+            }
+        }, true);
+    },
+
+    /**
+     * Work out the number of the section we have been dropped on to, from the section element
+     * @param DOMElement section the selected section
+     * @return int the section number
+     */
+    get_section_number: function(section) {
+        var sectionid = section.get('id').split('-');
+        if (sectionid.length < 2 || sectionid[0] != 'section') {
+            return false;
+        }
+        return parseInt(sectionid[1]);
+    },
+
+    /**
+     * Check if the event includes data of the given type
+     * @param e the event details
+     * @param type the data type to check for
+     * @return true if the data type is found in the event data
+     */
+    types_includes: function(e, type) {
+        var i;
+        var types = e._event.dataTransfer.types;
+        for (i=0; i<types.length; i++) {
+            if (types[i] == type) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    /**
+     * Look through the event data, checking it against the registered data types
+     * (in order of priority) and return details of the first matching data type
+     * @param e the event details
+     * @return mixed false if not found or an object {
+     *           realtype: the type as given by the browser
+     *           addmessage: the message to show to the user during dragging
+     *           namemessage: the message for requesting a name for the resource from the user
+     *           type: the identifier of the type (may match several 'realtype's)
+     *           }
+     */
+    drag_type: function(e) {
+        // Check there is some data attached.
+        if (e._event.dataTransfer === null) {
+            return false;
+        }
+        if (e._event.dataTransfer.types === null) {
+            return false;
+        }
+        if (e._event.dataTransfer.types.length == 0) {
+            return false;
+        }
+
+        // Check for files first.
+        if (this.types_includes(e, 'Files')) {
+            if (e.type != 'drop' || e._event.dataTransfer.files.length != 0) {
+                if (this.handlers.filehandlers.length == 0) {
+                    return false; // No available file handlers - ignore this drag.
+                }
+                return {
+                    realtype: 'Files',
+                    addmessage: M.util.get_string('addfilehere', 'moodle'),
+                    namemessage: null, // Should not be asked for anyway
+                    type: 'Files'
+                };
+            }
+        }
+
+        // Check each of the registered types.
+        var types = this.handlers.types;
+        for (var i=0; i<types.length; i++) {
+            // Check each of the different identifiers for this type
+            var dttypes = types[i].datatransfertypes;
+            for (var j=0; j<dttypes.length; j++) {
+                if (this.types_includes(e, dttypes[j])) {
+                    return {
+                        realtype: dttypes[j],
+                        addmessage: types[i].addmessage,
+                        namemessage: types[i].namemessage,
+                        type: types[i].identifier,
+                        handlers: types[i].handlers
+                    };
+                }
+            }
+        }
+        return false; // No types we can handle
+    },
+
+    /**
+     * Check the content of the drag/drop includes a type we can handle, then, if
+     * it is, notify the browser that we want to handle it
+     * @param event e
+     * @return string type of the event or false
+     */
+    check_drag: function(e) {
+        var type = this.drag_type(e);
+        if (type) {
+            // Notify browser that we will handle this drag/drop
+            e.stopPropagation();
+            e.preventDefault();
+        }
+        return type;
+    },
+
+    /**
+     * Handle a dragenter event: add a suitable 'add here' message
+     * when a drag event occurs, containing a registered data type
+     * @param e event data
+     * @return false to prevent the event from continuing to be processed
+     */
+    drag_enter: function(e) {
+        if (!(type = this.check_drag(e))) {
+            return false;
+        }
+
+        var section = this.get_section(e.currentTarget);
+        if (!section) {
+            return false;
+        }
+
+        if (this.currentsection && this.currentsection != section) {
+            this.currentsection = section;
+            this.entercount = 1;
+        } else {
+            this.entercount++;
+            if (this.entercount > 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 = '&nbsp;';
+        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<filehandlers.length; i++) {
+            if (filehandlers[i].extension == '*' || filehandlers[i].extension == extension) {
+                handlers.push(filehandlers[i]);
+            }
+        }
+
+        if (handlers.length == 0) {
+            // No handlers at all (not even 'resource'?)
+            return;
+        }
+
+        if (handlers.length == 1) {
+            this.upload_file(file, section, sectionnumber, handlers[0].module);
+            return;
+        }
+
+        this.file_handler_dialog(handlers, extension, file, section, sectionnumber);
+    },
+
+    /**
+     * Show a dialog box, allowing the user to choose what to do with the file they are uploading
+     * @param handlers the available handlers to choose between
+     * @param extension the extension of the file being uploaded
+     * @param file the File object being uploaded
+     * @param section the DOM element of the section being uploaded to
+     * @param sectionnumber the number of the selected course section
+     */
+    file_handler_dialog: function(handlers, extension, file, section, sectionnumber) {
+        if (this.uploaddialog) {
+            var details = new Object();
+            details.isfile = true;
+            details.handlers = handlers;
+            details.extension = extension;
+            details.file = file;
+            details.section = section;
+            details.sectionnumber = sectionnumber;
+            this.uploadqueue.push(details);
+            return;
+        }
+        this.uploaddialog = true;
+
+        var timestamp = new Date().getTime();
+        var uploadid = Math.round(Math.random()*100000)+'-'+timestamp;
+        var content = '';
+        var sel;
+        if (extension in this.lastselected) {
+            sel = this.lastselected[extension];
+        } else {
+            sel = handlers[0].module;
+        }
+        content += '<p>'+M.util.get_string('actionchoice', 'moodle', file.name)+'</p>';
+        content += '<div id="dndupload_handlers'+uploadid+'">';
+        for (var i=0; i<handlers.length; i++) {
+            var id = 'dndupload_handler'+uploadid+handlers[i].module;
+            var checked = (handlers[i].module == sel) ? 'checked="checked" ' : '';
+            content += '<input type="radio" name="handler" value="'+handlers[i].module+'" id="'+id+'" '+checked+'/>';
+            content += ' <label for="'+id+'">';
+            content += handlers[i].message;
+            content += '</label><br/>';
+        }
+        content += '</div>';
+
+        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 += '<label for="'+nameid+'">'+type.namemessage+'</label>';
+        content += ' <input type="text" id="'+nameid+'" value="" />';
+        if (type.handlers.length > 1) {
+            content += '<div id="dndupload_handlers'+uploadid+'">';
+            var sel = type.handlers[0].module;
+            for (var i=0; i<type.handlers.length; i++) {
+                var id = 'dndupload_handler'+uploadid;
+                var checked = (type.handlers[i].module == sel) ? 'checked="checked" ' : '';
+                content += '<input type="radio" name="handler" value="'+type.handlers[i].module+'" id="'+id+'" '+checked+'/>';
+                content += ' <label for="'+id+'">';
+                content += type.handlers[i].message;
+                content += '</label><br/>';
+            }
+            content += '</div>';
+        }
+
+        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 (file)
index 0000000..c4df5a8
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..26e41e8
--- /dev/null
@@ -0,0 +1,696 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..91f28ed
--- /dev/null
+++ b/edit.php
@@ -0,0 +1,173 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..8f7e875
--- /dev/null
@@ -0,0 +1,358 @@
+<?php
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->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 (file)
index 0000000..fab0f52
--- /dev/null
@@ -0,0 +1,162 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..3e3cfe3
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once ($CFG->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 (file)
index 0000000..c75e3dc
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..7fdbdf6
--- /dev/null
@@ -0,0 +1,330 @@
+<?php
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->libdir.'/filelib.php');
+require_once($CFG->libdir.'/completionlib.php');
+require_once($CFG->libdir.'/gradelib.php');
+
+/**
+ * Default form for editing course section
+ *
+ * Course format plugins may specify different editing form to use
+ */
+class editsection_form extends moodleform {
+
+    function definition() {
+
+        $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 (file)
index 0000000..aceb88b
--- /dev/null
+++ b/enrol.php
@@ -0,0 +1,30 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..b5c82f4
--- /dev/null
@@ -0,0 +1,1813 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+
+/**
+ * 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 (file)
index 0000000..9bb62ab
--- /dev/null
@@ -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:
+
+<?php
+$string['formatyourformat']='Your format'; // Name to display for format
+$string['nameyourformat']='section'; // Name of a section within your format
+?>
+
+  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.
+
+  <?php
+  $plugin->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 (file)
index 0000000..f353a4b
--- /dev/null
@@ -0,0 +1,363 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..f0ccf9b
--- /dev/null
@@ -0,0 +1,941 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..ceb3045
--- /dev/null
@@ -0,0 +1,797 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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('<br />', $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 (file)
index 0000000..465f7f7
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+      // format.php - course format featuring single activity
+      //              included from view.php
+
+    $module = $course->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 (file)
index 0000000..8b684c1
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..60e9a26
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..d1641c9
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version 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 (file)
index 0000000..b661c22
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+      // format.php - course format featuring social forum
+      //              included from view.php
+
+    $strgroups  = get_string('groups');
+    $strgroupmy = get_string('groupmy');
+    $editing    = $PAGE->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 .= '<div class="editinglink"><a title="'.$streditsummary.'" '.
+                                 '   href="modedit.php?update='.$cm->id.'&amp;sesskey='.sesskey().'">'.
+                                 '<img src="'.$OUTPUT->pix_url('t/edit') . '" '.
+                                 ' class="icon edit" alt="'.$streditsummary.'" /></a></div>';
+            }
+            echo $OUTPUT->box($introcontent, 'generalbox', 'intro');
+        }
+
+        echo '<div class="subscribelink">', forum_get_subscribe_link($forum, $context), '</div>';
+        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 (file)
index 0000000..0316353
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..3c84a15
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..645f02a
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version 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 (file)
index 0000000..172d1d9
--- /dev/null
@@ -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:
+ * <ul class="topics">
+ *  <li class="section">...</li>
+ *  <li class="section">...</li>
+ *   ...
+ * </ul>
+ *
+ * @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 (file)
index 0000000..cf2c052
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..f9766d9
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..debe045
--- /dev/null
@@ -0,0 +1,299 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..e293a9d
--- /dev/null
@@ -0,0 +1,105 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..432ad96
--- /dev/null
@@ -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 (file)
index 0000000..dff32bf
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version 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 (file)
index 0000000..4c5d131
--- /dev/null
@@ -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 (file)
index 0000000..6433702
--- /dev/null
@@ -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:
+ * <ul class="weeks">
+ *  <li class="section">...</li>
+ *  <li class="section">...</li>
+ *   ...
+ * </ul>
+ *
+ * @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 (file)
index 0000000..2f98855
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..fd6c24d
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..40686d4
--- /dev/null
@@ -0,0 +1,349 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..33590fd
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 (file)
index 0000000..80d3479
--- /dev/null
@@ -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 (file)
index 0000000..16971a6
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version 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 (file)
index 0000000..7253d44
--- /dev/null
+++ b/index.php
@@ -0,0 +1,398 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * 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 '<table class="generaltable editcourse boxaligncenter"><tr class="header">';
+echo '<th class="header" scope="col">'.$strcategories.'</th>';
+echo '<th class="header" scope="col">'.$strcourses.'</th>';
+echo '<th class="header" scope="col">'.$stredit.'</th>';
+echo '<th class="header" scope="col">'.$strmovecategoryto.'</th>';
+echo '</tr>';
+
+print_category_edit(NULL, $displaylist, $parentlist);
+echo '</table>';
+
+echo '<div class="buttons">';
+if (has_capability('moodle/course:create', $systemcontext)) {
+    // print create course link to first category
+    $options = array('category' => $CFG->defaultrequestcategory);
+    $options['returnto'] = 'topcat';
+    echo $OUTPUT->single_button(new moodle_url('edit.php', $options), get_string('addnewcourse'), 'get');
+}
+
+// Print button for creating new categories
+if (has_capability('moodle/category:manage', $systemcontext)) {
+    $options = array('parent'=>0);
+    echo $OUTPUT->single_button(new moodle_url('editcategory.php', $options), get_string('addnewcategory'), 'get');
+}
+
+print_course_request_buttons($systemcontext);
+echo '</div>';
+
+echo $OUTPUT->footer();
+
+function print_category_edit($category, $displaylist, $parentslist, $depth=-1, $up=false, $down=false) {
+/// Recursive function to print all the categories ready for editing
+
+    global $CFG, $USER, $OUTPUT;
+
+    static $str = NULL;
+
+    if (is_null($str)) {
+        $str = new stdClass;
+        $str->edit     = get_string('edit');
+        $str->delete   = get_string('delete');
+        $str->moveup   = get_string('moveup');
+        $str->movedown = get_string('movedown');
+        $str->edit     = get_string('editthiscategory');
+        $str->hide     = get_string('hide');
+        $str->show     = get_string('show');
+        $str->cohorts  = get_string('cohorts', 'cohort');
+        $str->spacer = $OUTPUT->spacer().' ';
+    }
+
+    if (!empty($category)) {
+
+        if (!isset($category->context)) {
+            $category->context = context_coursecat::instance($category->id);
+        }
+
+        echo '<tr><td align="left" class="name">';
+        for ($i=0; $i<$depth;$i++) {
+            echo '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+        }
+        $linkcss = $category->visible ? '' : ' class="dimmed" ';
+        echo '<a '.$linkcss.' title="'.$str->edit.'" '.
+             ' href="category.php?id='.$category->id.'&amp;categoryedit=on&amp;sesskey='.sesskey().'">'.
+             format_string($category->name, true, array('context' => $category->context)).'</a>';
+        echo '</td>';
+
+        echo '<td class="count">'.$category->coursecount.'</td>';
+
+        echo '<td class="icons">';    /// Print little icons
+
+        if (has_capability('moodle/category:manage', $category->context)) {
+            echo '<a title="'.$str->edit.'" href="editcategory.php?id='.$category->id.'"><img'.
+                 ' src="'.$OUTPUT->pix_url('t/edit') . '" class="iconsmall" alt="'.$str->edit.'" /></a> ';
+
+            echo '<a title="'.$str->delete.'" href="index.php?delete='.$category->id.'&amp;sesskey='.sesskey().'"><img'.
+                 ' src="'.$OUTPUT->pix_url('t/delete') . '" class="iconsmall" alt="'.$str->delete.'" /></a> ';
+
+            if (!empty($category->visible)) {
+                echo '<a title="'.$str->hide.'" href="index.php?hide='.$category->id.'&amp;sesskey='.sesskey().'"><img'.
+                     ' src="'.$OUTPUT->pix_url('t/hide') . '" class="iconsmall" alt="'.$str->hide.'" /></a> ';
+            } else {
+                echo '<a title="'.$str->show.'" href="index.php?show='.$category->id.'&amp;sesskey='.sesskey().'"><img'.
+                     ' src="'.$OUTPUT->pix_url('t/show') . '" class="iconsmall" alt="'.$str->show.'" /></a> ';
+            }
+
+            if (has_capability('moodle/cohort:manage', $category->context) or has_capability('moodle/cohort:view', $category->context)) {
+                echo '<a title="'.$str->cohorts.'" href="'.$CFG->wwwroot.'/cohort/index.php?contextid='.$category->context->id.'"><img'.
+                     ' src="'.$OUTPUT->pix_url('t/cohort') . '" class="iconsmall" alt="'.$str->cohorts.'" /></a> ';
+            }
+
+            if ($up) {
+                echo '<a title="'.$str->moveup.'" href="index.php?moveup='.$category->id.'&amp;sesskey='.sesskey().'"><img'.
+                     ' src="'.$OUTPUT->pix_url('t/up') . '" class="iconsmall" alt="'.$str->moveup.'" /></a> ';
+            } else {
+                echo $str->spacer;
+            }
+            if ($down) {
+                echo '<a title="'.$str->movedown.'" href="index.php?movedown='.$category->id.'&amp;sesskey='.sesskey().'"><img'.
+                     ' src="'.$OUTPUT->pix_url('t/down') . '" class="iconsmall" alt="'.$str->movedown.'" /></a> ';
+            } else {
+                echo $str->spacer;
+            }
+        }
+        echo '</td>';
+
+        echo '<td align="left">';
+        if (has_capability('moodle/category:manage', $category->context)) {
+            $tempdisplaylist = $displaylist;
+            unset($tempdisplaylist[$category->id]);
+            foreach ($parentslist as $key => $parents) {
+                if (in_array($category->id, $parents)) {
+                    unset($tempdisplaylist[$key]);
+                }
+            }
+            $popupurl = new moodle_url("index.php?move=$category->id&sesskey=".sesskey());
+            $select = new single_select($popupurl, 'moveto', $tempdisplaylist, $category->parent, null, "moveform$category->id");
+            $select->set_label(get_string('frontpagecategorynames'), array('class' => 'accesshide'));
+            echo $OUTPUT->render($select);
+        }
+        echo '</td>';
+        echo '</tr>';
+    } else {
+        $category = new stdClass();
+        $category->id = '0';
+    }
+
+    if ($categories = get_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_category_edit($cat, $displaylist, $parentslist, $depth+1, $up, $down);
+        }
+    }
+}
diff --git a/info.php b/info.php
new file mode 100644 (file)
index 0000000..9440168
--- /dev/null
+++ b/info.php
@@ -0,0 +1,61 @@
+<?php
+
+/// Displays external information about a course
+
+    require_once("../config.php");
+    require_once("lib.php");
+
+    $id   = optional_param('id', false, PARAM_INT); // Course id
+    $name = optional_param('name', false, PARAM_RAW); // Course short name
+
+    if (!$id and !$name) {
+        print_error("unspecifycourseid");
+    }
+
+    if ($name) {
+        if (!$course = $DB->get_record("course", array("shortname"=>$name))) {
+            print_error("invalidshortname");
+        }
+    } else {
+        if (!$course = $DB->get_record("course", array("id"=>$id))) {
+            print_error("invalidcourseid");
+        }
+    }
+
+    $site = get_site();
+
+    if ($CFG->forcelogin) {
+        require_login();
+    }
+
+    $context = context_course::instance($course->id);
+    if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $context)) {
+        print_error('coursehidden', '', $CFG->wwwroot .'/');
+    }
+
+    $PAGE->set_context($context);
+    $PAGE->set_pagelayout('popup');
+    $PAGE->set_url('/course/info.php', array('id' => $course->id));
+    $PAGE->set_title(get_string("summaryof", "", $course->fullname));
+    $PAGE->set_heading(get_string('courseinfo'));
+    $PAGE->set_course($course);
+    $PAGE->navbar->add(get_string('summary'));
+
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading('<a href="view.php?id='.$course->id.'">'.format_string($course->fullname) . '</a><br />(' . format_string($course->shortname, true, array('context' => $context)) . ')');
+
+    // print enrol info
+    if ($texts = enrol_get_course_description_texts($course)) {
+        echo $OUTPUT->box_start('generalbox icons');
+        echo implode($texts);
+        echo $OUTPUT->box_end();
+    }
+
+    $courserenderer = $PAGE->get_renderer('core', 'course');
+    echo $courserenderer->course_info_box($course);
+
+    echo "<br />";
+
+    echo $OUTPUT->footer();
+
+
diff --git a/jumpto.php b/jumpto.php
new file mode 100644 (file)
index 0000000..c4d11ef
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Jumps to a given relative or Moodle absolute URL.
+ * Mostly used for accessibility.
+ *
+ * @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');
+
+$jump = required_param('jump', PARAM_RAW);
+
+$PAGE->set_url('/course/jumpto.php');
+
+if (!confirm_sesskey()) {
+    print_error('confirmsesskeybad');
+}
+
+if (strpos($jump, '/') === 0) {
+    redirect(new moodle_url($jump));
+} else {
+    print_error('error');
+}
diff --git a/lib.php b/lib.php
new file mode 100644 (file)
index 0000000..f89572c
--- /dev/null
+++ b/lib.php
@@ -0,0 +1,4755 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Library of useful functions
+ *
+ * @copyright 1999 Martin Dougiamas  http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package core
+ * @subpackage course
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->libdir.'/completionlib.php');
+require_once($CFG->libdir.'/filelib.php');
+require_once($CFG->dirroot.'/course/dnduploadlib.php');
+require_once($CFG->dirroot.'/course/format/lib.php');
+
+define('COURSE_MAX_LOGS_PER_PAGE', 1000);       // records
+define('COURSE_MAX_RECENT_PERIOD', 172800);     // Two days, in seconds
+
+/**
+ * Number of courses to display when summaries are included.
+ * @var int
+ * @deprecated since 2.4, use $CFG->courseswithsummarieslimit instead.
+ */
+define('COURSE_MAX_SUMMARIES_PER_PAGE', 10);
+
+define('COURSE_MAX_COURSES_PER_DROPDOWN',1000); //  max courses in log dropdown before switching to optional
+define('COURSE_MAX_USERS_PER_DROPDOWN',1000);   //  max users in log dropdown before switching to optional
+define('FRONTPAGENEWS',           '0');
+define('FRONTPAGECOURSELIST',     '1');
+define('FRONTPAGECATEGORYNAMES',  '2');
+define('FRONTPAGETOPICONLY',      '3');
+define('FRONTPAGECATEGORYCOMBO',  '4');
+define('FRONTPAGECOURSELIMIT',    200);         // maximum number of courses displayed on the frontpage
+define('EXCELROWS', 65535);
+define('FIRSTUSEDEXCELROW', 3);
+
+define('MOD_CLASS_ACTIVITY', 0);
+define('MOD_CLASS_RESOURCE', 1);
+
+function make_log_url($module, $url) {
+    switch ($module) {
+        case 'course':
+            if (strpos($url, 'report/') === 0) {
+                // there is only one report type, course reports are deprecated
+                $url = "/$url";
+                break;
+            }
+        case 'file':
+        case 'login':
+        case 'lib':
+        case 'admin':
+        case 'calendar':
+        case 'category':
+        case 'mnet course':
+            if (strpos($url, '../') === 0) {
+                $url = ltrim($url, '.');
+            } else {
+                $url = "/course/$url";
+            }
+            break;
+        case 'user':
+        case 'blog':
+            $url = "/$module/$url";
+            break;
+        case 'upload':
+            $url = $url;
+            break;
+        case 'coursetags':
+            $url = '/'.$url;
+            break;
+        case 'library':
+        case '':
+            $url = '/';
+            break;
+        case 'message':
+            $url = "/message/$url";
+            break;
+        case 'notes':
+            $url = "/notes/$url";
+            break;
+        case 'tag':
+            $url = "/tag/$url";
+            break;
+        case 'role':
+            $url = '/'.$url;
+            break;
+        default:
+            $url = "/mod/$module/$url";
+            break;
+    }
+
+    //now let's sanitise urls - there might be some ugly nasties:-(
+    $parts = explode('?', $url);
+    $script = array_shift($parts);
+    if (strpos($script, 'http') === 0) {
+        $script = clean_param($script, PARAM_URL);
+    } else {
+        $script = clean_param($script, PARAM_PATH);
+    }
+
+    $query = '';
+    if ($parts) {
+        $query = implode('', $parts);
+        $query = str_replace('&amp;', '&', $query); // both & and &amp; are stored in db :-|
+        $parts = explode('&', $query);
+        $eq = urlencode('=');
+        foreach ($parts as $key=>$part) {
+            $part = urlencode(urldecode($part));
+            $part = str_replace($eq, '=', $part);
+            $parts[$key] = $part;
+        }
+        $query = '?'.implode('&amp;', $parts);
+    }
+
+    return $script.$query;
+}
+
+
+function build_mnet_logs_array($hostid, $course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
+                   $modname="", $modid=0, $modaction="", $groupid=0) {
+    global $CFG, $DB;
+
+    // It is assumed that $date is the GMT time of midnight for that day,
+    // and so the next 86400 seconds worth of logs are printed.
+
+    /// Setup for group handling.
+
+    // TODO: I don't understand group/context/etc. enough to be able to do
+    // something interesting with it here
+    // What is the context of a remote course?
+
+    /// If the group mode is separate, and this user does not have editing privileges,
+    /// then only the user's group can be viewed.
+    //if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
+    //    $groupid = get_current_group($course->id);
+    //}
+    /// If this course doesn't have groups, no groupid can be specified.
+    //else if (!$course->groupmode) {
+    //    $groupid = 0;
+    //}
+
+    $groupid = 0;
+
+    $joins = array();
+    $where = '';
+
+    $qry = "SELECT l.*, u.firstname, u.lastname, u.picture
+              FROM {mnet_log} l
+               LEFT JOIN {user} u ON l.userid = u.id
+              WHERE ";
+    $params = array();
+
+    $where .= "l.hostid = :hostid";
+    $params['hostid'] = $hostid;
+
+    // TODO: Is 1 really a magic number referring to the sitename?
+    if ($course != SITEID || $modid != 0) {
+        $where .= " AND l.course=:courseid";
+        $params['courseid'] = $course;
+    }
+
+    if ($modname) {
+        $where .= " AND l.module = :modname";
+        $params['modname'] = $modname;
+    }
+
+    if ('site_errors' === $modid) {
+        $where .= " AND ( l.action='error' OR l.action='infected' )";
+    } else if ($modid) {
+        //TODO: This assumes that modids are the same across sites... probably
+        //not true
+        $where .= " AND l.cmid = :modid";
+        $params['modid'] = $modid;
+    }
+
+    if ($modaction) {
+        $firstletter = substr($modaction, 0, 1);
+        if ($firstletter == '-') {
+            $where .= " AND ".$DB->sql_like('l.action', ':modaction', false, true, true);
+            $params['modaction'] = '%'.substr($modaction, 1).'%';
+        } else {
+            $where .= " AND ".$DB->sql_like('l.action', ':modaction', false);
+            $params['modaction'] = '%'.$modaction.'%';
+        }
+    }
+
+    if ($user) {
+        $where .= " AND l.userid = :user";
+        $params['user'] = $user;
+    }
+
+    if ($date) {
+        $enddate = $date + 86400;
+        $where .= " AND l.time > :date AND l.time < :enddate";
+        $params['date'] = $date;
+        $params['enddate'] = $enddate;
+    }
+
+    $result = array();
+    $result['totalcount'] = $DB->count_records_sql("SELECT COUNT('x') FROM {mnet_log} l WHERE $where", $params);
+    if(!empty($result['totalcount'])) {
+        $where .= " ORDER BY $order";
+        $result['logs'] = $DB->get_records_sql("$qry $where", $params, $limitfrom, $limitnum);
+    } else {
+        $result['logs'] = array();
+    }
+    return $result;
+}
+
+function build_logs_array($course, $user=0, $date=0, $order="l.time ASC", $limitfrom='', $limitnum='',
+                   $modname="", $modid=0, $modaction="", $groupid=0) {
+    global $DB, $SESSION, $USER;
+    // It is assumed that $date is the GMT time of midnight for that day,
+    // and so the next 86400 seconds worth of logs are printed.
+
+    /// Setup for group handling.
+
+    /// If the group mode is separate, and this user does not have editing privileges,
+    /// then only the user's group can be viewed.
+    if ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/course:managegroups', context_course::instance($course->id))) {
+        if (isset($SESSION->currentgroup[$course->id])) {
+            $groupid =  $SESSION->currentgroup[$course->id];
+        } else {
+            $groupid = groups_get_all_groups($course->id, $USER->id);
+            if (is_array($groupid)) {
+                $groupid = array_shift(array_keys($groupid));
+                $SESSION->currentgroup[$course->id] = $groupid;
+            } else {
+                $groupid = 0;
+            }
+        }
+    }
+    /// If this course doesn't have groups, no groupid can be specified.
+    else if (!$course->groupmode) {
+        $groupid = 0;
+    }
+
+    $joins = array();
+    $params = array();
+
+    if ($course->id != SITEID || $modid != 0) {
+        $joins[] = "l.course = :courseid";
+        $params['courseid'] = $course->id;
+    }
+
+    if ($modname) {
+        $joins[] = "l.module = :modname";
+        $params['modname'] = $modname;
+    }
+
+    if ('site_errors' === $modid) {
+        $joins[] = "( l.action='error' OR l.action='infected' )";
+    } else if ($modid) {
+        $joins[] = "l.cmid = :modid";
+        $params['modid'] = $modid;
+    }
+
+    if ($modaction) {
+        $firstletter = substr($modaction, 0, 1);
+        if ($firstletter == '-') {
+            $joins[] = $DB->sql_like('l.action', ':modaction', false, true, true);
+            $params['modaction'] = '%'.substr($modaction, 1).'%';
+        } else {
+            $joins[] = $DB->sql_like('l.action', ':modaction', false);
+            $params['modaction'] = '%'.$modaction.'%';
+        }
+    }
+
+
+    /// Getting all members of a group.
+    if ($groupid and !$user) {
+        if ($gusers = groups_get_members($groupid)) {
+            $gusers = array_keys($gusers);
+            $joins[] = 'l.userid IN (' . implode(',', $gusers) . ')';
+        } else {
+            $joins[] = 'l.userid = 0'; // No users in groups, so we want something that will always be false.
+        }
+    }
+    else if ($user) {
+        $joins[] = "l.userid = :userid";
+        $params['userid'] = $user;
+    }
+
+    if ($date) {
+        $enddate = $date + 86400;
+        $joins[] = "l.time > :date AND l.time < :enddate";
+        $params['date'] = $date;
+        $params['enddate'] = $enddate;
+    }
+
+    $selector = implode(' AND ', $joins);
+
+    $totalcount = 0;  // Initialise
+    $result = array();
+    $result['logs'] = get_logs($selector, $params, $order, $limitfrom, $limitnum, $totalcount);
+    $result['totalcount'] = $totalcount;
+    return $result;
+}
+
+
+function print_log($course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
+                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
+
+    global $CFG, $DB, $OUTPUT;
+
+    if (!$logs = build_logs_array($course, $user, $date, $order, $page*$perpage, $perpage,
+                       $modname, $modid, $modaction, $groupid)) {
+        echo $OUTPUT->notification("No logs found!");
+        echo $OUTPUT->footer();
+        exit;
+    }
+
+    $courses = array();
+
+    if ($course->id == SITEID) {
+        $courses[0] = '';
+        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
+            foreach ($ccc as $cc) {
+                $courses[$cc->id] = $cc->shortname;
+            }
+        }
+    } else {
+        $courses[$course->id] = $course->shortname;
+    }
+
+    $totalcount = $logs['totalcount'];
+    $count=0;
+    $ldcache = array();
+    $tt = getdate(time());
+    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
+
+    $strftimedatetime = get_string("strftimedatetime");
+
+    echo "<div class=\"info\">\n";
+    print_string("displayingrecords", "", $totalcount);
+    echo "</div>\n";
+
+    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+
+    $table = new html_table();
+    $table->classes = array('logtable','generalbox');
+    $table->align = array('right', 'left', 'left');
+    $table->head = array(
+        get_string('time'),
+        get_string('ip_address'),
+        get_string('fullnameuser'),
+        get_string('action'),
+        get_string('info')
+    );
+    $table->data = array();
+
+    if ($course->id == SITEID) {
+        array_unshift($table->align, 'left');
+        array_unshift($table->head, get_string('course'));
+    }
+
+    // Make sure that the logs array is an array, even it is empty, to avoid warnings from the foreach.
+    if (empty($logs['logs'])) {
+        $logs['logs'] = array();
+    }
+
+    foreach ($logs['logs'] as $log) {
+
+        if (isset($ldcache[$log->module][$log->action])) {
+            $ld = $ldcache[$log->module][$log->action];
+        } else {
+            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
+            $ldcache[$log->module][$log->action] = $ld;
+        }
+        if ($ld && is_numeric($log->info)) {
+            // ugly hack to make sure fullname is shown correctly
+            if ($ld->mtable == 'user' && $ld->field == $DB->sql_concat('firstname', "' '" , 'lastname')) {
+                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
+            } else {
+                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
+            }
+        }
+
+        //Filter log->info
+        $log->info = format_string($log->info);
+
+        // If $log->url has been trimmed short by the db size restriction
+        // code in add_to_log, keep a note so we don't add a link to a broken url
+        $brokenurl=(textlib::strlen($log->url)==100 && textlib::substr($log->url,97)=='...');
+
+        $row = array();
+        if ($course->id == SITEID) {
+            if (empty($log->course)) {
+                $row[] = get_string('site');
+            } else {
+                $row[] = "<a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">". format_string($courses[$log->course])."</a>";
+            }
+        }
+
+        $row[] = userdate($log->time, '%a').' '.userdate($log->time, $strftimedatetime);
+
+        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
+        $row[] = $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 440, 'width' => 700)));
+
+        $row[] = html_writer::link(new moodle_url("/user/view.php?id={$log->userid}&course={$log->course}"), fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id))));
+
+        $displayaction="$log->module $log->action";
+        if ($brokenurl) {
+            $row[] = $displayaction;
+        } else {
+            $link = make_log_url($log->module,$log->url);
+            $row[] = $OUTPUT->action_link($link, $displayaction, new popup_action('click', $link, 'fromloglive'), array('height' => 440, 'width' => 700));
+        }
+        $row[] = $log->info;
+        $table->data[] = $row;
+    }
+
+    echo html_writer::table($table);
+    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+}
+
+
+function print_mnet_log($hostid, $course, $user=0, $date=0, $order="l.time ASC", $page=0, $perpage=100,
+                   $url="", $modname="", $modid=0, $modaction="", $groupid=0) {
+
+    global $CFG, $DB, $OUTPUT;
+
+    if (!$logs = build_mnet_logs_array($hostid, $course, $user, $date, $order, $page*$perpage, $perpage,
+                       $modname, $modid, $modaction, $groupid)) {
+        echo $OUTPUT->notification("No logs found!");
+        echo $OUTPUT->footer();
+        exit;
+    }
+
+    if ($course->id == SITEID) {
+        $courses[0] = '';
+        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname,c.visible')) {
+            foreach ($ccc as $cc) {
+                $courses[$cc->id] = $cc->shortname;
+            }
+        }
+    }
+
+    $totalcount = $logs['totalcount'];
+    $count=0;
+    $ldcache = array();
+    $tt = getdate(time());
+    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
+
+    $strftimedatetime = get_string("strftimedatetime");
+
+    echo "<div class=\"info\">\n";
+    print_string("displayingrecords", "", $totalcount);
+    echo "</div>\n";
+
+    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+
+    echo "<table class=\"logtable\" cellpadding=\"3\" cellspacing=\"0\">\n";
+    echo "<tr>";
+    if ($course->id == SITEID) {
+        echo "<th class=\"c0 header\">".get_string('course')."</th>\n";
+    }
+    echo "<th class=\"c1 header\">".get_string('time')."</th>\n";
+    echo "<th class=\"c2 header\">".get_string('ip_address')."</th>\n";
+    echo "<th class=\"c3 header\">".get_string('fullnameuser')."</th>\n";
+    echo "<th class=\"c4 header\">".get_string('action')."</th>\n";
+    echo "<th class=\"c5 header\">".get_string('info')."</th>\n";
+    echo "</tr>\n";
+
+    if (empty($logs['logs'])) {
+        echo "</table>\n";
+        return;
+    }
+
+    $row = 1;
+    foreach ($logs['logs'] as $log) {
+
+        $log->info = $log->coursename;
+        $row = ($row + 1) % 2;
+
+        if (isset($ldcache[$log->module][$log->action])) {
+            $ld = $ldcache[$log->module][$log->action];
+        } else {
+            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
+            $ldcache[$log->module][$log->action] = $ld;
+        }
+        if (0 && $ld && !empty($log->info)) {
+            // ugly hack to make sure fullname is shown correctly
+            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
+                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
+            } else {
+                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
+            }
+        }
+
+        //Filter log->info
+        $log->info = format_string($log->info);
+
+        echo '<tr class="r'.$row.'">';
+        if ($course->id == SITEID) {
+            $courseshortname = format_string($courses[$log->course], true, array('context' => context_course::instance(SITEID)));
+            echo "<td class=\"r$row c0\" >\n";
+            echo "    <a href=\"{$CFG->wwwroot}/course/view.php?id={$log->course}\">".$courseshortname."</a>\n";
+            echo "</td>\n";
+        }
+        echo "<td class=\"r$row c1\" align=\"right\">".userdate($log->time, '%a').
+             ' '.userdate($log->time, $strftimedatetime)."</td>\n";
+        echo "<td class=\"r$row c2\" >\n";
+        $link = new moodle_url("/iplookup/index.php?ip=$log->ip&user=$log->userid");
+        echo $OUTPUT->action_link($link, $log->ip, new popup_action('click', $link, 'iplookup', array('height' => 400, 'width' => 700)));
+        echo "</td>\n";
+        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', context_course::instance($course->id)));
+        echo "<td class=\"r$row c3\" >\n";
+        echo "    <a href=\"$CFG->wwwroot/user/view.php?id={$log->userid}\">$fullname</a>\n";
+        echo "</td>\n";
+        echo "<td class=\"r$row c4\">\n";
+        echo $log->action .': '.$log->module;
+        echo "</td>\n";;
+        echo "<td class=\"r$row c5\">{$log->info}</td>\n";
+        echo "</tr>\n";
+    }
+    echo "</table>\n";
+
+    echo $OUTPUT->paging_bar($totalcount, $page, $perpage, "$url&perpage=$perpage");
+}
+
+
+function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
+                        $modid, $modaction, $groupid) {
+    global $DB, $CFG;
+
+    require_once($CFG->libdir . '/csvlib.class.php');
+
+    $csvexporter = new csv_export_writer('tab');
+
+    $header = array();
+    $header[] = get_string('course');
+    $header[] = get_string('time');
+    $header[] = get_string('ip_address');
+    $header[] = get_string('fullnameuser');
+    $header[] = get_string('action');
+    $header[] = get_string('info');
+
+    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
+                       $modname, $modid, $modaction, $groupid)) {
+        return false;
+    }
+
+    $courses = array();
+
+    if ($course->id == SITEID) {
+        $courses[0] = '';
+        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
+            foreach ($ccc as $cc) {
+                $courses[$cc->id] = $cc->shortname;
+            }
+        }
+    } else {
+        $courses[$course->id] = $course->shortname;
+    }
+
+    $count=0;
+    $ldcache = array();
+    $tt = getdate(time());
+    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
+
+    $strftimedatetime = get_string("strftimedatetime");
+
+    $csvexporter->set_filename('logs', '.txt');
+    $title = array(get_string('savedat').userdate(time(), $strftimedatetime));
+    $csvexporter->add_data($title);
+    $csvexporter->add_data($header);
+
+    if (empty($logs['logs'])) {
+        return true;
+    }
+
+    foreach ($logs['logs'] as $log) {
+        if (isset($ldcache[$log->module][$log->action])) {
+            $ld = $ldcache[$log->module][$log->action];
+        } else {
+            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
+            $ldcache[$log->module][$log->action] = $ld;
+        }
+        if ($ld && is_numeric($log->info)) {
+            // ugly hack to make sure fullname is shown correctly
+            if (($ld->mtable == 'user') and ($ld->field ==  $DB->sql_concat('firstname', "' '" , 'lastname'))) {
+                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
+            } else {
+                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
+            }
+        }
+
+        //Filter log->info
+        $log->info = format_string($log->info);
+        $log->info = strip_tags(urldecode($log->info));    // Some XSS protection
+
+        $coursecontext = context_course::instance($course->id);
+        $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
+        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
+        $csvexporter->add_data($row);
+    }
+    $csvexporter->download_file();
+    return true;
+}
+
+
+function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
+                        $modid, $modaction, $groupid) {
+
+    global $CFG, $DB;
+
+    require_once("$CFG->libdir/excellib.class.php");
+
+    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
+                       $modname, $modid, $modaction, $groupid)) {
+        return false;
+    }
+
+    $courses = array();
+
+    if ($course->id == SITEID) {
+        $courses[0] = '';
+        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
+            foreach ($ccc as $cc) {
+                $courses[$cc->id] = $cc->shortname;
+            }
+        }
+    } else {
+        $courses[$course->id] = $course->shortname;
+    }
+
+    $count=0;
+    $ldcache = array();
+    $tt = getdate(time());
+    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
+
+    $strftimedatetime = get_string("strftimedatetime");
+
+    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
+    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
+    $filename .= '.xls';
+
+    $workbook = new MoodleExcelWorkbook('-');
+    $workbook->send($filename);
+
+    $worksheet = array();
+    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
+                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
+
+    // Creating worksheets
+    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
+        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
+        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
+        $worksheet[$wsnumber]->set_column(1, 1, 30);
+        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
+                                    userdate(time(), $strftimedatetime));
+        $col = 0;
+        foreach ($headers as $item) {
+            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
+            $col++;
+        }
+    }
+
+    if (empty($logs['logs'])) {
+        $workbook->close();
+        return true;
+    }
+
+    $formatDate =& $workbook->add_format();
+    $formatDate->set_num_format(get_string('log_excel_date_format'));
+
+    $row = FIRSTUSEDEXCELROW;
+    $wsnumber = 1;
+    $myxls =& $worksheet[$wsnumber];
+    foreach ($logs['logs'] as $log) {
+        if (isset($ldcache[$log->module][$log->action])) {
+            $ld = $ldcache[$log->module][$log->action];
+        } else {
+            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
+            $ldcache[$log->module][$log->action] = $ld;
+        }
+        if ($ld && is_numeric($log->info)) {
+            // ugly hack to make sure fullname is shown correctly
+            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
+                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
+            } else {
+                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
+            }
+        }
+
+        // Filter log->info
+        $log->info = format_string($log->info);
+        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
+
+        if ($nroPages>1) {
+            if ($row > EXCELROWS) {
+                $wsnumber++;
+                $myxls =& $worksheet[$wsnumber];
+                $row = FIRSTUSEDEXCELROW;
+            }
+        }
+
+        $coursecontext = context_course::instance($course->id);
+
+        $myxls->write($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)), '');
+        $myxls->write_date($row, 1, $log->time, $formatDate); // write_date() does conversion/timezone support. MDL-14934
+        $myxls->write($row, 2, $log->ip, '');
+        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
+        $myxls->write($row, 3, $fullname, '');
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
+        $myxls->write($row, 5, $log->info, '');
+
+        $row++;
+    }
+
+    $workbook->close();
+    return true;
+}
+
+function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
+                        $modid, $modaction, $groupid) {
+
+    global $CFG, $DB;
+
+    require_once("$CFG->libdir/odslib.class.php");
+
+    if (!$logs = build_logs_array($course, $user, $date, $order, '', '',
+                       $modname, $modid, $modaction, $groupid)) {
+        return false;
+    }
+
+    $courses = array();
+
+    if ($course->id == SITEID) {
+        $courses[0] = '';
+        if ($ccc = get_courses('all', 'c.id ASC', 'c.id,c.shortname')) {
+            foreach ($ccc as $cc) {
+                $courses[$cc->id] = $cc->shortname;
+            }
+        }
+    } else {
+        $courses[$course->id] = $course->shortname;
+    }
+
+    $count=0;
+    $ldcache = array();
+    $tt = getdate(time());
+    $today = mktime (0, 0, 0, $tt["mon"], $tt["mday"], $tt["year"]);
+
+    $strftimedatetime = get_string("strftimedatetime");
+
+    $nroPages = ceil(count($logs)/(EXCELROWS-FIRSTUSEDEXCELROW+1));
+    $filename = 'logs_'.userdate(time(),get_string('backupnameformat', 'langconfig'),99,false);
+    $filename .= '.ods';
+
+    $workbook = new MoodleODSWorkbook('-');
+    $workbook->send($filename);
+
+    $worksheet = array();
+    $headers = array(get_string('course'), get_string('time'), get_string('ip_address'),
+                        get_string('fullnameuser'),    get_string('action'), get_string('info'));
+
+    // Creating worksheets
+    for ($wsnumber = 1; $wsnumber <= $nroPages; $wsnumber++) {
+        $sheettitle = get_string('logs').' '.$wsnumber.'-'.$nroPages;
+        $worksheet[$wsnumber] = $workbook->add_worksheet($sheettitle);
+        $worksheet[$wsnumber]->set_column(1, 1, 30);
+        $worksheet[$wsnumber]->write_string(0, 0, get_string('savedat').
+                                    userdate(time(), $strftimedatetime));
+        $col = 0;
+        foreach ($headers as $item) {
+            $worksheet[$wsnumber]->write(FIRSTUSEDEXCELROW-1,$col,$item,'');
+            $col++;
+        }
+    }
+
+    if (empty($logs['logs'])) {
+        $workbook->close();
+        return true;
+    }
+
+    $formatDate =& $workbook->add_format();
+    $formatDate->set_num_format(get_string('log_excel_date_format'));
+
+    $row = FIRSTUSEDEXCELROW;
+    $wsnumber = 1;
+    $myxls =& $worksheet[$wsnumber];
+    foreach ($logs['logs'] as $log) {
+        if (isset($ldcache[$log->module][$log->action])) {
+            $ld = $ldcache[$log->module][$log->action];
+        } else {
+            $ld = $DB->get_record('log_display', array('module'=>$log->module, 'action'=>$log->action));
+            $ldcache[$log->module][$log->action] = $ld;
+        }
+        if ($ld && is_numeric($log->info)) {
+            // ugly hack to make sure fullname is shown correctly
+            if (($ld->mtable == 'user') and ($ld->field == $DB->sql_concat('firstname', "' '" , 'lastname'))) {
+                $log->info = fullname($DB->get_record($ld->mtable, array('id'=>$log->info)), true);
+            } else {
+                $log->info = $DB->get_field($ld->mtable, $ld->field, array('id'=>$log->info));
+            }
+        }
+
+        // Filter log->info
+        $log->info = format_string($log->info);
+        $log->info = strip_tags(urldecode($log->info));  // Some XSS protection
+
+        if ($nroPages>1) {
+            if ($row > EXCELROWS) {
+                $wsnumber++;
+                $myxls =& $worksheet[$wsnumber];
+                $row = FIRSTUSEDEXCELROW;
+            }
+        }
+
+        $coursecontext = context_course::instance($course->id);
+
+        $myxls->write_string($row, 0, format_string($courses[$log->course], true, array('context' => $coursecontext)));
+        $myxls->write_date($row, 1, $log->time);
+        $myxls->write_string($row, 2, $log->ip);
+        $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
+        $myxls->write_string($row, 3, $fullname);
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
+        $myxls->write_string($row, 5, $log->info);
+
+        $row++;
+    }
+
+    $workbook->close();
+    return true;
+}
+
+
+function print_overview($courses, array $remote_courses=array()) {
+    global $CFG, $USER, $DB, $OUTPUT;
+
+    $htmlarray = array();
+    if ($modules = $DB->get_records('modules')) {
+        foreach ($modules as $mod) {
+            if (file_exists(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php')) {
+                include_once(dirname(dirname(__FILE__)).'/mod/'.$mod->name.'/lib.php');
+                $fname = $mod->name.'_print_overview';
+                if (function_exists($fname)) {
+                    $fname($courses,$htmlarray);
+                }
+            }
+        }
+    }
+    foreach ($courses as $course) {
+        $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
+        echo $OUTPUT->box_start('coursebox');
+        $attributes = array('title' => s($fullname));
+        if (empty($course->visible)) {
+            $attributes['class'] = 'dimmed';
+        }
+        echo $OUTPUT->heading(html_writer::link(
+            new moodle_url('/course/view.php', array('id' => $course->id)), $fullname, $attributes), 3);
+        if (array_key_exists($course->id,$htmlarray)) {
+            foreach ($htmlarray[$course->id] as $modname => $html) {
+                echo $html;
+            }
+        }
+        echo $OUTPUT->box_end();
+    }
+
+    if (!empty($remote_courses)) {
+        echo $OUTPUT->heading(get_string('remotecourses', 'mnet'));
+    }
+    foreach ($remote_courses as $course) {
+        echo $OUTPUT->box_start('coursebox');
+        $attributes = array('title' => s($course->fullname));
+        echo $OUTPUT->heading(html_writer::link(
+            new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
+            format_string($course->shortname),
+            $attributes) . ' (' . format_string($course->hostname) . ')', 3);
+        echo $OUTPUT->box_end();
+    }
+}
+
+
+/**
+ * This function trawls through the logs looking for
+ * anything new since the user's last login
+ */
+function print_recent_activity($course) {
+    // $course is an object
+    global $CFG, $USER, $SESSION, $DB, $OUTPUT;
+
+    $context = context_course::instance($course->id);
+
+    $viewfullnames = has_capability('moodle/site:viewfullnames', $context);
+
+    $timestart = round(time() - COURSE_MAX_RECENT_PERIOD, -2); // better db caching for guests - 100 seconds
+
+    if (!isguestuser()) {
+        if (!empty($USER->lastcourseaccess[$course->id])) {
+            if ($USER->lastcourseaccess[$course->id] > $timestart) {
+                $timestart = $USER->lastcourseaccess[$course->id];
+            }
+        }
+    }
+
+    echo '<div class="activitydate">';
+    echo get_string('activitysince', '', userdate($timestart));
+    echo '</div>';
+    echo '<div class="activityhead">';
+
+    echo '<a href="'.$CFG->wwwroot.'/course/recent.php?id='.$course->id.'">'.get_string('recentactivityreport').'</a>';
+
+    echo "</div>\n";
+
+    $content = false;
+
+/// Firstly, have there been any new enrolments?
+
+    $users = get_recent_enrolments($course->id, $timestart);
+
+    //Accessibility: new users now appear in an <OL> list.
+    if ($users) {
+        echo '<div class="newusers">';
+        echo $OUTPUT->heading(get_string("newusers").':', 3);
+        $content = true;
+        echo "<ol class=\"list\">\n";
+        foreach ($users as $user) {
+            $fullname = fullname($user, $viewfullnames);
+            echo '<li class="name"><a href="'."$CFG->wwwroot/user/view.php?id=$user->id&amp;course=$course->id\">$fullname</a></li>\n";
+        }
+        echo "</ol>\n</div>\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:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
+
+                } 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:<br /><a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id={$cm->id}\">".format_string($cm->name, true)."</a>");
+                }
+            }
+        }
+    }
+
+    if (!empty($changelist)) {
+        echo $OUTPUT->heading(get_string("courseupdates").':', 3);
+        $content = true;
+        foreach ($changelist as $changeinfo => $change) {
+            echo '<p class="activity">'.$change['text'].'</p>';
+        }
+    }
+
+/// 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 '<p class="message">'.get_string('nothingnew').'</p>';
+    }
+}
+
+/**
+ * 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 <ul>, but don't output empty list.
+    if (!empty($modinfo->sections[$section->section])) {
+
+        // Fix bug #5027, don't want style=\"width:$width\".
+        echo "<ul class=\"section img-text\">\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 '<a title="'.$strmovefull.'"'.
+                     ' href="'.$CFG->wwwroot.'/course/mod.php?moveto='.$mod->id.'&amp;sesskey='.sesskey().'">'.
+                     '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
+                     ' alt="'.$strmovehere.'" /></a><br />
+                     ';
+            }
+
+            $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 = '<span class="accesshide">' .
+                        get_string('notavailableyet', 'condition') .
+                        ': </span>';
+
+                if ($url = $mod->get_url()) {
+                    // Display greyed-out text of link
+                    echo '<div ' . $textcss . $mod->extra .
+                            ' >' . '<img src="' . $mod->get_icon_url() .
+                            '" class="activityicon" alt="" /> <span>'. $instancename . $altname .
+                            '</span></div>';
+
+                    // Do not display content after link when it is greyed out like this.
+                } else {
+                    // No link, so display only content (also greyed)
+                    $contentpart = '<div ' . $textcss . $mod->extra . '>' .
+                            $accesstext . $content . '</div>';
+                }
+            }
+
+            // 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 "<span class='autocompletion'>";
+                        echo "<img src='$imgsrc' alt='$imgalt' title='$imgalt' /></span>";
+                    }
+                }
+            }
+
+            // 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 '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
+            } 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 '<div class="availabilityinfo '.$hidinfoclass.'">'.get_string($mod->showavailability
+                            ? 'userrestriction_visible'
+                            : 'userrestriction_hidden','condition',
+                            $fullinfo).'</div>';
+                    }
+                }
+            }
+
+            echo html_writer::end_tag('div');
+            echo html_writer::end_tag('li')."\n";
+        }
+
+    } elseif ($ismoving) {
+        echo "<ul class=\"section\">\n";
+    }
+
+    if ($ismoving) {
+        echo '<li><a title="'.$strmovefull.'"'.
+             ' href="'.$CFG->wwwroot.'/course/mod.php?movetosection='.$section->id.'&amp;sesskey='.sesskey().'">'.
+             '<img class="movetarget" src="'.$OUTPUT->pix_url('movehere') . '" '.
+             ' alt="'.$strmovehere.'" /></a></li>
+             ';
+    }
+    if (!empty($modinfo->sections[$section->section]) || $ismoving) {
+        echo "</ul><!--class='section'-->\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 = '&section=' . $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('&amp;', '&', $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] = '&nbsp;'.$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 = '<img src="'.$OUTPUT->pix_url('i/course') . '" alt="" />';
+    } else {
+        $catimage = "&nbsp;";
+    }
+
+    $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 '<div class="categorylist clearfix">';
+        $cat = '';
+        $cat .= html_writer::tag('div', $catimage, array('class'=>'image'));
+        $catlink = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
+        $cat .= html_writer::tag('div', $catlink, array('class'=>'name'));
+
+        $html = '';
+        if ($depth > 0) {
+            for ($i=0; $i< $depth; $i++) {
+                $html = html_writer::tag('div', $html . $cat, array('class'=>'indentation'));
+                $cat = '';
+            }
+        } else {
+            $html = $cat;
+        }
+        echo html_writer::tag('div', $html, array('class'=>'category'));
+        echo html_writer::tag('div', '', array('class'=>'clearfloat'));
+
+        // does the depth exceed maxcategorydepth
+        // maxcategorydepth == 0 or unset meant no limit
+        $limit = !(isset($CFG->maxcategorydepth) && ($depth >= $CFG->maxcategorydepth-1));
+        if ($courses && ($limit || $CFG->maxcategorydepth == 0)) {
+            foreach ($courses as $course) {
+                $linkcss = null;
+                if (!$course->visible) {
+                    $linkcss = array('class'=>'dimmed');
+                }
+
+                $coursename = get_course_display_name_for_list($course);
+                $courselink = html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($coursename), $linkcss);
+
+                // print enrol info
+                $courseicon = '';
+                if ($icons = enrol_get_course_info_icons($course)) {
+                    foreach ($icons as $pix_icon) {
+                        $courseicon = $OUTPUT->render($pix_icon);
+                    }
+                }
+
+                $coursecontent = html_writer::tag('div', $courseicon.$courselink, array('class'=>'name'));
+
+                if ($course->summary) {
+                    $link = new moodle_url('/course/info.php?id='.$course->id);
+                    $actionlink = $OUTPUT->action_link($link, '<img alt="'.$strsummary.'" src="'.$OUTPUT->pix_url('i/info') . '" />',
+                        new popup_action('click', $link, 'courseinfo', array('height' => 400, 'width' => 500)),
+                        array('title'=>$strsummary));
+
+                    $coursecontent .= html_writer::tag('div', $actionlink, array('class'=>'info'));
+                }
+
+                $html = '';
+                for ($i=0; $i <= $depth; $i++) {
+                    $html = html_writer::tag('div', $html . $coursecontent , array('class'=>'indentation'));
+                    $coursecontent = '';
+                }
+                echo html_writer::tag('div', $html, array('class'=>'course clearfloat'));
+            }
+        }
+        echo '</div>';
+    } else {
+        echo '<div class="categorylist">';
+        $html = '';
+        $cat = html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $fullname, $catlinkcss);
+        if (count($courses) > 0) {
+            $cat .= html_writer::tag('span', ' ('.count($courses).')', array('title'=>get_string('numberofcourses'), 'class'=>'numberofcourse'));
+        }
+
+        if ($depth > 0) {
+            for ($i=0; $i< $depth; $i++) {
+                $html = html_writer::tag('div', $html .$cat, array('class'=>'indentation'));
+                $cat = '';
+            }
+        } else {
+            $html = $cat;
+        }
+
+        echo html_writer::tag('div', $html, array('class'=>'category'));
+        echo html_writer::tag('div', '', array('class'=>'clearfloat'));
+        echo '</div>';
+    }
+}
+
+/**
+ * Print the buttons relating to course requests.
+ *
+ * @param object $systemcontext the system context.
+ */
+function print_course_request_buttons($systemcontext) {
+    global $CFG, $DB, $OUTPUT;
+    if (empty($CFG->enablecourserequests)) {
+        return;
+    }
+    if (!has_capability('moodle/course:create', $systemcontext) && has_capability('moodle/course:request', $systemcontext)) {
+    /// Print a button to request a new course
+        echo $OUTPUT->single_button('request.php', get_string('requestcourse'), 'get');
+    }
+    /// Print a button to manage pending requests
+    if (has_capability('moodle/site:approvecourse', $systemcontext)) {
+        $disabled = !$DB->record_exists('course_request', array());
+        echo $OUTPUT->single_button('pending.php', get_string('coursespending'), 'get', array('disabled'=>$disabled));
+    }
+}
+
+/**
+ * Does the user have permission to edit things in this category?
+ *
+ * @param integer $categoryid The id of the category we are showing, or 0 for system context.
+ * @return boolean has_any_capability(array(...), ...); in the appropriate context.
+ */
+function can_edit_in_category($categoryid = 0) {
+    $context = get_category_or_system_context($categoryid);
+    return has_any_capability(array('moodle/category:manage', 'moodle/course:create'), $context);
+}
+
+/**
+ * Prints the turn editing on/off button on course/index.php or course/category.php.
+ *
+ * @param integer $categoryid The id of the category we are showing, or 0 for system context.
+ * @return string HTML of the editing button, or empty string, if this user is not allowed
+ *      to see it.
+ */
+function update_category_button($categoryid = 0) {
+    global $CFG, $PAGE, $OUTPUT;
+
+    // Check permissions.
+    if (!can_edit_in_category($categoryid)) {
+        return '';
+    }
+
+    // Work out the appropriate action.
+    if ($PAGE->user_is_editing()) {
+        $label = get_string('turneditingoff');
+        $edit = 'off';
+    } else {
+        $label = get_string('turneditingon');
+        $edit = 'on';
+    }
+
+    // Generate the button HTML.
+    $options = array('categoryedit' => $edit, 'sesskey' => sesskey());
+    if ($categoryid) {
+        $options['id'] = $categoryid;
+        $page = 'category.php';
+    } else {
+        $page = 'index.php';
+    }
+    return $OUTPUT->single_button(new moodle_url('/course/' . $page, $options), $label, 'get');
+}
+
+/**
+ * Print courses in category. If category is 0 then all courses are printed.
+ * @param int|stdClass $category category object or id.
+ * @return bool true if courses found and printed, else false.
+ */
+function print_courses($category) {
+    global $CFG, $OUTPUT;
+
+    if (!is_object($category) && $category==0) {
+        $categories = get_child_categories(0);  // Parent = 0   ie top-level categories only
+        if (is_array($categories) && count($categories) == 1) {
+            $category   = array_shift($categories);
+            $courses    = get_courses_wmanagers($category->id,
+                                                'c.sortorder ASC',
+                                                array('summary','summaryformat'));
+        } else {
+            $courses    = get_courses_wmanagers('all',
+                                                'c.sortorder ASC',
+                                                array('summary','summaryformat'));
+        }
+        unset($categories);
+    } else {
+        $courses    = get_courses_wmanagers($category->id,
+                                            'c.sortorder ASC',
+                                            array('summary','summaryformat'));
+    }
+
+    if ($courses) {
+        echo html_writer::start_tag('ul', array('class'=>'unlist'));
+        foreach ($courses as $course) {
+            $coursecontext = context_course::instance($course->id);
+            if ($course->visible == 1 || has_capability('moodle/course:viewhiddencourses', $coursecontext)) {
+                echo html_writer::start_tag('li');
+                print_course($course);
+                echo html_writer::end_tag('li');
+            }
+        }
+        echo html_writer::end_tag('ul');
+    } else {
+        echo $OUTPUT->heading(get_string("nocoursesyet"));
+        $context = context_system::instance();
+        if (has_capability('moodle/course:create', $context)) {
+            $options = array();
+            if (!empty($category->id)) {
+                $options['category'] = $category->id;
+            } else {
+                $options['category'] = $CFG->defaultrequestcategory;
+            }
+            echo html_writer::start_tag('div', array('class'=>'addcoursebutton'));
+            echo $OUTPUT->single_button(new moodle_url('/course/edit.php', $options), get_string("addnewcourse"));
+            echo html_writer::end_tag('div');
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * Print a description of a course, suitable for browsing in a list.
+ *
+ * @param object $course the course object.
+ * @param string $highlightterms (optional) some search terms that should be highlighted in the display.
+ */
+function print_course($course, $highlightterms = '') {
+    global $CFG, $USER, $DB, $OUTPUT;
+
+    $context = context_course::instance($course->id);
+
+    // Rewrite file URLs so that they are correct
+    $course->summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', NULL);
+
+    echo html_writer::start_tag('div', array('class'=>'coursebox clearfix'));
+    echo html_writer::start_tag('div', array('class'=>'info'));
+    echo html_writer::start_tag('h3', array('class'=>'name'));
+
+    $linkhref = new moodle_url('/course/view.php', array('id'=>$course->id));
+
+    $coursename = get_course_display_name_for_list($course);
+    $linktext = highlight($highlightterms, format_string($coursename));
+    $linkparams = array('title'=>get_string('entercourse'));
+    if (empty($course->visible)) {
+        $linkparams['class'] = 'dimmed';
+    }
+    echo html_writer::link($linkhref, $linktext, $linkparams);
+    echo html_writer::end_tag('h3');
+
+    /// first find all roles that are supposed to be displayed
+    if (!empty($CFG->coursecontact)) {
+        $managerroles = explode(',', $CFG->coursecontact);
+        $rusers = array();
+
+        if (!isset($course->managers)) {
+            list($sort, $sortparams) = users_order_by_sql('u');
+            $rusers = get_role_users($managerroles, $context, true,
+                'ra.id AS raid, u.id, u.username, u.firstname, u.lastname, rn.name AS rolecoursealias,
+                 r.name AS rolename, r.sortorder, r.id AS roleid, r.shortname AS roleshortname',
+                'r.sortorder ASC, ' . $sort, null, '', '', '', '', $sortparams);
+        } else {
+            //  use the managers array if we have it for perf reasosn
+            //  populate the datastructure like output of get_role_users();
+            foreach ($course->managers as $manager) {
+                $user = clone($manager->user);
+                $user->roleid = $manager->roleid;
+                $user->rolename = $manager->rolename;
+                $user->roleshortname = $manager->roleshortname;
+                $user->rolecoursealias = $manager->rolecoursealias;
+                $rusers[$user->id] = $user;
+            }
+        }
+
+        $namesarray = array();
+        $canviewfullnames = has_capability('moodle/site:viewfullnames', $context);
+        foreach ($rusers as $ra) {
+            if (isset($namesarray[$ra->id])) {
+                //  only display a user once with the higest sortorder role
+                continue;
+            }
+
+            $role = new stdClass();
+            $role->id = $ra->roleid;
+            $role->name = $ra->rolename;
+            $role->shortname = $ra->roleshortname;
+            $role->coursealias = $ra->rolecoursealias;
+            $rolename = role_get_name($role, $context, ROLENAME_ALIAS);
+
+            $fullname = fullname($ra, $canviewfullnames);
+            $namesarray[$ra->id] = $rolename.': '.
+                html_writer::link(new moodle_url('/user/view.php', array('id'=>$ra->id, 'course'=>SITEID)), $fullname);
+        }
+
+        if (!empty($namesarray)) {
+            echo html_writer::start_tag('ul', array('class'=>'teachers'));
+            foreach ($namesarray as $name) {
+                echo html_writer::tag('li', $name);
+            }
+            echo html_writer::end_tag('ul');
+        }
+    }
+    echo html_writer::end_tag('div'); // End of info div
+
+/* CUSTOMICP
+    echo html_writer::start_tag('div', array('class'=>'summary'));
+    $options = new stdClass();
+    $options->noclean = true;
+    $options->para = false;
+    $options->overflowdiv = true;
+    if (!isset($course->summaryformat)) {
+        $course->summaryformat = FORMAT_MOODLE;
+    }
+    echo highlight($highlightterms, format_text($course->summary, $course->summaryformat, $options,  $course->id));
+    if ($icons = enrol_get_course_info_icons($course)) {
+        echo html_writer::start_tag('div', array('class'=>'enrolmenticons'));
+        foreach ($icons as $icon) {
+            $icon->attributes["alt"] .= ": ". format_string($coursename, true, array('context'=>$context));
+            echo $OUTPUT->render($icon);
+        }
+        echo html_writer::end_tag('div'); // End of enrolmenticons div
+    }
+    echo html_writer::end_tag('div'); // End of summary div
+///*ENDCUSTOMICP */
+    echo html_writer::end_tag('div'); // End of coursebox div
+}
+
+/**
+ * Prints custom user information on the home page.
+ * Over time this can include all sorts of information
+ */
+function print_my_moodle() {
+    global $USER, $CFG, $DB, $OUTPUT;
+
+    if (!isloggedin() or isguestuser()) {
+        print_error('nopermissions', '', '', 'See My Moodle');
+    }
+
+    $courses  = enrol_get_my_courses('summary', 'visible DESC,sortorder ASC');
+    $rhosts   = array();
+    $rcourses = array();
+    if (!empty($CFG->mnet_dispatcher_mode) && $CFG->mnet_dispatcher_mode==='strict') {
+        $rcourses = get_my_remotecourses($USER->id);
+        $rhosts   = get_my_remotehosts();
+    }
+
+    if (!empty($courses) || !empty($rcourses) || !empty($rhosts)) {
+
+/* CUSTOMICP désactivation des lignes suivantes
+        if (!empty($courses)) {
+            echo '<ul class="unlist">';
+            foreach ($courses as $course) {
+                if ($course->id == SITEID) {
+                    continue;
+                }
+                echo '<li>';
+                print_course($course);
+                echo "</li>\n";
+            }
+            echo "</ul>\n";
+        }
+
+///*ENDCUSTOMICP */
+
+///* CUSTOMICP insertion des lignes suivantes
+if (!empty($courses)) {
+            echo '<ul class="unlist" style="margin-top: 20px;">';
+            $teacher_once=0;
+            $both=0;
+            foreach ($courses as $course) {
+                if ($course->id == SITEID) {
+                    continue;
+                }                
+
+                $u_context = get_context_instance(CONTEXT_COURSE, $course->id);
+                $u_managerroles = explode(',', $CFG->coursecontact);
+                //CUSTOMICP LIMIT 3
+                $u_rusers = get_role_users($u_managerroles, $u_context, true, '', 'r.sortorder ASC, u.lastname ASC LIMIT 3');
+                $bool_teacher=0;
+                foreach ($u_rusers as $u_teacher) {
+                   if ($u_teacher->id == $USER->id) {
+                        $bool_teacher=1;
+                        $teacher_once++;      
+                    }                  
+                }
+                if ( $teacher_once == 1) {                    
+                    ?> <h2 id="h2_teacher" onclick="bascule('div_teacher','img_teacher'); return false;" >En tant qu'enseignant <img id="img_teacher" alt="toggle" src="theme/standard/pix/bas.png"></h2><div id='div_teacher' ><?php
+                    $both++;
+                    $teacher_once++;
+                }
+                if ( $bool_teacher) {
+                    echo '<li>';
+                    print_course($course);
+                    echo "</li>\n";
+                }                
+            }
+            if ( $teacher_once != 0) {              
+                echo "</div>";
+            }
+            
+            $student_once=0;           
+            foreach ($courses as $course) {
+                if ($course->id == SITEID) {
+                    continue;
+                }                
+
+                $u_context = get_context_instance(CONTEXT_COURSE, $course->id);
+                $u_managerroles = explode(',', $CFG->coursecontact);
+                //CUSTOMICP LIMIT 3
+                $u_rusers = get_role_users($u_managerroles, $u_context, true, '', 'r.sortorder ASC, u.lastname ASC LIMIT 3');
+                $bool_teacher=0;
+                foreach ($u_rusers as $u_teacher) {
+                   if ($u_teacher->id == $USER->id) {
+                        $bool_teacher=1;                        
+                    }
+                }                
+                if ( !$bool_teacher) {
+
+                    if ( $student_once == 0) {                    
+                        ?><h2 id="h2_student" onclick="bascule('div_student', 'img_student'); return false;" >En tant qu'étudiant <img id="img_student" alt="toggle" src="theme/standard/pix/bas.png"></h2><div id='div_student' ><?php
+                        $both++;
+                        $student_once++;
+                    }
+                    echo '<li>';
+                    print_course($course);
+                    echo "</li>\n"; 
+                }
+                
+            }
+            if ( $student_once != 0) { 
+                echo "</div>";
+            }
+            if ( $both==2 ){
+                ?>
+                    <script language="javascript" type="text/javascript">
+                        document.getElementById('div_student').style.display="none";
+                        document.getElementById('div_teacher').style.display="none";
+                    </script>
+                <?php
+            }
+            else {
+                ?>
+                    <script language="javascript" type="text/javascript">
+                        if ( document.getElementById('h2_teacher') != null )
+                            document.getElementById('h2_teacher').style.display="none";
+                        if (  document.getElementById('h2_student') !=null )
+                            document.getElementById('h2_student').style.display="none";
+                    </script>
+                <?php
+            }
+            echo "</ul>\n";
+            ?>            
+                <script language="javascript" type="text/javascript">                                      
+                    function bascule(elem,img) {
+                        etat=document.getElementById(elem).style.display;
+                        if(etat=="none"){
+                            document.getElementById(elem).style.display="block";
+                            document.getElementById(img).src="theme/standard/pix/haut.png";
+                        }
+                        else{
+                            document.getElementById(elem).style.display="none";
+                            document.getElementById(img).src="theme/standard/pix/bas.png";
+                        }
+                    }
+                </script>
+                <style type="text/css">
+                    .unlist h2 {
+                        cursor:pointer;cursor:hand
+                    }
+                </style>
+            <?php
+        }
+///* ENDCUSTOMICP */
+
+        // MNET
+        if (!empty($rcourses)) {
+            // at the IDP, we know of all the remote courses
+            foreach ($rcourses as $course) {
+                print_remote_course($course, "100%");
+            }
+        } elseif (!empty($rhosts)) {
+            // non-IDP, we know of all the remote servers, but not courses
+            foreach ($rhosts as $host) {
+                print_remote_host($host, "100%");
+            }
+        }
+        unset($course);
+        unset($host);
+
+        if ($DB->count_records("course") > (count($courses) + 1) ) {  // Some courses not being displayed
+            echo "<table width=\"100%\"><tr><td align=\"center\">";
+            print_course_search("", false, "short");
+            echo "</td><td align=\"center\">";
+            echo $OUTPUT->single_button("$CFG->wwwroot/course/index.php", get_string("fulllistofcourses"), "get");
+            echo "</td></tr></table>\n";
+        }
+
+    } else {
+        if ($DB->count_records("course_categories") > 1) {
+            echo $OUTPUT->box_start("categorybox");
+            print_whole_category_list();
+            echo $OUTPUT->box_end();
+        } else {
+            print_courses(0);
+        }
+    }
+}
+
+
+function print_course_search($value="", $return=false, $format="plain") {
+    global $CFG;
+    static $count = 0;
+
+    $count++;
+
+    $id = 'coursesearch';
+
+    if ($count > 1) {
+        $id .= $count;
+    }
+
+    $strsearchcourses= get_string("searchcourses");
+
+    if ($format == 'plain') {
+        $output  = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
+        $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
+        $output .= '<label for="coursesearchbox">'.$strsearchcourses.': </label>';
+        $output .= '<input type="text" id="coursesearchbox" size="30" name="search" value="'.s($value).'" />';
+        $output .= '<input type="submit" value="'.get_string('go').'" />';
+        $output .= '</fieldset></form>';
+    } else if ($format == 'short') {
+        $output  = '<form id="'.$id.'" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
+        $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
+        $output .= '<label for="shortsearchbox">'.$strsearchcourses.': </label>';
+        $output .= '<input type="text" id="shortsearchbox" size="12" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="submit" value="'.get_string('go').'" />';
+        $output .= '</fieldset></form>';
+    } else if ($format == 'navbar') {
+        $output  = '<form id="coursesearchnavbar" action="'.$CFG->wwwroot.'/course/search.php" method="get">';
+        $output .= '<fieldset class="coursesearchbox invisiblefieldset">';
+        $output .= '<label for="navsearchbox">'.$strsearchcourses.': </label>';
+        $output .= '<input type="text" id="navsearchbox" size="20" name="search" alt="'.s($strsearchcourses).'" value="'.s($value).'" />';
+        $output .= '<input type="submit" value="'.get_string('go').'" />';
+        $output .= '</fieldset></form>';
+    }
+
+    if ($return) {
+        return $output;
+    }
+    echo $output;
+}
+
+function print_remote_course($course, $width="100%") {
+    global $CFG, $USER;
+
+    $linkcss = '';
+
+    $url = "{$CFG->wwwroot}/auth/mnet/jump.php?hostid={$course->hostid}&amp;wantsurl=/course/view.php?id={$course->remoteid}";
+
+    echo '<div class="coursebox remotecoursebox clearfix">';
+    echo '<div class="info">';
+    echo '<div class="name"><a title="'.get_string('entercourse').'"'.
+         $linkcss.' href="'.$url.'">'
+        .  format_string($course->fullname) .'</a><br />'
+        . format_string($course->hostname) . ' : '
+        . format_string($course->cat_name) . ' : '
+        . format_string($course->shortname). '</div>';
+    echo '</div><div class="summary">';
+    $options = new stdClass();
+    $options->noclean = true;
+    $options->para = false;
+    $options->overflowdiv = true;
+    echo format_text($course->summary, $course->summaryformat, $options);
+    echo '</div>';
+    echo '</div>';
+}
+
+function print_remote_host($host, $width="100%") {
+    global $OUTPUT;
+
+    $linkcss = '';
+
+    echo '<div class="coursebox clearfix">';
+    echo '<div class="info">';
+    echo '<div class="name">';
+    echo '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="'.get_string('course').'" />';
+    echo '<a title="'.s($host['name']).'" href="'.s($host['url']).'">'
+        . s($host['name']).'</a> - ';
+    echo $host['count'] . ' ' . get_string('courses');
+    echo '</div>';
+    echo '</div>';
+    echo '</div>';
+}
+
+
+/// MODULE FUNCTIONS /////////////////////////////////////////////////////////////////
+
+function add_course_module($mod) {
+    global $DB;
+
+    $mod->added = time();
+    unset($mod->id);
+
+    $cmid = $DB->insert_record("course_modules", $mod);
+    rebuild_course_cache($mod->course, true);
+    return $cmid;
+}
+
+/**
+ * Creates missing course section(s) and rebuilds course cache
+ *
+ * @param int|stdClass $courseorid course id or course object
+ * @param int|array $sections list of relative section numbers to create
+ * @return bool if there were any sections created
+ */
+function course_create_sections_if_missing($courseorid, $sections) {
+    global $DB;
+    if (!is_array($sections)) {
+        $sections = array($sections);
+    }
+    $existing = array_keys(get_fast_modinfo($courseorid)->get_section_info_all());
+    if (is_object($courseorid)) {
+        $courseorid = $courseorid->id;
+    }
+    $coursechanged = false;
+    foreach ($sections as $sectionnum) {
+        if (!in_array($sectionnum, $existing)) {
+            $cw = new stdClass();
+            $cw->course   = $courseorid;
+            $cw->section  = $sectionnum;
+            $cw->summary  = '';
+            $cw->summaryformat = FORMAT_HTML;
+            $cw->sequence = '';
+            $id = $DB->insert_record("course_sections", $cw);
+            $coursechanged = true;
+        }
+    }
+    if ($coursechanged) {
+        rebuild_course_cache($courseorid, true);
+    }
+    return $coursechanged;
+}
+
+/**
+ * Adds an existing module to the section
+ *
+ * Updates both tables {course_sections} and {course_modules}
+ *
+ * @param int|stdClass $courseorid course id or course object
+ * @param int $cmid id of the module already existing in course_modules table
+ * @param int $sectionnum relative number of the section (field course_sections.section)
+ *     If section does not exist it will be created
+ * @param int|stdClass $beforemod id or object with field id corresponding to the module
+ *     before which the module needs to be included. Null for inserting in the
+ *     end of the section
+ * @return int The course_sections ID where the module is inserted
+ */
+function course_add_cm_to_section($courseorid, $cmid, $sectionnum, $beforemod = null) {
+    global $DB, $COURSE;
+    if (is_object($beforemod)) {
+        $beforemod = $beforemod->id;
+    }
+    if (is_object($courseorid)) {
+        $courseid = $courseorid->id;
+    } else {
+        $courseid = $courseorid;
+    }
+    course_create_sections_if_missing($courseorid, $sectionnum);
+    // Do not try to use modinfo here, there is no guarantee it is valid!
+    $section = $DB->get_record('course_sections', array('course'=>$courseid, 'section'=>$sectionnum), '*', MUST_EXIST);
+    $modarray = explode(",", trim($section->sequence));
+    if (empty($section->sequence)) {
+        $newsequence = "$cmid";
+    } else if ($beforemod && ($key = array_keys($modarray, $beforemod))) {
+        $insertarray = array($cmid, $beforemod);
+        array_splice($modarray, $key[0], 1, $insertarray);
+        $newsequence = implode(",", $modarray);
+    } else {
+        $newsequence = "$section->sequence,$cmid";
+    }
+    $DB->set_field("course_sections", "sequence", $newsequence, array("id" => $section->id));
+    $DB->set_field('course_modules', 'section', $section->id, array('id' => $cmid));
+    if (is_object($courseorid)) {
+        rebuild_course_cache($courseorid->id, true);
+    } else {
+        rebuild_course_cache($courseorid, true);
+    }
+    return $section->id;     // Return course_sections ID that was used.
+}
+
+function set_coursemodule_groupmode($id, $groupmode) {
+    global $DB;
+    $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,groupmode', MUST_EXIST);
+    if ($cm->groupmode != $groupmode) {
+        $DB->set_field('course_modules', 'groupmode', $groupmode, array('id' => $cm->id));
+        rebuild_course_cache($cm->course, true);
+    }
+    return ($cm->groupmode != $groupmode);
+}
+
+function set_coursemodule_idnumber($id, $idnumber) {
+    global $DB;
+    $cm = $DB->get_record('course_modules', array('id' => $id), 'id,course,idnumber', MUST_EXIST);
+    if ($cm->idnumber != $idnumber) {
+        $DB->set_field('course_modules', 'idnumber', $idnumber, array('id' => $cm->id));
+        rebuild_course_cache($cm->course, true);
+    }
+    return ($cm->idnumber != $idnumber);
+}
+
+/**
+ * Set the visibility of a module and inherent properties.
+ *
+ * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
+ * has been moved to {@link set_section_visible()} which was the only place from which
+ * the parameter was used.
+ *
+ * @param int $id of the module
+ * @param int $visible state of the module
+ * @return bool false when the module was not found, true otherwise
+ */
+function set_coursemodule_visible($id, $visible) {
+    global $DB, $CFG;
+    require_once($CFG->libdir.'/gradelib.php');
+
+    // Trigger developer's attention when using the previously removed argument.
+    if (func_num_args() > 2) {
+        debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
+            has been removed.', DEBUG_DEVELOPER);
+    }
+
+    if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
+        return false;
+    }
+
+    // Create events and propagate visibility to associated grade items if the value has changed.
+    // Only do this if it's changed to avoid accidently overwriting manual showing/hiding of student grades.
+    if ($cm->visible == $visible) {
+        return true;
+    }
+
+    if (!$modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module))) {
+        return false;
+    }
+    if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
+        foreach($events as $event) {
+            if ($visible) {
+                show_event($event);
+            } else {
+                hide_event($event);
+            }
+        }
+    }
+
+    // Hide the associated grade items so the teacher doesn't also have to go to the gradebook and hide them there.
+    $grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename, 'iteminstance'=>$cm->instance, 'courseid'=>$cm->course));
+    if ($grade_items) {
+        foreach ($grade_items as $grade_item) {
+            $grade_item->set_hidden(!$visible);
+        }
+    }
+
+    // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
+    // affect visibleold to allow for an original visibility restore. See set_section_visible().
+    $cminfo = new stdClass();
+    $cminfo->id = $id;
+    $cminfo->visible = $visible;
+    $cminfo->visibleold = $visible;
+    $DB->update_record('course_modules', $cminfo);
+
+    rebuild_course_cache($cm->course, true);
+    return true;
+}
+
+/**
+ * Delete a course module and any associated data at the course level (events)
+ * Until 1.5 this function simply marked a deleted flag ... now it
+ * deletes it completely.
+ *
+ */
+function delete_course_module($id) {
+    global $CFG, $DB;
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->dirroot.'/blog/lib.php');
+
+    if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
+        return true;
+    }
+    $modulename = $DB->get_field('modules', 'name', array('id'=>$cm->module));
+    //delete events from calendar
+    if ($events = $DB->get_records('event', array('instance'=>$cm->instance, 'modulename'=>$modulename))) {
+        foreach($events as $event) {
+            delete_event($event->id);
+        }
+    }
+    //delete grade items, outcome items and grades attached to modules
+    if ($grade_items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,
+                                                   'iteminstance'=>$cm->instance, 'courseid'=>$cm->course))) {
+        foreach ($grade_items as $grade_item) {
+            $grade_item->delete('moddelete');
+        }
+    }
+    // Delete completion and availability data; it is better to do this even if the
+    // features are not turned on, in case they were turned on previously (these will be
+    // very quick on an empty table)
+    $DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
+    $DB->delete_records('course_modules_availability', array('coursemoduleid'=> $cm->id));
+    $DB->delete_records('course_modules_avail_fields', array('coursemoduleid' => $cm->id));
+    $DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
+                                                            'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
+
+    delete_context(CONTEXT_MODULE, $cm->id);
+    $DB->delete_records('course_modules', array('id'=>$cm->id));
+    rebuild_course_cache($cm->course, true);
+    return true;
+}
+
+function delete_mod_from_section($modid, $sectionid) {
+    global $DB;
+
+    if ($section = $DB->get_record("course_sections", array("id"=>$sectionid)) ) {
+
+        $modarray = explode(",", $section->sequence);
+
+        if ($key = array_keys ($modarray, $modid)) {
+            array_splice($modarray, $key[0], 1);
+            $newsequence = implode(",", $modarray);
+            $DB->set_field("course_sections", "sequence", $newsequence, array("id"=>$section->id));
+            rebuild_course_cache($section->course, true);
+            return true;
+        } else {
+            return false;
+        }
+
+    }
+    return false;
+}
+
+/**
+ * Moves a section up or down by 1. CANNOT BE USED DIRECTLY BY AJAX!
+ *
+ * @param object $course course object
+ * @param int $section Section number (not id!!!)
+ * @param int $move (-1 or 1)
+ * @return boolean true if section moved successfully
+ * @todo MDL-33379 remove this function in 2.5
+ */
+function move_section($course, $section, $move) {
+    debugging('This function will be removed before 2.5 is released please use move_section_to', DEBUG_DEVELOPER);
+
+/// Moves a whole course section up and down within the course
+    global $USER;
+
+    if (!$move) {
+        return true;
+    }
+
+    $sectiondest = $section + $move;
+
+    // compartibility with course formats using field 'numsections'
+    $courseformatoptions = course_get_format($course)->get_format_options();
+    if (array_key_exists('numsections', $courseformatoptions) &&
+            $sectiondest > $courseformatoptions['numsections'] or $sectiondest < 1) {
+        return false;
+    }
+
+    $retval = move_section_to($course, $section, $sectiondest);
+    return $retval;
+}
+
+/**
+ * Moves a section within a course, from a position to another.
+ * Be very careful: $section and $destination refer to section number,
+ * not id!.
+ *
+ * @param object $course
+ * @param int $section Section number (not id!!!)
+ * @param int $destination
+ * @return boolean Result
+ */
+function move_section_to($course, $section, $destination) {
+/// Moves a whole course section up and down within the course
+    global $USER, $DB;
+
+    if (!$destination && $destination != 0) {
+        return true;
+    }
+
+    // compartibility with course formats using field 'numsections'
+    $courseformatoptions = course_get_format($course)->get_format_options();
+    if ((array_key_exists('numsections', $courseformatoptions) &&
+            ($destination > $courseformatoptions['numsections'])) || ($destination < 1)) {
+        return false;
+    }
+
+    // Get all sections for this course and re-order them (2 of them should now share the same section number)
+    if (!$sections = $DB->get_records_menu('course_sections', array('course' => $course->id),
+            'section ASC, id ASC', 'id, section')) {
+        return false;
+    }
+
+    $movedsections = reorder_sections($sections, $section, $destination);
+
+    // Update all sections. Do this in 2 steps to avoid breaking database
+    // uniqueness constraint
+    $transaction = $DB->start_delegated_transaction();
+    foreach ($movedsections as $id => $position) {
+        if ($sections[$id] !== $position) {
+            $DB->set_field('course_sections', 'section', -$position, array('id' => $id));
+        }
+    }
+    foreach ($movedsections as $id => $position) {
+        if ($sections[$id] !== $position) {
+            $DB->set_field('course_sections', 'section', $position, array('id' => $id));
+        }
+    }
+
+    // If we move the highlighted section itself, then just highlight the destination.
+    // Adjust the higlighted section location if we move something over it either direction.
+    if ($section == $course->marker) {
+        course_set_marker($course->id, $destination);
+    } elseif ($section > $course->marker && $course->marker >= $destination) {
+        course_set_marker($course->id, $course->marker+1);
+    } elseif ($section < $course->marker && $course->marker <= $destination) {
+        course_set_marker($course->id, $course->marker-1);
+    }
+
+    $transaction->allow_commit();
+    rebuild_course_cache($course->id, true);
+    return true;
+}
+
+/**
+ * Reordering algorithm for course sections. Given an array of section->section indexed by section->id,
+ * an original position number and a target position number, rebuilds the array so that the
+ * move is made without any duplication of section positions.
+ * Note: The target_position is the position AFTER WHICH the moved section will be inserted. If you want to
+ * insert a section before the first one, you must give 0 as the target (section 0 can never be moved).
+ *
+ * @param array $sections
+ * @param int $origin_position
+ * @param int $target_position
+ * @return array
+ */
+function reorder_sections($sections, $origin_position, $target_position) {
+    if (!is_array($sections)) {
+        return false;
+    }
+
+    // We can't move section position 0
+    if ($origin_position < 1) {
+        echo "We can't move section position 0";
+        return false;
+    }
+
+    // Locate origin section in sections array
+    if (!$origin_key = array_search($origin_position, $sections)) {
+        echo "searched position not in sections array";
+        return false; // searched position not in sections array
+    }
+
+    // Extract origin section
+    $origin_section = $sections[$origin_key];
+    unset($sections[$origin_key]);
+
+    // Find offset of target position (stupid PHP's array_splice requires offset instead of key index!)
+    $found = false;
+    $append_array = array();
+    foreach ($sections as $id => $position) {
+        if ($found) {
+            $append_array[$id] = $position;
+            unset($sections[$id]);
+        }
+        if ($position == $target_position) {
+            if ($target_position < $origin_position) {
+                $append_array[$id] = $position;
+                unset($sections[$id]);
+            }
+            $found = true;
+        }
+    }
+
+    // Append moved section
+    $sections[$origin_key] = $origin_section;
+
+    // Append rest of array (if applicable)
+    if (!empty($append_array)) {
+        foreach ($append_array as $id => $position) {
+            $sections[$id] = $position;
+        }
+    }
+
+    // Renumber positions
+    $position = 0;
+    foreach ($sections as $id => $p) {
+        $sections[$id] = $position;
+        $position++;
+    }
+
+    return $sections;
+
+}
+
+/**
+ * Move the module object $mod to the specified $section
+ * If $beforemod exists then that is the module
+ * before which $modid should be inserted
+ * All parameters are objects
+ */
+function moveto_module($mod, $section, $beforemod=NULL) {
+    global $OUTPUT, $DB;
+
+/// Remove original module from original section
+    if (! delete_mod_from_section($mod->id, $mod->section)) {
+        echo $OUTPUT->notification("Could not delete module from existing section");
+    }
+
+    // if moving to a hidden section then hide module
+    if (!$section->visible && $mod->visible) {
+        // Set this in the object because it is sent as a response to ajax calls.
+        $mod->visible = 0;
+        set_coursemodule_visible($mod->id, 0);
+        // Set visibleold to 1 so module will be visible when section is made visible.
+        $DB->set_field('course_modules', 'visibleold', 1, array('id' => $mod->id));
+    }
+    if ($section->visible && !$mod->visible) {
+        set_coursemodule_visible($mod->id, $mod->visibleold);
+        // Set this in the object because it is sent as a response to ajax calls.
+        $mod->visible = $mod->visibleold;
+    }
+
+/// Add the module into the new section
+    course_add_cm_to_section($section->course, $mod->id, $section->section, $beforemod);
+    return true;
+}
+
+/**
+ * Produces the editing buttons for a module
+ *
+ * @global core_renderer $OUTPUT
+ * @staticvar type $str
+ * @param stdClass $mod The module to produce editing buttons for
+ * @param bool $absolute_ignored ignored - all links are absolute
+ * @param bool $moveselect If true a move seleciton process is used (default true)
+ * @param int $indent The current indenting
+ * @param int $section The section to link back to
+ * @return string XHTML for the editing buttons
+ */
+function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $moveselect = true, $indent=-1, $section=null) {
+    global $CFG, $OUTPUT, $COURSE;
+
+    static $str;
+
+    $coursecontext = context_course::instance($mod->course);
+    $modcontext = context_module::instance($mod->id);
+
+    $editcaps = array('moodle/course:manageactivities', 'moodle/course:activityvisibility', 'moodle/role:assign');
+    $dupecaps = array('moodle/backup:backuptargetimport', 'moodle/restore:restoretargetimport');
+
+    // no permission to edit anything
+    if (!has_any_capability($editcaps, $modcontext) and !has_all_capabilities($dupecaps, $coursecontext)) {
+        return false;
+    }
+
+    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
+
+    if (!isset($str)) {
+        $str = new stdClass;
+        $str->assign         = get_string("assignroles", 'role');
+        $str->delete         = get_string("delete");
+        $str->move           = get_string("move");
+        $str->moveup         = get_string("moveup");
+        $str->movedown       = get_string("movedown");
+        $str->moveright      = get_string("moveright");
+        $str->moveleft       = get_string("moveleft");
+        $str->update         = get_string("update");
+        $str->duplicate      = get_string("duplicate");
+        $str->hide           = get_string("hide");
+        $str->show           = get_string("show");
+        $str->groupsnone     = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsnone"));
+        $str->groupsseparate = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsseparate"));
+        $str->groupsvisible  = get_string('clicktochangeinbrackets', 'moodle', get_string("groupsvisible"));
+        $str->forcedgroupsnone     = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsnone"));
+        $str->forcedgroupsseparate = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsseparate"));
+        $str->forcedgroupsvisible  = get_string('forcedmodeinbrackets', 'moodle', get_string("groupsvisible"));
+        $str->edittitle = get_string('edittitle', 'moodle');
+    }
+
+    $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
+
+    if ($section !== null) {
+        $baseurl->param('sr', $section);
+    }
+    $actions = array();
+
+    // AJAX edit title
+    if ($mod->modname !== 'label' && $hasmanageactivities && course_ajax_enabled($COURSE)) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('update' => $mod->id)),
+            new pix_icon('t/editstring', $str->edittitle, 'moodle', array('class' => 'iconsmall visibleifjs', 'title' => '')),
+            null,
+            array('class' => 'editing_title', 'title' => $str->edittitle)
+        );
+    }
+
+    // leftright
+    if ($hasmanageactivities) {
+        if (right_to_left()) {   // Exchange arrows on RTL
+            $rightarrow = 't/left';
+            $leftarrow  = 't/right';
+        } else {
+            $rightarrow = 't/right';
+            $leftarrow  = 't/left';
+        }
+
+        if ($indent > 0) {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '-1')),
+                new pix_icon($leftarrow, $str->moveleft, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_moveleft', 'title' => $str->moveleft)
+            );
+        }
+        if ($indent >= 0) {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('id' => $mod->id, 'indent' => '1')),
+                new pix_icon($rightarrow, $str->moveright, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_moveright', 'title' => $str->moveright)
+            );
+        }
+    }
+
+    // move
+    if ($hasmanageactivities) {
+        if ($moveselect) {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('copy' => $mod->id)),
+                new pix_icon('t/move', $str->move, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_move', 'title' => $str->move)
+            );
+        } else {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('id' => $mod->id, 'move' => '-1')),
+                new pix_icon('t/up', $str->moveup, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_moveup', 'title' => $str->moveup)
+            );
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('id' => $mod->id, 'move' => '1')),
+                new pix_icon('t/down', $str->movedown, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_movedown', 'title' => $str->movedown)
+            );
+        }
+    }
+
+    // Update
+    if ($hasmanageactivities) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('update' => $mod->id)),
+            new pix_icon('t/edit', $str->update, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            null,
+            array('class' => 'editing_update', 'title' => $str->update)
+        );
+    }
+
+    // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
+    if (has_all_capabilities($dupecaps, $coursecontext) && plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('duplicate' => $mod->id)),
+            new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            null,
+            array('class' => 'editing_duplicate', 'title' => $str->duplicate)
+        );
+    }
+
+    // Delete
+    if ($hasmanageactivities) {
+        $actions[] = new action_link(
+            new moodle_url($baseurl, array('delete' => $mod->id)),
+            new pix_icon('t/delete', $str->delete, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            null,
+            array('class' => 'editing_delete', 'title' => $str->delete)
+        );
+    }
+
+    // hideshow
+    if (has_capability('moodle/course:activityvisibility', $modcontext)) {
+        if ($mod->visible) {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('hide' => $mod->id)),
+                new pix_icon('t/hide', $str->hide, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_hide', 'title' => $str->hide)
+            );
+        } else {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('show' => $mod->id)),
+                new pix_icon('t/show', $str->show, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => 'editing_show', 'title' => $str->show)
+            );
+        }
+    }
+
+    // groupmode
+    if ($hasmanageactivities and $mod->groupmode !== false) {
+        if ($mod->groupmode == SEPARATEGROUPS) {
+            $groupmode = 0;
+            $grouptitle = $str->groupsseparate;
+            $forcedgrouptitle = $str->forcedgroupsseparate;
+            $groupclass = 'editing_groupsseparate';
+            $groupimage = 't/groups';
+        } else if ($mod->groupmode == VISIBLEGROUPS) {
+            $groupmode = 1;
+            $grouptitle = $str->groupsvisible;
+            $forcedgrouptitle = $str->forcedgroupsvisible;
+            $groupclass = 'editing_groupsvisible';
+            $groupimage = 't/groupv';
+        } else {
+            $groupmode = 2;
+            $grouptitle = $str->groupsnone;
+            $forcedgrouptitle = $str->forcedgroupsnone;
+            $groupclass = 'editing_groupsnone';
+            $groupimage = 't/groupn';
+        }
+        if ($mod->groupmodelink) {
+            $actions[] = new action_link(
+                new moodle_url($baseurl, array('id' => $mod->id, 'groupmode' => $groupmode)),
+                new pix_icon($groupimage, $grouptitle, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                null,
+                array('class' => $groupclass, 'title' => $grouptitle)
+            );
+        } else {
+            $actions[] = new pix_icon($groupimage, $forcedgrouptitle, 'moodle', array('title' => $forcedgrouptitle, 'class' => 'iconsmall'));
+        }
+    }
+
+    // Assign
+    if (has_capability('moodle/role:assign', $modcontext)){
+        $actions[] = new action_link(
+            new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
+            new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            null,
+            array('class' => 'editing_assign', 'title' => $str->assign)
+        );
+    }
+
+    // The space added before the <span> is a ugly hack but required to set the CSS property white-space: nowrap
+    // and having it to work without attaching the preceding text along with it. Hopefully the refactoring of
+    // the course page HTML will allow this to be removed.
+    $output = ' ' . html_writer::start_tag('span', array('class' => 'commands'));
+    foreach ($actions as $action) {
+        if ($action instanceof renderable) {
+            $output .= $OUTPUT->render($action);
+        } else {
+            $output .= $action;
+        }
+    }
+    $output .= html_writer::end_tag('span');
+    return $output;
+}
+
+/**
+ * given a course object with shortname & fullname, this function will
+ * truncate the the number of chars allowed and add ... if it was too long
+ */
+function course_format_name ($course,$max=100) {
+
+    $context = context_course::instance($course->id);
+    $shortname = format_string($course->shortname, true, array('context' => $context));
+    $fullname = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
+    $str = $shortname.': '. $fullname;
+    if (textlib::strlen($str) <= $max) {
+        return $str;
+    }
+    else {
+        return textlib::substr($str,0,$max-3).'...';
+    }
+}
+
+/**
+ * Is the user allowed to add this type of module to this course?
+ * @param object $course the course settings. Only $course->id is used.
+ * @param string $modname the module name. E.g. 'forum' or 'quiz'.
+ * @return bool whether the current user is allowed to add this type of module to this course.
+ */
+function course_allowed_module($course, $modname) {
+    if (is_numeric($modname)) {
+        throw new coding_exception('Function course_allowed_module no longer
+                supports numeric module ids. Please update your code to pass the module name.');
+    }
+
+    $capability = 'mod/' . $modname . ':addinstance';
+    if (!get_capability_info($capability)) {
+        // Debug warning that the capability does not exist, but no more than once per page.
+        static $warned = array();
+        $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
+        if (!isset($warned[$modname]) && $archetype !== MOD_ARCHETYPE_SYSTEM) {
+            debugging('The module ' . $modname . ' does not define the standard capability ' .
+                    $capability , DEBUG_DEVELOPER);
+            $warned[$modname] = 1;
+        }
+
+        // If the capability does not exist, the module can always be added.
+        return true;
+    }
+
+    $coursecontext = context_course::instance($course->id);
+    return has_capability($capability, $coursecontext);
+}
+
+/**
+ * Recursively delete category including all subcategories and courses.
+ * @param stdClass $category
+ * @param boolean $showfeedback display some notices
+ * @return array return deleted courses
+ */
+function category_delete_full($category, $showfeedback=true) {
+    global $CFG, $DB;
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->libdir.'/questionlib.php');
+    require_once($CFG->dirroot.'/cohort/lib.php');
+
+    if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
+        foreach ($children as $childcat) {
+            category_delete_full($childcat, $showfeedback);
+        }
+    }
+
+    $deletedcourses = array();
+    if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC')) {
+        foreach ($courses as $course) {
+            if (!delete_course($course, false)) {
+                throw new moodle_exception('cannotdeletecategorycourse','','',$course->shortname);
+            }
+            $deletedcourses[] = $course;
+        }
+    }
+
+    // move or delete cohorts in this context
+    cohort_delete_category($category);
+
+    // now delete anything that may depend on course category context
+    grade_course_category_delete($category->id, 0, $showfeedback);
+    if (!question_delete_course_category($category, 0, $showfeedback)) {
+        throw new moodle_exception('cannotdeletecategoryquestions','','',$category->name);
+    }
+
+    // finally delete the category and it's context
+    $DB->delete_records('course_categories', array('id'=>$category->id));
+    delete_context(CONTEXT_COURSECAT, $category->id);
+    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
+
+    events_trigger('course_category_deleted', $category);
+
+    return $deletedcourses;
+}
+
+/**
+ * Delete category, but move contents to another category.
+ * @param object $ccategory
+ * @param int $newparentid category id
+ * @return bool status
+ */
+function category_delete_move($category, $newparentid, $showfeedback=true) {
+    global $CFG, $DB, $OUTPUT;
+    require_once($CFG->libdir.'/gradelib.php');
+    require_once($CFG->libdir.'/questionlib.php');
+    require_once($CFG->dirroot.'/cohort/lib.php');
+
+    if (!$newparentcat = $DB->get_record('course_categories', array('id'=>$newparentid))) {
+        return false;
+    }
+
+    if ($children = $DB->get_records('course_categories', array('parent'=>$category->id), 'sortorder ASC')) {
+        foreach ($children as $childcat) {
+            move_category($childcat, $newparentcat);
+        }
+    }
+
+    if ($courses = $DB->get_records('course', array('category'=>$category->id), 'sortorder ASC', 'id')) {
+        if (!move_courses(array_keys($courses), $newparentid)) {
+            if ($showfeedback) {
+                echo $OUTPUT->notification("Error moving courses");
+            }
+            return false;
+        }
+        if ($showfeedback) {
+            echo $OUTPUT->notification(get_string('coursesmovedout', '', format_string($category->name)), 'notifysuccess');
+        }
+    }
+
+    // move or delete cohorts in this context
+    cohort_delete_category($category);
+
+    // now delete anything that may depend on course category context
+    grade_course_category_delete($category->id, $newparentid, $showfeedback);
+    if (!question_delete_course_category($category, $newparentcat, $showfeedback)) {
+        if ($showfeedback) {
+            echo $OUTPUT->notification(get_string('errordeletingquestionsfromcategory', 'question', $category), 'notifysuccess');
+        }
+        return false;
+    }
+
+    // finally delete the category and it's context
+    $DB->delete_records('course_categories', array('id'=>$category->id));
+    delete_context(CONTEXT_COURSECAT, $category->id);
+    add_to_log(SITEID, "category", "delete", "index.php", "$category->name (ID $category->id)");
+
+    events_trigger('course_category_deleted', $category);
+
+    if ($showfeedback) {
+        echo $OUTPUT->notification(get_string('coursecategorydeleted', '', format_string($category->name)), 'notifysuccess');
+    }
+    return true;
+}
+
+/**
+ * Efficiently moves many courses around while maintaining
+ * sortorder in order.
+ *
+ * @param array $courseids is an array of course ids
+ * @param int $categoryid
+ * @return bool success
+ */
+function move_courses($courseids, $categoryid) {
+    global $CFG, $DB, $OUTPUT;
+
+    if (empty($courseids)) {
+        // nothing to do
+        return;
+    }
+
+    if (!$category = $DB->get_record('course_categories', array('id'=>$categoryid))) {
+        return false;
+    }
+
+    $courseids = array_reverse($courseids);
+    $newparent = context_coursecat::instance($category->id);
+    $i = 1;
+
+    foreach ($courseids as $courseid) {
+        if ($course = $DB->get_record('course', array('id'=>$courseid), 'id, category')) {
+            $course = new stdClass();
+            $course->id = $courseid;
+            $course->category  = $category->id;
+            $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
+            if ($category->visible == 0) {
+                // hide the course when moving into hidden category,
+                // do not update the visibleold flag - we want to get to previous state if somebody unhides the category
+                $course->visible = 0;
+            }
+
+            $DB->update_record('course', $course);
+            add_to_log($course->id, "course", "move", "edit.php?id=$course->id", $course->id);
+
+            $context   = context_course::instance($course->id);
+            context_moved($context, $newparent);
+        }
+    }
+    fix_course_sortorder();
+
+    return true;
+}
+
+/**
+ * Hide course category and child course and subcategories
+ * @param stdClass $category
+ * @return void
+ */
+function course_category_hide($category) {
+    global $DB;
+
+    $category->visible = 0;
+    $DB->set_field('course_categories', 'visible', 0, array('id'=>$category->id));
+    $DB->set_field('course_categories', 'visibleold', 0, array('id'=>$category->id));
+    $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($category->id)); // store visible flag so that we can return to it if we immediately unhide
+    $DB->set_field('course', 'visible', 0, array('category' => $category->id));
+    // get all child categories and hide too
+    if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
+        foreach ($subcats as $cat) {
+            $DB->set_field('course_categories', 'visibleold', $cat->visible, array('id'=>$cat->id));
+            $DB->set_field('course_categories', 'visible', 0, array('id'=>$cat->id));
+            $DB->execute("UPDATE {course} SET visibleold = visible WHERE category = ?", array($cat->id));
+            $DB->set_field('course', 'visible', 0, array('category' => $cat->id));
+        }
+    }
+    add_to_log(SITEID, "category", "hide", "editcategory.php?id=$category->id", $category->id);
+}
+
+/**
+ * Show course category and child course and subcategories
+ * @param stdClass $category
+ * @return void
+ */
+function course_category_show($category) {
+    global $DB;
+
+    $category->visible = 1;
+    $DB->set_field('course_categories', 'visible', 1, array('id'=>$category->id));
+    $DB->set_field('course_categories', 'visibleold', 1, array('id'=>$category->id));
+    $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($category->id));
+    // get all child categories and unhide too
+    if ($subcats = $DB->get_records_select('course_categories', "path LIKE ?", array("$category->path/%"))) {
+        foreach ($subcats as $cat) {
+            if ($cat->visibleold) {
+                $DB->set_field('course_categories', 'visible', 1, array('id'=>$cat->id));
+            }
+            $DB->execute("UPDATE {course} SET visible = visibleold WHERE category = ?", array($cat->id));
+        }
+    }
+    add_to_log(SITEID, "category", "show", "editcategory.php?id=$category->id", $category->id);
+}
+
+/**
+ * Efficiently moves a category - NOTE that this can have
+ * a huge impact access-control-wise...
+ */
+function move_category($category, $newparentcat) {
+    global $CFG, $DB;
+
+    $context = context_coursecat::instance($category->id);
+
+    $hidecat = false;
+    if (empty($newparentcat->id)) {
+        $DB->set_field('course_categories', 'parent', 0, array('id' => $category->id));
+        $newparent = context_system::instance();
+    } else {
+        $DB->set_field('course_categories', 'parent', $newparentcat->id, array('id' => $category->id));
+        $newparent = context_coursecat::instance($newparentcat->id);
+
+        if (!$newparentcat->visible and $category->visible) {
+            // better hide category when moving into hidden category, teachers may unhide afterwards and the hidden children will be restored properly
+            $hidecat = true;
+        }
+    }
+
+    context_moved($context, $newparent);
+
+    // now make it last in new category
+    $DB->set_field('course_categories', 'sortorder', MAX_COURSES_IN_CATEGORY*MAX_COURSE_CATEGORIES, array('id'=>$category->id));
+
+    // Log action.
+    add_to_log(SITEID, "category", "move", "editcategory.php?id=$category->id", $category->id);
+
+    // and fix the sortorders
+    fix_course_sortorder();
+
+    if ($hidecat) {
+        course_category_hide($category);
+    }
+}
+
+/**
+ * Returns the display name of the given section that the course prefers
+ *
+ * Implementation of this function is provided by course format
+ * @see format_base::get_section_name()
+ *
+ * @param int|stdClass $courseorid The course to get the section name for (object or just course id)
+ * @param int|stdClass $section Section object from database or just field course_sections.section
+ * @return string Display name that the course format prefers, e.g. "Week 2"
+ */
+function get_section_name($courseorid, $section) {
+    return course_get_format($courseorid)->get_section_name($section);
+}
+
+/**
+ * Tells if current course format uses sections
+ *
+ * @param string $format Course format ID e.g. 'weeks' $course->format
+ * @return bool
+ */
+function course_format_uses_sections($format) {
+    $course = new stdClass();
+    $course->format = $format;
+    return course_get_format($course)->uses_sections();
+}
+
+/**
+ * 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 {@see ajaxenabled()}.
+ *
+ * @param string $format
+ * @return stdClass
+ */
+function course_format_ajax_support($format) {
+    $course = new stdClass();
+    $course->format = $format;
+    return course_get_format($course)->supports_ajax();
+}
+
+/**
+ * Can the current user delete this course?
+ * Course creators have exception,
+ * 1 day after the creation they can sill delete the course.
+ * @param int $courseid
+ * @return boolean
+ */
+function can_delete_course($courseid) {
+    global $USER, $DB;
+
+    $context = context_course::instance($courseid);
+
+    if (has_capability('moodle/course:delete', $context)) {
+        return true;
+    }
+
+    // hack: now try to find out if creator created this course recently (1 day)
+    if (!has_capability('moodle/course:create', $context)) {
+        return false;
+    }
+
+    $since = time() - 60*60*24;
+
+    $params = array('userid'=>$USER->id, 'url'=>"view.php?id=$courseid", 'since'=>$since);
+    $select = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
+
+    return $DB->record_exists_select('log', $select, $params);
+}
+
+/**
+ * Save the Your name for 'Some role' strings.
+ *
+ * @param integer $courseid the id of this course.
+ * @param array $data the data that came from the course settings form.
+ */
+function save_local_role_names($courseid, $data) {
+    global $DB;
+    $context = context_course::instance($courseid);
+
+    foreach ($data as $fieldname => $value) {
+        if (strpos($fieldname, 'role_') !== 0) {
+            continue;
+        }
+        list($ignored, $roleid) = explode('_', $fieldname);
+
+        // make up our mind whether we want to delete, update or insert
+        if (!$value) {
+            $DB->delete_records('role_names', array('contextid' => $context->id, 'roleid' => $roleid));
+
+        } else if ($rolename = $DB->get_record('role_names', array('contextid' => $context->id, 'roleid' => $roleid))) {
+            $rolename->name = $value;
+            $DB->update_record('role_names', $rolename);
+
+        } else {
+            $rolename = new stdClass;
+            $rolename->contextid = $context->id;
+            $rolename->roleid = $roleid;
+            $rolename->name = $value;
+            $DB->insert_record('role_names', $rolename);
+        }
+    }
+}
+
+/**
+ * Create a course and either return a $course object
+ *
+ * Please note this functions does not verify any access control,
+ * the calling code is responsible for all validation (usually it is the form definition).
+ *
+ * @param array $editoroptions course description editor options
+ * @param object $data  - all the data needed for an entry in the 'course' table
+ * @return object new course instance
+ */
+function create_course($data, $editoroptions = NULL) {
+    global $CFG, $DB;
+
+    //check the categoryid - must be given for all new courses
+    $category = $DB->get_record('course_categories', array('id'=>$data->category), '*', MUST_EXIST);
+
+    //check if the shortname already exist
+    if (!empty($data->shortname)) {
+        if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
+            throw new moodle_exception('shortnametaken');
+        }
+    }
+
+    //check if the id number already exist
+    if (!empty($data->idnumber)) {
+        if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+            throw new moodle_exception('idnumbertaken');
+        }
+    }
+
+    $data->timecreated  = time();
+    $data->timemodified = $data->timecreated;
+
+    // place at beginning of any category
+    $data->sortorder = 0;
+
+    if ($editoroptions) {
+        // summary text is updated later, we need context to store the files first
+        $data->summary = '';
+        $data->summary_format = FORMAT_HTML;
+    }
+
+    if (!isset($data->visible)) {
+        // data not from form, add missing visibility info
+        $data->visible = $category->visible;
+    }
+    $data->visibleold = $data->visible;
+
+    $newcourseid = $DB->insert_record('course', $data);
+    $context = context_course::instance($newcourseid, MUST_EXIST);
+
+    if ($editoroptions) {
+        // Save the files used in the summary editor and store
+        $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
+        $DB->set_field('course', 'summary', $data->summary, array('id'=>$newcourseid));
+        $DB->set_field('course', 'summaryformat', $data->summary_format, array('id'=>$newcourseid));
+    }
+
+    // update course format options
+    course_get_format($newcourseid)->update_course_format_options($data);
+
+    $course = course_get_format($newcourseid)->get_course();
+
+    // Setup the blocks
+    blocks_add_default_course_blocks($course);
+
+    // Create a default section.
+    course_create_sections_if_missing($course, 0);
+
+    fix_course_sortorder();
+
+    // new context created - better mark it as dirty
+    mark_context_dirty($context->path);
+
+    // Save any custom role names.
+    save_local_role_names($course->id, (array)$data);
+
+    // set up enrolments
+    enrol_course_updated(true, $course, $data);
+
+    add_to_log(SITEID, 'course', 'new', 'view.php?id='.$course->id, $data->fullname.' (ID '.$course->id.')');
+
+    // Trigger events
+    events_trigger('course_created', $course);
+
+    return $course;
+}
+
+/**
+ * Create a new course category and marks the context as dirty
+ *
+ * This function does not set the sortorder for the new category and
+ * @see{fix_course_sortorder} should be called after creating a new course
+ * category
+ *
+ * Please note that this function does not verify access control.
+ *
+ * @param object $category All of the data required for an entry in the course_categories table
+ * @return object new course category
+ */
+function create_course_category($category) {
+    global $DB;
+
+    $category->timemodified = time();
+    $category->id = $DB->insert_record('course_categories', $category);
+    $category = $DB->get_record('course_categories', array('id' => $category->id));
+
+    // We should mark the context as dirty
+    $category->context = context_coursecat::instance($category->id);
+    $category->context->mark_dirty();
+
+    return $category;
+}
+
+/**
+ * Update a course.
+ *
+ * Please note this functions does not verify any access control,
+ * the calling code is responsible for all validation (usually it is the form definition).
+ *
+ * @param object $data  - all the data needed for an entry in the 'course' table
+ * @param array $editoroptions course description editor options
+ * @return void
+ */
+function update_course($data, $editoroptions = NULL) {
+    global $CFG, $DB;
+
+    $data->timemodified = time();
+
+    $oldcourse = course_get_format($data->id)->get_course();
+    $context   = context_course::instance($oldcourse->id);
+
+    if ($editoroptions) {
+        $data = file_postupdate_standard_editor($data, 'summary', $editoroptions, $context, 'course', 'summary', 0);
+    }
+
+    if (!isset($data->category) or empty($data->category)) {
+        // prevent nulls and 0 in category field
+        unset($data->category);
+    }
+    $movecat = (isset($data->category) and $oldcourse->category != $data->category);
+
+    if (!isset($data->visible)) {
+        // data not from form, add missing visibility info
+        $data->visible = $oldcourse->visible;
+    }
+
+    if ($data->visible != $oldcourse->visible) {
+        // reset the visibleold flag when manually hiding/unhiding course
+        $data->visibleold = $data->visible;
+    } else {
+        if ($movecat) {
+            $newcategory = $DB->get_record('course_categories', array('id'=>$data->category));
+            if (empty($newcategory->visible)) {
+                // make sure when moving into hidden category the course is hidden automatically
+                $data->visible = 0;
+            }
+        }
+    }
+
+    // Update with the new data
+    $DB->update_record('course', $data);
+    // make sure the modinfo cache is reset
+    rebuild_course_cache($data->id);
+
+    // update course format options with full course data
+    course_get_format($data->id)->update_course_format_options($data, $oldcourse);
+
+    $course = $DB->get_record('course', array('id'=>$data->id));
+
+    if ($movecat) {
+        $newparent = context_coursecat::instance($course->category);
+        context_moved($context, $newparent);
+    }
+
+    fix_course_sortorder();
+
+    // Test for and remove blocks which aren't appropriate anymore
+    blocks_remove_inappropriate($course);
+
+    // Save any custom role names.
+    save_local_role_names($course->id, $data);
+
+    // update enrol settings
+    enrol_course_updated(false, $course, $data);
+
+    add_to_log($course->id, "course", "update", "edit.php?id=$course->id", $course->id);
+
+    // Trigger events
+    events_trigger('course_updated', $course);
+
+    if ($oldcourse->format !== $course->format) {
+        // Remove all options stored for the previous format
+        // We assume that new course format migrated everything it needed watching trigger
+        // 'course_updated' and in method format_XXX::update_course_format_options()
+        $DB->delete_records('course_format_options',
+                array('courseid' => $course->id, 'format' => $oldcourse->format));
+    }
+}
+
+/**
+ * Average number of participants
+ * @return integer
+ */
+function average_number_of_participants() {
+    global $DB, $SITE;
+
+    //count total of enrolments for visible course (except front page)
+    $sql = 'SELECT COUNT(*) FROM (
+        SELECT DISTINCT ue.userid, e.courseid
+        FROM {user_enrolments} ue, {enrol} e, {course} c
+        WHERE ue.enrolid = e.id
+            AND e.courseid <> :siteid
+            AND c.id = e.courseid
+            AND c.visible = 1) total';
+    $params = array('siteid' => $SITE->id);
+    $enrolmenttotal = $DB->count_records_sql($sql, $params);
+
+
+    //count total of visible courses (minus front page)
+    $coursetotal = $DB->count_records('course', array('visible' => 1));
+    $coursetotal = $coursetotal - 1 ;
+
+    //average of enrolment
+    if (empty($coursetotal)) {
+        $participantaverage = 0;
+    } else {
+        $participantaverage = $enrolmenttotal / $coursetotal;
+    }
+
+    return $participantaverage;
+}
+
+/**
+ * Average number of course modules
+ * @return integer
+ */
+function average_number_of_courses_modules() {
+    global $DB, $SITE;
+
+    //count total of visible course module (except front page)
+    $sql = 'SELECT COUNT(*) FROM (
+        SELECT cm.course, cm.module
+        FROM {course} c, {course_modules} cm
+        WHERE c.id = cm.course
+            AND c.id <> :siteid
+            AND cm.visible = 1
+            AND c.visible = 1) total';
+    $params = array('siteid' => $SITE->id);
+    $moduletotal = $DB->count_records_sql($sql, $params);
+
+
+    //count total of visible courses (minus front page)
+    $coursetotal = $DB->count_records('course', array('visible' => 1));
+    $coursetotal = $coursetotal - 1 ;
+
+    //average of course module
+    if (empty($coursetotal)) {
+        $coursemoduleaverage = 0;
+    } else {
+        $coursemoduleaverage = $moduletotal / $coursetotal;
+    }
+
+    return $coursemoduleaverage;
+}
+
+/**
+ * This class pertains to course requests and contains methods associated with
+ * create, approving, and removing course requests.
+ *
+ * Please note we do not allow embedded images here because there is no context
+ * to store them with proper access control.
+ *
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.0
+ *
+ * @property-read int $id
+ * @property-read string $fullname
+ * @property-read string $shortname
+ * @property-read string $summary
+ * @property-read int $summaryformat
+ * @property-read int $summarytrust
+ * @property-read string $reason
+ * @property-read int $requester
+ */
+class course_request {
+
+    /**
+     * This is the stdClass that stores the properties for the course request
+     * and is externally accessed through the __get magic method
+     * @var stdClass
+     */
+    protected $properties;
+
+    /**
+     * An array of options for the summary editor used by course request forms.
+     * This is initially set by {@link summary_editor_options()}
+     * @var array
+     * @static
+     */
+    protected static $summaryeditoroptions;
+
+    /**
+     * Static function to prepare the summary editor for working with a course
+     * request.
+     *
+     * @static
+     * @param null|stdClass $data Optional, an object containing the default values
+     *                       for the form, these may be modified when preparing the
+     *                       editor so this should be called before creating the form
+     * @return stdClass An object that can be used to set the default values for
+     *                   an mforms form
+     */
+    public static function prepare($data=null) {
+        if ($data === null) {
+            $data = new stdClass;
+        }
+        $data = file_prepare_standard_editor($data, 'summary', self::summary_editor_options());
+        return $data;
+    }
+
+    /**
+     * Static function to create a new course request when passed an array of properties
+     * for it.
+     *
+     * This function also handles saving any files that may have been used in the editor
+     *
+     * @static
+     * @param stdClass $data
+     * @return course_request The newly created course request
+     */
+    public static function create($data) {
+        global $USER, $DB, $CFG;
+        $data->requester = $USER->id;
+
+        // Setting the default category if none set.
+        if (empty($data->category) || empty($CFG->requestcategoryselection)) {
+            $data->category = $CFG->defaultrequestcategory;
+        }
+
+        // Summary is a required field so copy the text over
+        $data->summary       = $data->summary_editor['text'];
+        $data->summaryformat = $data->summary_editor['format'];
+
+        $data->id = $DB->insert_record('course_request', $data);
+
+        // Create a new course_request object and return it
+        $request = new course_request($data);
+
+        // Notify the admin if required.
+        if ($users = get_users_from_config($CFG->courserequestnotify, 'moodle/site:approvecourse')) {
+
+            $a = new stdClass;
+            $a->link = "$CFG->wwwroot/course/pending.php";
+            $a->user = fullname($USER);
+            $subject = get_string('courserequest');
+            $message = get_string('courserequestnotifyemail', 'admin', $a);
+            foreach ($users as $user) {
+                $request->notify($user, $USER, 'courserequested', $subject, $message);
+            }
+        }
+
+        return $request;
+    }
+
+    /**
+     * Returns an array of options to use with a summary editor
+     *
+     * @uses course_request::$summaryeditoroptions
+     * @return array An array of options to use with the editor
+     */
+    public static function summary_editor_options() {
+        global $CFG;
+        if (self::$summaryeditoroptions === null) {
+            self::$summaryeditoroptions = array('maxfiles' => 0, 'maxbytes'=>0);
+        }
+        return self::$summaryeditoroptions;
+    }
+
+    /**
+     * Loads the properties for this course request object. Id is required and if
+     * only id is provided then we load the rest of the properties from the database
+     *
+     * @param stdClass|int $properties Either an object containing properties
+     *                      or the course_request id to load
+     */
+    public function __construct($properties) {
+        global $DB;
+        if (empty($properties->id)) {
+            if (empty($properties)) {
+                throw new coding_exception('You must provide a course request id when creating a course_request object');
+            }
+            $id = $properties;
+            $properties = new stdClass;
+            $properties->id = (int)$id;
+            unset($id);
+        }
+        if (empty($properties->requester)) {
+            if (!($this->properties = $DB->get_record('course_request', array('id' => $properties->id)))) {
+                print_error('unknowncourserequest');
+            }
+        } else {
+            $this->properties = $properties;
+        }
+        $this->properties->collision = null;
+    }
+
+    /**
+     * Returns the requested property
+     *
+     * @param string $key
+     * @return mixed
+     */
+    public function __get($key) {
+        return $this->properties->$key;
+    }
+
+    /**
+     * Override this to ensure empty($request->blah) calls return a reliable answer...
+     *
+     * This is required because we define the __get method
+     *
+     * @param mixed $key
+     * @return bool True is it not empty, false otherwise
+     */
+    public function __isset($key) {
+        return (!empty($this->properties->$key));
+    }
+
+    /**
+     * Returns the user who requested this course
+     *
+     * Uses a static var to cache the results and cut down the number of db queries
+     *
+     * @staticvar array $requesters An array of cached users
+     * @return stdClass The user who requested the course
+     */
+    public function get_requester() {
+        global $DB;
+        static $requesters= array();
+        if (!array_key_exists($this->properties->requester, $requesters)) {
+            $requesters[$this->properties->requester] = $DB->get_record('user', array('id'=>$this->properties->requester));
+        }
+        return $requesters[$this->properties->requester];
+    }
+
+    /**
+     * Checks that the shortname used by the course does not conflict with any other
+     * courses that exist
+     *
+     * @param string|null $shortnamemark The string to append to the requests shortname
+     *                     should a conflict be found
+     * @return bool true is there is a conflict, false otherwise
+     */
+    public function check_shortname_collision($shortnamemark = '[*]') {
+        global $DB;
+
+        if ($this->properties->collision !== null) {
+            return $this->properties->collision;
+        }
+
+        if (empty($this->properties->shortname)) {
+            debugging('Attempting to check a course request shortname before it has been set', DEBUG_DEVELOPER);
+            $this->properties->collision = false;
+        } else if ($DB->record_exists('course', array('shortname' => $this->properties->shortname))) {
+            if (!empty($shortnamemark)) {
+                $this->properties->shortname .= ' '.$shortnamemark;
+            }
+            $this->properties->collision = true;
+        } else {
+            $this->properties->collision = false;
+        }
+        return $this->properties->collision;
+    }
+
+    /**
+     * This function approves the request turning it into a course
+     *
+     * This function converts the course request into a course, at the same time
+     * transferring any files used in the summary to the new course and then removing
+     * the course request and the files associated with it.
+     *
+     * @return int The id of the course that was created from this request
+     */
+    public function approve() {
+        global $CFG, $DB, $USER;
+
+        $user = $DB->get_record('user', array('id' => $this->properties->requester, 'deleted'=>0), '*', MUST_EXIST);
+
+        $courseconfig = get_config('moodlecourse');
+
+        // Transfer appropriate settings
+        $data = clone($this->properties);
+        unset($data->id);
+        unset($data->reason);
+        unset($data->requester);
+
+        // If the category is not set, if the current user does not have the rights to change the category, or if the
+        // category does not exist, we set the default category to the course to be approved.
+        // The system level is used because the capability moodle/site:approvecourse is based on a system level.
+        if (empty($data->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
+                (!$category = get_course_category($data->category))) {
+            $category = get_course_category($CFG->defaultrequestcategory);
+        }
+
+        // Set category
+        $data->category = $category->id;
+        $data->sortorder = $category->sortorder; // place as the first in category
+
+        // Set misc settings
+        $data->requested = 1;
+
+        // Apply course default settings
+        $data->format             = $courseconfig->format;
+        $data->newsitems          = $courseconfig->newsitems;
+        $data->showgrades         = $courseconfig->showgrades;
+        $data->showreports        = $courseconfig->showreports;
+        $data->maxbytes           = $courseconfig->maxbytes;
+        $data->groupmode          = $courseconfig->groupmode;
+        $data->groupmodeforce     = $courseconfig->groupmodeforce;
+        $data->visible            = $courseconfig->visible;
+        $data->visibleold         = $data->visible;
+        $data->lang               = $courseconfig->lang;
+
+        $course = create_course($data);
+        $context = context_course::instance($course->id, MUST_EXIST);
+
+        // add enrol instances
+        if (!$DB->record_exists('enrol', array('courseid'=>$course->id, 'enrol'=>'manual'))) {
+            if ($manual = enrol_get_plugin('manual')) {
+                $manual->add_default_instance($course);
+            }
+        }
+
+        // enrol the requester as teacher if necessary
+        if (!empty($CFG->creatornewroleid) and !is_viewing($context, $user, 'moodle/role:assign') and !is_enrolled($context, $user, 'moodle/role:assign')) {
+            enrol_try_internal_enrol($course->id, $user->id, $CFG->creatornewroleid);
+        }
+
+        $this->delete();
+
+        $a = new stdClass();
+        $a->name = format_string($course->fullname, true, array('context' => context_course::instance($course->id)));
+        $a->url = $CFG->wwwroot.'/course/view.php?id=' . $course->id;
+        $this->notify($user, $USER, 'courserequestapproved', get_string('courseapprovedsubject'), get_string('courseapprovedemail2', 'moodle', $a));
+
+        return $course->id;
+    }
+
+    /**
+     * Reject a course request
+     *
+     * This function rejects a course request, emailing the requesting user the
+     * provided notice and then removing the request from the database
+     *
+     * @param string $notice The message to display to the user
+     */
+    public function reject($notice) {
+        global $USER, $DB;
+        $user = $DB->get_record('user', array('id' => $this->properties->requester), '*', MUST_EXIST);
+        $this->notify($user, $USER, 'courserequestrejected', get_string('courserejectsubject'), get_string('courserejectemail', 'moodle', $notice));
+        $this->delete();
+    }
+
+    /**
+     * Deletes the course request and any associated files
+     */
+    public function delete() {
+        global $DB;
+        $DB->delete_records('course_request', array('id' => $this->properties->id));
+    }
+
+    /**
+     * Send a message from one user to another using events_trigger
+     *
+     * @param object $touser
+     * @param object $fromuser
+     * @param string $name
+     * @param string $subject
+     * @param string $message
+     */
+    protected function notify($touser, $fromuser, $name='courserequested', $subject, $message) {
+        $eventdata = new stdClass();
+        $eventdata->component         = 'moodle';
+        $eventdata->name              = $name;
+        $eventdata->userfrom          = $fromuser;
+        $eventdata->userto            = $touser;
+        $eventdata->subject           = $subject;
+        $eventdata->fullmessage       = $message;
+        $eventdata->fullmessageformat = FORMAT_PLAIN;
+        $eventdata->fullmessagehtml   = '';
+        $eventdata->smallmessage      = '';
+        $eventdata->notification      = 1;
+        message_send($eventdata);
+    }
+}
+
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function course_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    // if above course context ,display all course fomats
+    list($currentcontext, $course, $cm) = get_context_info_array($currentcontext->id);
+    if ($course->id == SITEID) {
+        return array('*'=>get_string('page-x', 'pagetype'));
+    } else {
+        return array('*'=>get_string('page-x', 'pagetype'),
+            'course-*'=>get_string('page-course-x', 'pagetype'),
+            'course-view-*'=>get_string('page-course-view-x', 'pagetype')
+        );
+    }
+}
+
+/**
+ * Determine whether course ajax should be enabled for the specified course
+ *
+ * @param stdClass $course The course to test against
+ * @return boolean Whether course ajax is enabled or note
+ */
+function course_ajax_enabled($course) {
+    global $CFG, $PAGE, $SITE;
+
+    // Ajax must be enabled globally
+    if (!$CFG->enableajax) {
+        return false;
+    }
+
+    // The user must be editing for AJAX to be included
+    if (!$PAGE->user_is_editing()) {
+        return false;
+    }
+
+    // Check that the theme suports
+    if (!$PAGE->theme->enablecourseajax) {
+        return false;
+    }
+
+    // Check that the course format supports ajax functionality
+    // The site 'format' doesn't have information on course format support
+    if ($SITE->id !== $course->id) {
+        $courseformatajaxsupport = course_format_ajax_support($course->format);
+        if (!$courseformatajaxsupport->capable) {
+            return false;
+        }
+    }
+
+    // All conditions have been met so course ajax should be enabled
+    return true;
+}
+
+/**
+ * Include the relevant javascript and language strings for the resource
+ * toolbox YUI module
+ *
+ * @param integer $id The ID of the course being applied to
+ * @param array $usedmodules An array containing the names of the modules in use on the page
+ * @param array $enabledmodules An array containing the names of the enabled (visible) modules on this site
+ * @param stdClass $config An object containing configuration parameters for ajax modules including:
+ *          * resourceurl   The URL to post changes to for resource changes
+ *          * sectionurl    The URL to post changes to for section changes
+ *          * pageparams    Additional parameters to pass through in the post
+ * @return bool
+ */
+function include_course_ajax($course, $usedmodules = array(), $enabledmodules = null, $config = null) {
+    global $PAGE, $SITE;
+
+    // Ensure that ajax should be included
+    if (!course_ajax_enabled($course)) {
+        return false;
+    }
+
+    if (!$config) {
+        $config = new stdClass();
+    }
+
+    // The URL to use for resource changes
+    if (!isset($config->resourceurl)) {
+        $config->resourceurl = '/course/rest.php';
+    }
+
+    // The URL to use for section changes
+    if (!isset($config->sectionurl)) {
+        $config->sectionurl = '/course/rest.php';
+    }
+
+    // Any additional parameters which need to be included on page submission
+    if (!isset($config->pageparams)) {
+        $config->pageparams = array();
+    }
+
+    // Include toolboxes
+    $PAGE->requires->yui_module('moodle-course-toolboxes',
+            'M.course.init_resource_toolbox',
+            array(array(
+                'courseid' => $course->id,
+                'ajaxurl' => $config->resourceurl,
+                'config' => $config,
+            ))
+    );
+    $PAGE->requires->yui_module('moodle-course-toolboxes',
+            'M.course.init_section_toolbox',
+            array(array(
+                'courseid' => $course->id,
+                'format' => $course->format,
+                'ajaxurl' => $config->sectionurl,
+                'config' => $config,
+            ))
+    );
+
+    // Include course dragdrop
+    if ($course->id != $SITE->id) {
+        $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_section_dragdrop',
+            array(array(
+                'courseid' => $course->id,
+                'ajaxurl' => $config->sectionurl,
+                'config' => $config,
+            )), null, true);
+
+        $PAGE->requires->yui_module('moodle-course-dragdrop', 'M.course.init_resource_dragdrop',
+            array(array(
+                'courseid' => $course->id,
+                'ajaxurl' => $config->resourceurl,
+                'config' => $config,
+            )), null, true);
+    }
+
+    // Include blocks dragdrop
+    $params = array(
+        'courseid' => $course->id,
+        'pagetype' => $PAGE->pagetype,
+        'pagelayout' => $PAGE->pagelayout,
+        'subpage' => $PAGE->subpage,
+        'regions' => $PAGE->blocks->get_regions(),
+    );
+    $PAGE->requires->yui_module('moodle-core-blocks', 'M.core_blocks.init_dragdrop', array($params), null, true);
+
+    // Require various strings for the command toolbox
+    $PAGE->requires->strings_for_js(array(
+            'moveleft',
+            'deletechecktype',
+            'deletechecktypename',
+            'edittitle',
+            'edittitleinstructions',
+            'show',
+            'hide',
+            'groupsnone',
+            'groupsvisible',
+            'groupsseparate',
+            'clicktochangeinbrackets',
+            'markthistopic',
+            'markedthistopic',
+            'move',
+            'movesection',
+        ), 'moodle');
+
+    // Include format-specific strings
+    if ($course->id != $SITE->id) {
+        $PAGE->requires->strings_for_js(array(
+                'showfromothers',
+                'hidefromothers',
+            ), 'format_' . $course->format);
+    }
+
+    // For confirming resource deletion we need the name of the module in question
+    foreach ($usedmodules as $module => $modname) {
+        $PAGE->requires->string_for_js('pluginname', $module);
+    }
+
+    // Load drag and drop upload AJAX.
+    dndupload_add_to_course($course, $enabledmodules);
+
+    // Add the module chooser
+    $PAGE->requires->yui_module('moodle-course-modchooser',
+        'M.course.init_chooser',
+        array(array('courseid' => $course->id, 'closeButtonTitle' => get_string('close', 'editor')))
+    );
+    $PAGE->requires->strings_for_js(array(
+            'addresourceoractivity',
+            'modchooserenable',
+            'modchooserdisable',
+    ), 'moodle');
+
+    return true;
+}
+
+/**
+ * Returns the sorted list of available course formats, filtered by enabled if necessary
+ *
+ * @param bool $enabledonly return only formats that are enabled
+ * @return array array of sorted format names
+ */
+function get_sorted_course_formats($enabledonly = false) {
+    global $CFG;
+    $formats = get_plugin_list('format');
+
+    if (!empty($CFG->format_plugins_sortorder)) {
+        $order = explode(',', $CFG->format_plugins_sortorder);
+        $order = array_merge(array_intersect($order, array_keys($formats)),
+                    array_diff(array_keys($formats), $order));
+    } else {
+        $order = array_keys($formats);
+    }
+    if (!$enabledonly) {
+        return $order;
+    }
+    $sortedformats = array();
+    foreach ($order as $formatname) {
+        if (!get_config('format_'.$formatname, 'disabled')) {
+            $sortedformats[] = $formatname;
+        }
+    }
+    return $sortedformats;
+}
+
+/**
+ * The URL to use for the specified course (with section)
+ *
+ * @param int|stdClass $courseorid The course to get the section name for (either object or just course id)
+ * @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 moodle_url The url of course
+ */
+function course_get_url($courseorid, $section = null, $options = array()) {
+    return course_get_format($courseorid)->get_view_url($section, $options);
+}
diff --git a/loginas.php b/loginas.php
new file mode 100644 (file)
index 0000000..b4e5ea7
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// Allows a teacher/admin to login as another user (in stealth mode)
+
+require_once('../config.php');
+require_once('lib.php');
+
+$id = optional_param('id', SITEID, PARAM_INT);   // course id
+
+/// Reset user back to their real self if needed, for security reasons you need to log out and log in again
+if (session_is_loggedinas()) {
+    require_sesskey();
+    require_logout();
+
+    if ($id and $id != SITEID) {
+        $SESSION->wantsurl = "$CFG->wwwroot/course/view.php?id=".$id;
+    } else {
+        $SESSION->wantsurl = "$CFG->wwwroot/";
+    }
+
+    redirect(get_login_url());
+}
+
+///-------------------------------------
+/// We are trying to log in as this user in the first place
+
+$userid = required_param('user', PARAM_INT);         // login as this user
+
+$url = new moodle_url('/course/loginas.php', array('user'=>$userid, 'sesskey'=>sesskey()));
+if ($id !== SITEID) {
+    $url->param('id', $id);
+}
+$PAGE->set_url($url);
+
+require_sesskey();
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+
+/// User must be logged in
+
+$systemcontext = context_system::instance();
+$coursecontext = context_course::instance($course->id);
+
+require_login();
+
+if (has_capability('moodle/user:loginas', $systemcontext)) {
+    if (is_siteadmin($userid)) {
+        print_error('nologinas');
+    }
+    $context = $systemcontext;
+    $PAGE->set_context($context);
+} else {
+    require_login($course);
+    require_capability('moodle/user:loginas', $coursecontext);
+    if (is_siteadmin($userid)) {
+        print_error('nologinas');
+    }
+    if (!is_enrolled($coursecontext, $userid)) {
+        print_error('usernotincourse');
+    }
+    $context = $coursecontext;
+}
+
+/// Login as this user and return to course home page.
+$oldfullname = fullname($USER, true);
+session_loginas($userid, $context);
+$newfullname = fullname($USER, true);
+
+add_to_log($course->id, "course", "loginas", "../user/view.php?id=$course->id&amp;user=$userid", "$oldfullname -> $newfullname");
+
+$strloginas    = get_string('loginas');
+$strloggedinas = get_string('loggedinas', '', $newfullname);
+
+$PAGE->set_title($strloggedinas);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add($strloggedinas);
+notice($strloggedinas, "$CFG->wwwroot/course/view.php?id=$course->id");
\ No newline at end of file
diff --git a/mod.php b/mod.php
new file mode 100644 (file)
index 0000000..b1ed617
--- /dev/null
+++ b/mod.php
@@ -0,0 +1,337 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Moves, adds, updates, duplicates or deletes modules in a course
+ *
+ * @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");
+require_once("lib.php");
+
+$sectionreturn = optional_param('sr', null, PARAM_INT);
+$add           = optional_param('add', '', PARAM_ALPHA);
+$type          = optional_param('type', '', PARAM_ALPHA);
+$indent        = optional_param('indent', 0, PARAM_INT);
+$update        = optional_param('update', 0, PARAM_INT);
+$duplicate     = optional_param('duplicate', 0, PARAM_INT);
+$hide          = optional_param('hide', 0, PARAM_INT);
+$show          = optional_param('show', 0, PARAM_INT);
+$copy          = optional_param('copy', 0, PARAM_INT);
+$moveto        = optional_param('moveto', 0, PARAM_INT);
+$movetosection = optional_param('movetosection', 0, PARAM_INT);
+$delete        = optional_param('delete', 0, PARAM_INT);
+$course        = optional_param('course', 0, PARAM_INT);
+$groupmode     = optional_param('groupmode', -1, PARAM_INT);
+$cancelcopy    = optional_param('cancelcopy', 0, PARAM_BOOL);
+$confirm       = optional_param('confirm', 0, PARAM_BOOL);
+
+// This page should always redirect
+$url = new moodle_url('/course/mod.php');
+foreach (compact('indent','update','hide','show','copy','moveto','movetosection','delete','course','cancelcopy','confirm') as $key=>$value) {
+    if ($value !== 0) {
+        $url->param($key, $value);
+    }
+}
+$url->param('sr', $sectionreturn);
+if ($add !== '') {
+    $url->param('add', $add);
+}
+if ($type !== '') {
+    $url->param('type', $type);
+}
+if ($groupmode !== '') {
+    $url->param('groupmode', $groupmode);
+}
+$PAGE->set_url($url);
+
+require_login();
+
+//check if we are adding / editing a module that has new forms using formslib
+if (!empty($add)) {
+    $id          = required_param('id', PARAM_INT);
+    $section     = required_param('section', PARAM_INT);
+    $type        = optional_param('type', '', PARAM_ALPHA);
+    $returntomod = optional_param('return', 0, PARAM_BOOL);
+
+    redirect("$CFG->wwwroot/course/modedit.php?add=$add&type=$type&course=$id&section=$section&return=$returntomod&sr=$sectionreturn");
+
+} else if (!empty($update)) {
+    $cm = get_coursemodule_from_id('', $update, 0, true, MUST_EXIST);
+    $returntomod = optional_param('return', 0, PARAM_BOOL);
+    redirect("$CFG->wwwroot/course/modedit.php?update=$update&return=$returntomod&sr=$sectionreturn");
+
+} else if (!empty($duplicate)) {
+    $cm     = get_coursemodule_from_id('', $duplicate, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $coursecontext);
+
+    if (!$confirm or !confirm_sesskey()) {
+        $PAGE->set_title(get_string('duplicate'));
+        $PAGE->set_heading($course->fullname);
+        $PAGE->navbar->add(get_string('duplicatinga', 'core', format_string($cm->name)));
+        $PAGE->set_pagelayout('incourse');
+
+        $a = new stdClass();
+        $a->modtype = get_string('modulename', $cm->modname);
+        $a->modname = format_string($cm->name);
+        $a->modid   = $cm->id;
+
+        echo $OUTPUT->header();
+        echo $OUTPUT->confirm(
+            get_string('duplicateconfirm', 'core', $a),
+            new single_button(
+                new moodle_url('/course/modduplicate.php', array(
+                    'cmid' => $cm->id, 'course' => $course->id, 'sr' => $sectionreturn)),
+                get_string('continue'),
+                'post'),
+            new single_button(
+                course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)),
+                get_string('cancel'),
+                'get')
+        );
+        echo $OUTPUT->footer();
+        die();
+    }
+
+} else if (!empty($delete)) {
+    $cm     = get_coursemodule_from_id('', $delete, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
+
+    $return = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn));
+
+    if (!$confirm or !confirm_sesskey()) {
+        $fullmodulename = get_string('modulename', $cm->modname);
+
+        $optionsyes = array('confirm'=>1, 'delete'=>$cm->id, 'sesskey'=>sesskey(), 'sr' => $sectionreturn);
+
+        $strdeletecheck = get_string('deletecheck', '', $fullmodulename);
+        $strdeletecheckfull = get_string('deletecheckfull', '', "$fullmodulename '$cm->name'");
+
+        $PAGE->set_pagetype('mod-' . $cm->modname . '-delete');
+        $PAGE->set_title($strdeletecheck);
+        $PAGE->set_heading($course->fullname);
+        $PAGE->navbar->add($strdeletecheck);
+        echo $OUTPUT->header();
+
+        // print_simple_box_start('center', '60%', '#FFAAAA', 20, 'noticebox');
+        echo $OUTPUT->box_start('noticebox');
+        $formcontinue = new single_button(new moodle_url("$CFG->wwwroot/course/mod.php", $optionsyes), get_string('yes'));
+        $formcancel = new single_button($return, get_string('no'), 'get');
+        echo $OUTPUT->confirm($strdeletecheckfull, $formcontinue, $formcancel);
+        echo $OUTPUT->box_end();
+        echo $OUTPUT->footer();
+
+        exit;
+    }
+
+    $modlib = "$CFG->dirroot/mod/$cm->modname/lib.php";
+
+    if (file_exists($modlib)) {
+        require_once($modlib);
+    } else {
+        print_error('modulemissingcode', '', '', $modlib);
+    }
+
+    $deleteinstancefunction = $cm->modname."_delete_instance";
+
+    if (!$deleteinstancefunction($cm->instance)) {
+        echo $OUTPUT->notification("Could not delete the $cm->modname (instance)");
+    }
+
+    // remove all module files in case modules forget to do that
+    $fs = get_file_storage();
+    $fs->delete_area_files($modcontext->id);
+
+    if (!delete_course_module($cm->id)) {
+        echo $OUTPUT->notification("Could not delete the $cm->modname (coursemodule)");
+    }
+    if (!delete_mod_from_section($cm->id, $cm->section)) {
+        echo $OUTPUT->notification("Could not delete the $cm->modname from that section");
+    }
+
+    // Trigger a mod_deleted event with information about this module.
+    $eventdata = new stdClass();
+    $eventdata->modulename = $cm->modname;
+    $eventdata->cmid       = $cm->id;
+    $eventdata->courseid   = $course->id;
+    $eventdata->userid     = $USER->id;
+    events_trigger('mod_deleted', $eventdata);
+
+    add_to_log($course->id, 'course', "delete mod",
+               "view.php?id=$cm->course",
+               "$cm->modname $cm->instance", $cm->id);
+
+    redirect($return);
+}
+
+
+if ((!empty($movetosection) or !empty($moveto)) and confirm_sesskey()) {
+    $cm     = get_coursemodule_from_id('', $USER->activitycopy, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
+
+    if (!empty($movetosection)) {
+        if (!$section = $DB->get_record('course_sections', array('id'=>$movetosection, 'course'=>$cm->course))) {
+            print_error('sectionnotexist');
+        }
+        $beforecm = NULL;
+
+    } else {                      // normal moveto
+        if (!$beforecm = get_coursemodule_from_id('', $moveto, $cm->course, true)) {
+            print_error('invalidcoursemodule');
+        }
+        if (!$section = $DB->get_record('course_sections', array('id'=>$beforecm->section, 'course'=>$cm->course))) {
+            print_error('sectionnotexist');
+        }
+    }
+
+    if (!ismoving($section->course)) {
+        print_error('needcopy', '', "view.php?id=$section->course");
+    }
+
+    moveto_module($cm, $section, $beforecm);
+
+    $sectionreturn = $USER->activitycopysectionreturn;
+    unset($USER->activitycopy);
+    unset($USER->activitycopycourse);
+    unset($USER->activitycopyname);
+    unset($USER->activitycopysectionreturn);
+
+    redirect(course_get_url($course, $section->section, array('sr' => $sectionreturn)));
+
+} else if (!empty($indent) and confirm_sesskey()) {
+    $id = required_param('id', PARAM_INT);
+
+    $cm     = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
+
+    $cm->indent += $indent;
+
+    if ($cm->indent < 0) {
+        $cm->indent = 0;
+    }
+
+    $DB->set_field('course_modules', 'indent', $cm->indent, array('id'=>$cm->id));
+
+    rebuild_course_cache($cm->course);
+
+    redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
+
+} else if (!empty($hide) and confirm_sesskey()) {
+    $cm     = get_coursemodule_from_id('', $hide, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:activityvisibility', $modcontext);
+
+    set_coursemodule_visible($cm->id, 0);
+
+    redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
+
+} else if (!empty($show) and confirm_sesskey()) {
+    $cm     = get_coursemodule_from_id('', $show, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:activityvisibility', $modcontext);
+
+    $section = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
+
+    $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
+
+    if ($module->visible and ($section->visible or (SITEID == $cm->course))) {
+        set_coursemodule_visible($cm->id, 1);
+    }
+
+    redirect(course_get_url($course, $section->section, array('sr' => $sectionreturn)));
+
+} else if ($groupmode > -1 and confirm_sesskey()) {
+    $id = required_param('id', PARAM_INT);
+
+    $cm     = get_coursemodule_from_id('', $id, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
+
+    set_coursemodule_groupmode($cm->id, $groupmode);
+
+    redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
+
+} else if (!empty($copy) and confirm_sesskey()) { // value = course module
+    $cm     = get_coursemodule_from_id('', $copy, 0, true, MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm);
+    $coursecontext = context_course::instance($course->id);
+    $modcontext = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $modcontext);
+
+    $section = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
+
+    $USER->activitycopy              = $copy;
+    $USER->activitycopycourse        = $cm->course;
+    $USER->activitycopyname          = $cm->name;
+    $USER->activitycopysectionreturn = $sectionreturn;
+
+    redirect(course_get_url($course, $section->section, array('sr' => $sectionreturn)));
+
+} else if (!empty($cancelcopy) and confirm_sesskey()) { // value = course module
+
+    $courseid = $USER->activitycopycourse;
+    $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+
+    $cm     = get_coursemodule_from_id('', $USER->activitycopy, 0, true, IGNORE_MISSING);
+    $sectionreturn = $USER->activitycopysectionreturn;
+    unset($USER->activitycopy);
+    unset($USER->activitycopycourse);
+    unset($USER->activitycopyname);
+    unset($USER->activitycopysectionreturn);
+    redirect(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
+} else {
+    print_error('unknowaction');
+}
+
+
diff --git a/modduplicate.php b/modduplicate.php
new file mode 100644 (file)
index 0000000..d175a87
--- /dev/null
@@ -0,0 +1,151 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Duplicates a given course module
+ *
+ * The script backups and restores a single activity as if it was imported
+ * from the same course, using the default import settings. The newly created
+ * copy of the activity is then moved right below the original one.
+ *
+ * @package    core
+ * @subpackage course
+ * @copyright  2011 David Mudrak <david@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(dirname(__FILE__)) . '/config.php');
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+require_once($CFG->libdir . '/filelib.php');
+
+$cmid           = required_param('cmid', PARAM_INT);
+$courseid       = required_param('course', PARAM_INT);
+$sectionreturn  = optional_param('sr', null, PARAM_INT);
+
+$course     = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+$cm         = get_coursemodule_from_id('', $cmid, $course->id, true, MUST_EXIST);
+$cmcontext  = context_module::instance($cm->id);
+$context    = context_course::instance($courseid);
+$section    = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
+
+require_login($course);
+require_sesskey();
+require_capability('moodle/course:manageactivities', $context);
+// Require both target import caps to be able to duplicate, see make_editing_buttons()
+require_capability('moodle/backup:backuptargetimport', $context);
+require_capability('moodle/restore:restoretargetimport', $context);
+
+$PAGE->set_title(get_string('duplicate'));
+$PAGE->set_heading($course->fullname);
+$PAGE->set_url(new moodle_url('/course/modduplicate.php', array('cmid' => $cm->id, 'courseid' => $course->id)));
+$PAGE->set_pagelayout('incourse');
+
+$output = $PAGE->get_renderer('core', 'backup');
+
+$a          = new stdClass();
+$a->modtype = get_string('modulename', $cm->modname);
+$a->modname = format_string($cm->name);
+
+if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) {
+    $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn));
+    print_error('duplicatenosupport', 'error', $url, $a);
+}
+
+// backup the activity
+
+$bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
+        backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
+
+$backupid       = $bc->get_backupid();
+$backupbasepath = $bc->get_plan()->get_basepath();
+
+$bc->execute_plan();
+
+$bc->destroy();
+
+// restore the backup immediately
+
+$rc = new restore_controller($backupid, $courseid,
+        backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id, backup::TARGET_CURRENT_ADDING);
+
+if (!$rc->execute_precheck()) {
+    $precheckresults = $rc->get_precheck_results();
+    if (is_array($precheckresults) && !empty($precheckresults['errors'])) {
+        if (empty($CFG->keeptempdirectoriesonbackup)) {
+            fulldelete($backupbasepath);
+        }
+
+        echo $output->header();
+        echo $output->precheck_notices($precheckresults);
+        $url = course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn));
+        echo $output->continue_button($url);
+        echo $output->footer();
+        die();
+    }
+}
+
+$rc->execute_plan();
+
+// now a bit hacky part follows - we try to get the cmid of the newly
+// restored copy of the module
+$newcmid = null;
+$tasks = $rc->get_plan()->get_tasks();
+foreach ($tasks as $task) {
+    if (is_subclass_of($task, 'restore_activity_task')) {
+        if ($task->get_old_contextid() == $cmcontext->id) {
+            $newcmid = $task->get_moduleid();
+            break;
+        }
+    }
+}
+
+// if we know the cmid of the new course module, let us move it
+// right below the original one. otherwise it will stay at the
+// end of the section
+if ($newcmid) {
+    $newcm = get_coursemodule_from_id('', $newcmid, $course->id, true, MUST_EXIST);
+    moveto_module($newcm, $section, $cm);
+    moveto_module($cm, $section, $newcm);
+}
+
+$rc->destroy();
+
+if (empty($CFG->keeptempdirectoriesonbackup)) {
+    fulldelete($backupbasepath);
+}
+
+echo $output->header();
+
+if ($newcmid) {
+    echo $output->confirm(
+        get_string('duplicatesuccess', 'core', $a),
+        new single_button(
+            new moodle_url('/course/modedit.php', array('update' => $newcmid, 'sr' => $sectionreturn)),
+            get_string('duplicatecontedit'),
+            'get'),
+        new single_button(
+            course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)),
+            get_string('duplicatecontcourse'),
+            'get')
+    );
+
+} else {
+    echo $output->notification(get_string('duplicatesuccess', 'core', $a), 'notifysuccess');
+    echo $output->continue_button(course_get_url($course, $cm->sectionnum, array('sr' => $sectionreturn)));
+}
+
+echo $output->footer();
diff --git a/modedit.php b/modedit.php
new file mode 100644 (file)
index 0000000..c08108a
--- /dev/null
@@ -0,0 +1,665 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+* Adds or updates modules in a course using new formslib
+*
+* @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($CFG->libdir.'/filelib.php');
+require_once($CFG->libdir.'/gradelib.php');
+require_once($CFG->libdir.'/completionlib.php');
+require_once($CFG->libdir.'/conditionlib.php');
+require_once($CFG->libdir.'/plagiarismlib.php');
+
+$add    = optional_param('add', '', PARAM_ALPHA);     // module name
+$update = optional_param('update', 0, PARAM_INT);
+$return = optional_param('return', 0, PARAM_BOOL);    //return to course/view.php if false or mod/modname/view.php if true
+$type   = optional_param('type', '', PARAM_ALPHANUM); //TODO: hopefully will be removed in 2.0
+$sectionreturn = optional_param('sr', null, PARAM_INT);
+
+$url = new moodle_url('/course/modedit.php');
+$url->param('sr', $sectionreturn);
+if (!empty($return)) {
+    $url->param('return', $return);
+}
+
+if (!empty($add)) {
+    $section = required_param('section', PARAM_INT);
+    $course  = required_param('course', PARAM_INT);
+
+    $url->param('add', $add);
+    $url->param('section', $section);
+    $url->param('course', $course);
+    $PAGE->set_url($url);
+
+    $course = $DB->get_record('course', array('id'=>$course), '*', MUST_EXIST);
+    $module = $DB->get_record('modules', array('name'=>$add), '*', MUST_EXIST);
+
+    require_login($course);
+    $context = context_course::instance($course->id);
+    require_capability('moodle/course:manageactivities', $context);
+
+    course_create_sections_if_missing($course, $section);
+    $cw = get_fast_modinfo($course)->get_section_info($section);
+
+    if (!course_allowed_module($course, $module->name)) {
+        print_error('moduledisable');
+    }
+
+    $cm = null;
+
+    $data = new stdClass();
+    $data->section          = $section;  // The section number itself - relative!!! (section column in course_sections)
+    $data->visible          = $cw->visible;
+    $data->course           = $course->id;
+    $data->module           = $module->id;
+    $data->modulename       = $module->name;
+    $data->groupmode        = $course->groupmode;
+    $data->groupingid       = $course->defaultgroupingid;
+    $data->groupmembersonly = 0;
+    $data->id               = '';
+    $data->instance         = '';
+    $data->coursemodule     = '';
+    $data->add              = $add;
+    $data->return           = 0; //must be false if this is an add, go back to course view on cancel
+    $data->sr               = $sectionreturn;
+
+    if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
+        $draftid_editor = file_get_submitted_draft_itemid('introeditor');
+        file_prepare_draft_area($draftid_editor, null, null, null, null);
+        $data->introeditor = array('text'=>'', 'format'=>FORMAT_HTML, 'itemid'=>$draftid_editor); // TODO: add better default
+    }
+
+    if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
+            and has_capability('moodle/grade:managegradingforms', $context)) {
+        require_once($CFG->dirroot.'/grade/grading/lib.php');
+
+        $data->_advancedgradingdata['methods'] = grading_manager::available_methods();
+        $areas = grading_manager::available_areas('mod_'.$module->name);
+
+        foreach ($areas as $areaname => $areatitle) {
+            $data->_advancedgradingdata['areas'][$areaname] = array(
+                'title'  => $areatitle,
+                'method' => '',
+            );
+            $formfield = 'advancedgradingmethod_'.$areaname;
+            $data->{$formfield} = '';
+        }
+    }
+
+    if (!empty($type)) { //TODO: hopefully will be removed in 2.0
+        $data->type = $type;
+    }
+
+    $sectionname = get_section_name($course, $cw);
+    $fullmodulename = get_string('modulename', $module->name);
+
+    if ($data->section && $course->format != 'site') {
+        $heading = new stdClass();
+        $heading->what = $fullmodulename;
+        $heading->to   = $sectionname;
+        $pageheading = get_string('addinganewto', 'moodle', $heading);
+    } else {
+        $pageheading = get_string('addinganew', 'moodle', $fullmodulename);
+    }
+
+} else if (!empty($update)) {
+
+    $url->param('update', $update);
+    $PAGE->set_url($url);
+
+    $cm = get_coursemodule_from_id('', $update, 0, false, MUST_EXIST);
+    $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+
+    require_login($course, false, $cm); // needed to setup proper $COURSE
+    $context = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $context);
+
+    $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
+    $data = $data = $DB->get_record($module->name, array('id'=>$cm->instance), '*', MUST_EXIST);
+    $cw = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
+
+    $data->coursemodule       = $cm->id;
+    $data->section            = $cw->section;  // The section number itself - relative!!! (section column in course_sections)
+    $data->visible            = $cm->visible; //??  $cw->visible ? $cm->visible : 0; // section hiding overrides
+    $data->cmidnumber         = $cm->idnumber;          // The cm IDnumber
+    $data->groupmode          = groups_get_activity_groupmode($cm); // locked later if forced
+    $data->groupingid         = $cm->groupingid;
+    $data->groupmembersonly   = $cm->groupmembersonly;
+    $data->course             = $course->id;
+    $data->module             = $module->id;
+    $data->modulename         = $module->name;
+    $data->instance           = $cm->instance;
+    $data->return             = $return;
+    $data->sr                 = $sectionreturn;
+    $data->update             = $update;
+    $data->completion         = $cm->completion;
+    $data->completionview     = $cm->completionview;
+    $data->completionexpected = $cm->completionexpected;
+    $data->completionusegrade = is_null($cm->completiongradeitemnumber) ? 0 : 1;
+    $data->showdescription    = $cm->showdescription;
+    if (!empty($CFG->enableavailability)) {
+        $data->availablefrom      = $cm->availablefrom;
+        $data->availableuntil     = $cm->availableuntil;
+        $data->showavailability   = $cm->showavailability;
+    }
+
+    if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
+        $draftid_editor = file_get_submitted_draft_itemid('introeditor');
+        $currentintro = file_prepare_draft_area($draftid_editor, $context->id, 'mod_'.$data->modulename, 'intro', 0, array('subdirs'=>true), $data->intro);
+        $data->introeditor = array('text'=>$currentintro, 'format'=>$data->introformat, 'itemid'=>$draftid_editor);
+    }
+
+    if (plugin_supports('mod', $data->modulename, FEATURE_ADVANCED_GRADING, false)
+            and has_capability('moodle/grade:managegradingforms', $context)) {
+        require_once($CFG->dirroot.'/grade/grading/lib.php');
+        $gradingman = get_grading_manager($context, 'mod_'.$data->modulename);
+        $data->_advancedgradingdata['methods'] = $gradingman->get_available_methods();
+        $areas = $gradingman->get_available_areas();
+
+        foreach ($areas as $areaname => $areatitle) {
+            $gradingman->set_area($areaname);
+            $method = $gradingman->get_active_method();
+            $data->_advancedgradingdata['areas'][$areaname] = array(
+                'title'  => $areatitle,
+                'method' => $method,
+            );
+            $formfield = 'advancedgradingmethod_'.$areaname;
+            $data->{$formfield} = $method;
+        }
+    }
+
+    if ($items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$data->modulename,
+                                             'iteminstance'=>$data->instance, 'courseid'=>$course->id))) {
+        // add existing outcomes
+        foreach ($items as $item) {
+            if (!empty($item->outcomeid)) {
+                $data->{'outcome_'.$item->outcomeid} = 1;
+            }
+        }
+
+        // set category if present
+        $gradecat = false;
+        foreach ($items as $item) {
+            if ($gradecat === false) {
+                $gradecat = $item->categoryid;
+                continue;
+            }
+            if ($gradecat != $item->categoryid) {
+                //mixed categories
+                $gradecat = false;
+                break;
+            }
+        }
+        if ($gradecat !== false) {
+            // do not set if mixed categories present
+            $data->gradecat = $gradecat;
+        }
+    }
+
+    $sectionname = get_section_name($course, $cw);
+    $fullmodulename = get_string('modulename', $module->name);
+
+    if ($data->section && $course->format != 'site') {
+        $heading = new stdClass();
+        $heading->what = $fullmodulename;
+        $heading->in   = $sectionname;
+        $pageheading = get_string('updatingain', 'moodle', $heading);
+    } else {
+        $pageheading = get_string('updatinga', 'moodle', $fullmodulename);
+    }
+
+} else {
+    require_login();
+    print_error('invalidaction');
+}
+
+$pagepath = 'mod-' . $module->name . '-';
+if (!empty($type)) { //TODO: hopefully will be removed in 2.0
+    $pagepath .= $type;
+} else {
+    $pagepath .= 'mod';
+}
+$PAGE->set_pagetype($pagepath);
+$PAGE->set_pagelayout('admin');
+
+$modmoodleform = "$CFG->dirroot/mod/$module->name/mod_form.php";
+if (file_exists($modmoodleform)) {
+    require_once($modmoodleform);
+} else {
+    print_error('noformdesc');
+}
+
+$modlib = "$CFG->dirroot/mod/$module->name/lib.php";
+if (file_exists($modlib)) {
+    include_once($modlib);
+} else {
+    print_error('modulemissingcode', '', '', $modlib);
+}
+
+$mformclassname = 'mod_'.$module->name.'_mod_form';
+$mform = new $mformclassname($data, $cw->section, $cm, $course);
+$mform->set_data($data);
+
+if ($mform->is_cancelled()) {
+    if ($return && !empty($cm->id)) {
+        redirect("$CFG->wwwroot/mod/$module->name/view.php?id=$cm->id");
+    } else {
+        redirect(course_get_url($course, $cw->section, array('sr' => $sectionreturn)));
+    }
+} else if ($fromform = $mform->get_data()) {
+    if (empty($fromform->coursemodule)) {
+        // Add
+        $cm = null;
+        $course = $DB->get_record('course', array('id'=>$fromform->course), '*', MUST_EXIST);
+        $fromform->instance     = '';
+        $fromform->coursemodule = '';
+    } else {
+        // Update
+        $cm = get_coursemodule_from_id('', $fromform->coursemodule, 0, false, MUST_EXIST);
+        $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+        $fromform->instance     = $cm->instance;
+        $fromform->coursemodule = $cm->id;
+    }
+
+    if (!empty($fromform->coursemodule)) {
+        $context = context_module::instance($fromform->coursemodule);
+    } else {
+        $context = context_course::instance($course->id);
+    }
+
+    $fromform->course = $course->id;
+    $fromform->modulename = clean_param($fromform->modulename, PARAM_PLUGIN);  // For safety
+
+    $addinstancefunction    = $fromform->modulename."_add_instance";
+    $updateinstancefunction = $fromform->modulename."_update_instance";
+
+    if (!isset($fromform->groupingid)) {
+        $fromform->groupingid = 0;
+    }
+
+    if (!isset($fromform->groupmembersonly)) {
+        $fromform->groupmembersonly = 0;
+    }
+
+    if (!isset($fromform->name)) { //label
+        $fromform->name = $fromform->modulename;
+    }
+
+    if (!isset($fromform->completion)) {
+        $fromform->completion = COMPLETION_DISABLED;
+    }
+    if (!isset($fromform->completionview)) {
+        $fromform->completionview = COMPLETION_VIEW_NOT_REQUIRED;
+    }
+
+    // Convert the 'use grade' checkbox into a grade-item number: 0 if
+    // checked, null if not
+    if (isset($fromform->completionusegrade) && $fromform->completionusegrade) {
+        $fromform->completiongradeitemnumber = 0;
+    } else {
+        $fromform->completiongradeitemnumber = null;
+    }
+
+    // the type of event to trigger (mod_created/mod_updated)
+    $eventname = '';
+
+    if (!empty($fromform->update)) {
+
+        if (!empty($course->groupmodeforce) or !isset($fromform->groupmode)) {
+            $fromform->groupmode = $cm->groupmode; // keep original
+        }
+
+        // update course module first
+        $cm->groupmode        = $fromform->groupmode;
+        $cm->groupingid       = $fromform->groupingid;
+        $cm->groupmembersonly = $fromform->groupmembersonly;
+
+        $completion = new completion_info($course);
+        if ($completion->is_enabled()) {
+            // Update completion settings
+            $cm->completion                = $fromform->completion;
+            $cm->completiongradeitemnumber = $fromform->completiongradeitemnumber;
+            $cm->completionview            = $fromform->completionview;
+            $cm->completionexpected        = $fromform->completionexpected;
+        }
+        if (!empty($CFG->enableavailability)) {
+            $cm->availablefrom             = $fromform->availablefrom;
+            $cm->availableuntil            = $fromform->availableuntil;
+            $cm->showavailability          = $fromform->showavailability;
+            condition_info::update_cm_from_form($cm,$fromform,true);
+        }
+        if (isset($fromform->showdescription)) {
+            $cm->showdescription = $fromform->showdescription;
+        } else {
+            $cm->showdescription = 0;
+        }
+
+        $DB->update_record('course_modules', $cm);
+
+        $modcontext = context_module::instance($fromform->coursemodule);
+
+        // update embedded links and save files
+        if (plugin_supports('mod', $fromform->modulename, FEATURE_MOD_INTRO, true)) {
+            $fromform->intro = file_save_draft_area_files($fromform->introeditor['itemid'], $modcontext->id,
+                                                          'mod_'.$fromform->modulename, 'intro', 0,
+                                                          array('subdirs'=>true), $fromform->introeditor['text']);
+            $fromform->introformat = $fromform->introeditor['format'];
+            unset($fromform->introeditor);
+        }
+
+        if (!$updateinstancefunction($fromform, $mform)) {
+            print_error('cannotupdatemod', '', course_get_url($course, $cw->section), $fromform->modulename);
+        }
+
+        // make sure visibility is set correctly (in particular in calendar)
+        if (has_capability('moodle/course:activityvisibility', $modcontext)) {
+            set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
+        }
+
+        if (isset($fromform->cmidnumber)) { //label
+            // set cm idnumber - uniqueness is already verified by form validation
+            set_coursemodule_idnumber($fromform->coursemodule, $fromform->cmidnumber);
+        }
+
+        // Now that module is fully updated, also update completion data if
+        // required (this will wipe all user completion data and recalculate it)
+        if ($completion->is_enabled() && !empty($fromform->completionunlocked)) {
+            $completion->reset_all_state($cm);
+        }
+
+        $eventname = 'mod_updated';
+
+        add_to_log($course->id, "course", "update mod",
+                   "../mod/$fromform->modulename/view.php?id=$fromform->coursemodule",
+                   "$fromform->modulename $fromform->instance");
+        add_to_log($course->id, $fromform->modulename, "update",
+                   "view.php?id=$fromform->coursemodule",
+                   "$fromform->instance", $fromform->coursemodule);
+
+    } else if (!empty($fromform->add)) {
+
+        if (!empty($course->groupmodeforce) or !isset($fromform->groupmode)) {
+            $fromform->groupmode = 0; // do not set groupmode
+        }
+
+        if (!course_allowed_module($course, $fromform->modulename)) {
+            print_error('moduledisable', '', '', $fromform->modulename);
+        }
+
+        // first add course_module record because we need the context
+        $newcm = new stdClass();
+        $newcm->course           = $course->id;
+        $newcm->module           = $fromform->module;
+        $newcm->instance         = 0; // not known yet, will be updated later (this is similar to restore code)
+        $newcm->visible          = $fromform->visible;
+        $newcm->groupmode        = $fromform->groupmode;
+        $newcm->groupingid       = $fromform->groupingid;
+        $newcm->groupmembersonly = $fromform->groupmembersonly;
+        $completion = new completion_info($course);
+        if ($completion->is_enabled()) {
+            $newcm->completion                = $fromform->completion;
+            $newcm->completiongradeitemnumber = $fromform->completiongradeitemnumber;
+            $newcm->completionview            = $fromform->completionview;
+            $newcm->completionexpected        = $fromform->completionexpected;
+        }
+        if(!empty($CFG->enableavailability)) {
+            $newcm->availablefrom             = $fromform->availablefrom;
+            $newcm->availableuntil            = $fromform->availableuntil;
+            $newcm->showavailability          = $fromform->showavailability;
+        }
+        if (isset($fromform->showdescription)) {
+            $newcm->showdescription = $fromform->showdescription;
+        } else {
+            $newcm->showdescription = 0;
+        }
+
+        if (!$fromform->coursemodule = add_course_module($newcm)) {
+            print_error('cannotaddcoursemodule');
+        }
+
+        if (plugin_supports('mod', $fromform->modulename, FEATURE_MOD_INTRO, true)) {
+            $introeditor = $fromform->introeditor;
+            unset($fromform->introeditor);
+            $fromform->intro       = $introeditor['text'];
+            $fromform->introformat = $introeditor['format'];
+        }
+
+        $returnfromfunc = $addinstancefunction($fromform, $mform);
+
+        if (!$returnfromfunc or !is_number($returnfromfunc)) {
+            // undo everything we can
+            $modcontext = context_module::instance($fromform->coursemodule);
+            delete_context(CONTEXT_MODULE, $fromform->coursemodule);
+            $DB->delete_records('course_modules', array('id'=>$fromform->coursemodule));
+
+            if (!is_number($returnfromfunc)) {
+                print_error('invalidfunction', '', course_get_url($course, $cw->section));
+            } else {
+                print_error('cannotaddnewmodule', '', course_get_url($course, $cw->section), $fromform->modulename);
+            }
+        }
+
+        $fromform->instance = $returnfromfunc;
+
+        $DB->set_field('course_modules', 'instance', $returnfromfunc, array('id'=>$fromform->coursemodule));
+
+        // update embedded links and save files
+        $modcontext = context_module::instance($fromform->coursemodule);
+        if (!empty($introeditor)) {
+            $fromform->intro = file_save_draft_area_files($introeditor['itemid'], $modcontext->id,
+                                                          'mod_'.$fromform->modulename, 'intro', 0,
+                                                          array('subdirs'=>true), $introeditor['text']);
+            $DB->set_field($fromform->modulename, 'intro', $fromform->intro, array('id'=>$fromform->instance));
+        }
+
+        // course_modules and course_sections each contain a reference
+        // to each other, so we have to update one of them twice.
+        $sectionid = course_add_cm_to_section($course, $fromform->coursemodule, $fromform->section);
+
+        // make sure visibility is set correctly (in particular in calendar)
+        // note: allow them to set it even without moodle/course:activityvisibility
+        set_coursemodule_visible($fromform->coursemodule, $fromform->visible);
+        $DB->set_field('course_modules', 'visibleold', 1, array('id' => $fromform->coursemodule));
+
+        if (isset($fromform->cmidnumber)) { //label
+            // set cm idnumber - uniqueness is already verified by form validation
+            set_coursemodule_idnumber($fromform->coursemodule, $fromform->cmidnumber);
+        }
+
+        // Set up conditions
+        if ($CFG->enableavailability) {
+            condition_info::update_cm_from_form((object)array('id'=>$fromform->coursemodule), $fromform, false);
+        }
+
+        $eventname = 'mod_created';
+
+        add_to_log($course->id, "course", "add mod",
+                   "../mod/$fromform->modulename/view.php?id=$fromform->coursemodule",
+                   "$fromform->modulename $fromform->instance");
+        add_to_log($course->id, $fromform->modulename, "add",
+                   "view.php?id=$fromform->coursemodule",
+                   "$fromform->instance", $fromform->coursemodule);
+    } else {
+        print_error('invaliddata');
+    }
+
+    // Trigger mod_created/mod_updated event with information about this module.
+    $eventdata = new stdClass();
+    $eventdata->modulename = $fromform->modulename;
+    $eventdata->name       = $fromform->name;
+    $eventdata->cmid       = $fromform->coursemodule;
+    $eventdata->courseid   = $course->id;
+    $eventdata->userid     = $USER->id;
+    events_trigger($eventname, $eventdata);
+
+    // sync idnumber with grade_item
+    if ($grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$fromform->modulename,
+                 'iteminstance'=>$fromform->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
+        if ($grade_item->idnumber != $fromform->cmidnumber) {
+            $grade_item->idnumber = $fromform->cmidnumber;
+            $grade_item->update();
+        }
+    }
+
+    $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$fromform->modulename,
+                                         'iteminstance'=>$fromform->instance, 'courseid'=>$course->id));
+
+    // create parent category if requested and move to correct parent category
+    if ($items and isset($fromform->gradecat)) {
+        if ($fromform->gradecat == -1) {
+            $grade_category = new grade_category();
+            $grade_category->courseid = $course->id;
+            $grade_category->fullname = $fromform->name;
+            $grade_category->insert();
+            if ($grade_item) {
+                $parent = $grade_item->get_parent_category();
+                $grade_category->set_parent($parent->id);
+            }
+            $fromform->gradecat = $grade_category->id;
+        }
+        foreach ($items as $itemid=>$unused) {
+            $items[$itemid]->set_parent($fromform->gradecat);
+            if ($itemid == $grade_item->id) {
+                // use updated grade_item
+                $grade_item = $items[$itemid];
+            }
+        }
+    }
+
+    // add outcomes if requested
+    if ($outcomes = grade_outcome::fetch_all_available($course->id)) {
+        $grade_items = array();
+
+        // Outcome grade_item.itemnumber start at 1000, there is nothing above outcomes
+        $max_itemnumber = 999;
+        if ($items) {
+            foreach($items as $item) {
+                if ($item->itemnumber > $max_itemnumber) {
+                    $max_itemnumber = $item->itemnumber;
+                }
+            }
+        }
+
+        foreach($outcomes as $outcome) {
+            $elname = 'outcome_'.$outcome->id;
+
+            if (property_exists($fromform, $elname) and $fromform->$elname) {
+                // so we have a request for new outcome grade item?
+                if ($items) {
+                    foreach($items as $item) {
+                        if ($item->outcomeid == $outcome->id) {
+                            //outcome aready exists
+                            continue 2;
+                        }
+                    }
+                }
+
+                $max_itemnumber++;
+
+                $outcome_item = new grade_item();
+                $outcome_item->courseid     = $course->id;
+                $outcome_item->itemtype     = 'mod';
+                $outcome_item->itemmodule   = $fromform->modulename;
+                $outcome_item->iteminstance = $fromform->instance;
+                $outcome_item->itemnumber   = $max_itemnumber;
+                $outcome_item->itemname     = $outcome->fullname;
+                $outcome_item->outcomeid    = $outcome->id;
+                $outcome_item->gradetype    = GRADE_TYPE_SCALE;
+                $outcome_item->scaleid      = $outcome->scaleid;
+                $outcome_item->insert();
+
+                // move the new outcome into correct category and fix sortorder if needed
+                if ($grade_item) {
+                    $outcome_item->set_parent($grade_item->categoryid);
+                    $outcome_item->move_after_sortorder($grade_item->sortorder);
+
+                } else if (isset($fromform->gradecat)) {
+                    $outcome_item->set_parent($fromform->gradecat);
+                }
+            }
+        }
+    }
+
+    if (plugin_supports('mod', $fromform->modulename, FEATURE_ADVANCED_GRADING, false)
+            and has_capability('moodle/grade:managegradingforms', $modcontext)) {
+        require_once($CFG->dirroot.'/grade/grading/lib.php');
+        $gradingman = get_grading_manager($modcontext, 'mod_'.$fromform->modulename);
+        $showgradingmanagement = false;
+        foreach ($gradingman->get_available_areas() as $areaname => $aretitle) {
+            $formfield = 'advancedgradingmethod_'.$areaname;
+            if (isset($fromform->{$formfield})) {
+                $gradingman->set_area($areaname);
+                $methodchanged = $gradingman->set_active_method($fromform->{$formfield});
+                if (empty($fromform->{$formfield})) {
+                    // going back to the simple direct grading is not a reason
+                    // to open the management screen
+                    $methodchanged = false;
+                }
+                $showgradingmanagement = $showgradingmanagement || $methodchanged;
+            }
+        }
+    }
+
+    rebuild_course_cache($course->id);
+    grade_regrade_final_grades($course->id);
+    plagiarism_save_form_elements($fromform); //save plagiarism settings
+
+    if (isset($fromform->submitbutton)) {
+        if (empty($showgradingmanagement)) {
+            redirect("$CFG->wwwroot/mod/$module->name/view.php?id=$fromform->coursemodule");
+        } else {
+            $returnurl = new moodle_url("/mod/$module->name/view.php", array('id' => $fromform->coursemodule));
+            redirect($gradingman->get_management_url($returnurl));
+        }
+    } else {
+        redirect(course_get_url($course, $cw->section, array('sr' => $sectionreturn)));
+    }
+    exit;
+
+} else {
+
+    $streditinga = get_string('editinga', 'moodle', $fullmodulename);
+    $strmodulenameplural = get_string('modulenameplural', $module->name);
+
+    if (!empty($cm->id)) {
+        $context = context_module::instance($cm->id);
+    } else {
+        $context = context_course::instance($course->id);
+    }
+
+    $PAGE->set_heading($course->fullname);
+    $PAGE->set_title($streditinga);
+    $PAGE->set_cacheable(false);
+    echo $OUTPUT->header();
+
+    if (get_string_manager()->string_exists('modulename_help', $module->name)) {
+        echo $OUTPUT->heading_with_help($pageheading, 'modulename', $module->name, 'icon');
+    } else {
+        echo $OUTPUT->heading_with_help($pageheading, '', $module->name, 'icon');
+    }
+
+    $mform->display();
+
+    echo $OUTPUT->footer();
+}
diff --git a/moodleform_mod.php b/moodleform_mod.php
new file mode 100644 (file)
index 0000000..b4feb9d
--- /dev/null
@@ -0,0 +1,862 @@
+<?php
+require_once ($CFG->libdir.'/formslib.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+/**
+ * This class adds extra methods to form wrapper specific to be used for module
+ * add / update forms mod/{modname}/mod_form.php replaced deprecated mod/{modname}/mod.html
+ */
+abstract class moodleform_mod extends moodleform {
+    /** Current data */
+    protected $current;
+    /**
+     * Instance of the module that is being updated. This is the id of the {prefix}{modulename}
+     * record. Can be used in form definition. Will be "" if this is an 'add' form and not an
+     * update one.
+     *
+     * @var mixed
+     */
+    protected $_instance;
+    /**
+     * Section of course that module instance will be put in or is in.
+     * This is always the section number itself (column 'section' from 'course_sections' table).
+     *
+     * @var mixed
+     */
+    protected $_section;
+    /**
+     * Course module record of the module that is being updated. Will be null if this is an 'add' form and not an
+     * update one.
+      *
+     * @var mixed
+     */
+    protected $_cm;
+    /**
+     * List of modform features
+     */
+    protected $_features;
+    /**
+     * @var array Custom completion-rule elements, if enabled
+     */
+    protected $_customcompletionelements;
+    /**
+     * @var string name of module
+     */
+    protected $_modname;
+    /** current context, course or module depends if already exists*/
+    protected $context;
+
+    /** a flag indicating whether outcomes are being used*/
+    protected $_outcomesused;
+
+    function moodleform_mod($current, $section, $cm, $course) {
+        $this->current   = $current;
+        $this->_instance = $current->instance;
+        $this->_section  = $section;
+        $this->_cm       = $cm;
+        if ($this->_cm) {
+            $this->context = context_module::instance($this->_cm->id);
+        } else {
+            $this->context = context_course::instance($course->id);
+        }
+
+        // Guess module name
+        $matches = array();
+        if (!preg_match('/^mod_([^_]+)_mod_form$/', get_class($this), $matches)) {
+            debugging('Use $modname parameter or rename form to mod_xx_mod_form, where xx is name of your module');
+            print_error('unknownmodulename');
+        }
+        $this->_modname = $matches[1];
+        $this->init_features();
+        parent::moodleform('modedit.php');
+    }
+
+    protected function init_features() {
+        global $CFG;
+
+        $this->_features = new stdClass();
+        $this->_features->groups            = plugin_supports('mod', $this->_modname, FEATURE_GROUPS, true);
+        $this->_features->groupings         = plugin_supports('mod', $this->_modname, FEATURE_GROUPINGS, false);
+        $this->_features->groupmembersonly  = (!empty($CFG->enablegroupmembersonly) and plugin_supports('mod', $this->_modname, FEATURE_GROUPMEMBERSONLY, false));
+        $this->_features->outcomes          = (!empty($CFG->enableoutcomes) and plugin_supports('mod', $this->_modname, FEATURE_GRADE_OUTCOMES, true));
+        $this->_features->hasgrades         = plugin_supports('mod', $this->_modname, FEATURE_GRADE_HAS_GRADE, false);
+        $this->_features->idnumber          = plugin_supports('mod', $this->_modname, FEATURE_IDNUMBER, true);
+        $this->_features->introeditor       = plugin_supports('mod', $this->_modname, FEATURE_MOD_INTRO, true);
+        $this->_features->defaultcompletion = plugin_supports('mod', $this->_modname, FEATURE_MODEDIT_DEFAULT_COMPLETION, true);
+        $this->_features->rating            = plugin_supports('mod', $this->_modname, FEATURE_RATE, false);
+        $this->_features->showdescription   = plugin_supports('mod', $this->_modname, FEATURE_SHOW_DESCRIPTION, false);
+
+        $this->_features->gradecat          = ($this->_features->outcomes or $this->_features->hasgrades);
+        $this->_features->advancedgrading   = plugin_supports('mod', $this->_modname, FEATURE_ADVANCED_GRADING, false);
+    }
+
+    /**
+     * Only available on moodleform_mod.
+     *
+     * @param array $default_values passed by reference
+     */
+    function data_preprocessing(&$default_values){
+        if (empty($default_values['scale'])) {
+            $default_values['assessed'] = 0;
+        }
+
+        if (empty($default_values['assessed'])){
+            $default_values['ratingtime'] = 0;
+        } else {
+            $default_values['ratingtime']=
+                ($default_values['assesstimestart'] && $default_values['assesstimefinish']) ? 1 : 0;
+        }
+    }
+
+    /**
+     * Each module which defines definition_after_data() must call this method using parent::definition_after_data();
+     */
+    function definition_after_data() {
+        global $CFG, $COURSE;
+        $mform =& $this->_form;
+
+        if ($id = $mform->getElementValue('update')) {
+            $modulename = $mform->getElementValue('modulename');
+            $instance   = $mform->getElementValue('instance');
+
+            if ($this->_features->gradecat) {
+                $gradecat = false;
+                if (!empty($CFG->enableoutcomes) and $this->_features->outcomes) {
+                    $outcomes = grade_outcome::fetch_all_available($COURSE->id);
+                    if (!empty($outcomes)) {
+                        $gradecat = true;
+                    }
+                }
+
+                $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$modulename,'iteminstance'=>$instance, 'courseid'=>$COURSE->id));
+                //will be no items if, for example, this activity supports ratings but rating aggregate type == no ratings
+                if (!empty($items)) {
+                    foreach ($items as $item) {
+                        if (!empty($item->outcomeid)) {
+                            $elname = 'outcome_'.$item->outcomeid;
+                            if ($mform->elementExists($elname)) {
+                                $mform->hardFreeze($elname); // prevent removing of existing outcomes
+                            }
+                        }
+                    }
+
+                    foreach ($items as $item) {
+                        if (is_bool($gradecat)) {
+                            $gradecat = $item->categoryid;
+                            continue;
+                        }
+                        if ($gradecat != $item->categoryid) {
+                            //mixed categories
+                            $gradecat = false;
+                            break;
+                        }
+                    }
+                }
+
+                if ($gradecat === false) {
+                    // items and outcomes in different categories - remove the option
+                    // TODO: add a "Mixed categories" text instead of removing elements with no explanation
+                    if ($mform->elementExists('gradecat')) {
+                        $mform->removeElement('gradecat');
+                        if ($this->_features->rating) {
+                            //if supports ratings then the max grade dropdown wasnt added so the grade box can be removed entirely
+                            $mform->removeElement('modstandardgrade');
+                        }
+                    }
+                }
+            }
+        }
+
+        if ($COURSE->groupmodeforce) {
+            if ($mform->elementExists('groupmode')) {
+                $mform->hardFreeze('groupmode'); // groupmode can not be changed if forced from course settings
+            }
+        }
+
+        // Don't disable/remove groupingid if it is currently set to something,
+        // otherwise you cannot turn it off at same time as turning off other
+        // option (MDL-30764)
+        if (empty($this->_cm) || !$this->_cm->groupingid) {
+            if ($mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly') and empty($COURSE->groupmodeforce)) {
+                $mform->disabledIf('groupingid', 'groupmode', 'eq', NOGROUPS);
+
+            } else if (!$mform->elementExists('groupmode') and $mform->elementExists('groupmembersonly')) {
+                $mform->disabledIf('groupingid', 'groupmembersonly', 'notchecked');
+
+            } else if (!$mform->elementExists('groupmode') and !$mform->elementExists('groupmembersonly')) {
+                // groupings have no use without groupmode or groupmembersonly
+                if ($mform->elementExists('groupingid')) {
+                    $mform->removeElement('groupingid');
+                }
+            }
+        }
+
+        // Completion: If necessary, freeze fields
+        $completion = new completion_info($COURSE);
+        if ($completion->is_enabled()) {
+            // If anybody has completed the activity, these options will be 'locked'
+            $completedcount = empty($this->_cm)
+                ? 0
+                : $completion->count_user_data($this->_cm);
+
+            $freeze = false;
+            if (!$completedcount) {
+                if ($mform->elementExists('unlockcompletion')) {
+                    $mform->removeElement('unlockcompletion');
+                }
+                // Automatically set to unlocked (note: this is necessary
+                // in order to make it recalculate completion once the option
+                // is changed, maybe someone has completed it now)
+                $mform->getElement('completionunlocked')->setValue(1);
+            } else {
+                // Has the element been unlocked?
+                if ($mform->exportValue('unlockcompletion')) {
+                    // Yes, add in warning text and set the hidden variable
+                    $mform->insertElementBefore(
+                        $mform->createElement('static', 'completedunlocked',
+                            get_string('completedunlocked', 'completion'),
+                            get_string('completedunlockedtext', 'completion')),
+                        'unlockcompletion');
+                    $mform->removeElement('unlockcompletion');
+                    $mform->getElement('completionunlocked')->setValue(1);
+                } else {
+                    // No, add in the warning text with the count (now we know
+                    // it) before the unlock button
+                    $mform->insertElementBefore(
+                        $mform->createElement('static', 'completedwarning',
+                            get_string('completedwarning', 'completion'),
+                            get_string('completedwarningtext', 'completion', $completedcount)),
+                        'unlockcompletion');
+                    $freeze = true;
+                }
+            }
+
+            if ($freeze) {
+                $mform->freeze('completion');
+                if ($mform->elementExists('completionview')) {
+                    $mform->freeze('completionview'); // don't use hardFreeze or checkbox value gets lost
+                }
+                if ($mform->elementExists('completionusegrade')) {
+                    $mform->freeze('completionusegrade');
+                }
+                $mform->freeze($this->_customcompletionelements);
+            }
+        }
+
+        // Availability conditions
+        if (!empty($CFG->enableavailability) && $this->_cm) {
+            $ci = new condition_info($this->_cm);
+            $fullcm=$ci->get_full_course_module();
+
+            $num=0;
+            foreach($fullcm->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($fullcm->conditionsfield as $field => $details) {
+                $groupelements = $mform->getElement('conditionfieldgroup['.$num.']')->getElements();
+                $groupelements[0]->setValue($field);
+                $groupelements[1]->setValue(is_null($details->operator) ? '' : $details->operator);
+                $groupelements[2]->setValue(is_null($details->value) ? '' : $details->value);
+                $num++;
+            }
+
+            if ($completion->is_enabled()) {
+                $num=0;
+                foreach($fullcm->conditionscompletion as $othercmid=>$state) {
+                    $groupelements=$mform->getElement('conditioncompletiongroup['.$num.']')->getElements();
+                    $groupelements[0]->setValue($othercmid);
+                    $groupelements[1]->setValue($state);
+                    $num++;
+                }
+            }
+        }
+    }
+
+    // form verification
+    function validation($data, $files) {
+        global $COURSE, $DB;
+        $errors = parent::validation($data, $files);
+
+        $mform =& $this->_form;
+
+        $errors = array();
+
+        if ($mform->elementExists('name')) {
+            $name = trim($data['name']);
+            if ($name == '') {
+                $errors['name'] = get_string('required');
+            }
+        }
+
+        $grade_item = grade_item::fetch(array('itemtype'=>'mod', 'itemmodule'=>$data['modulename'],
+                     'iteminstance'=>$data['instance'], 'itemnumber'=>0, 'courseid'=>$COURSE->id));
+        if ($data['coursemodule']) {
+            $cm = $DB->get_record('course_modules', array('id'=>$data['coursemodule']));
+        } else {
+            $cm = null;
+        }
+
+        if ($mform->elementExists('cmidnumber')) {
+            // verify the idnumber
+            if (!grade_verify_idnumber($data['cmidnumber'], $COURSE->id, $grade_item, $cm)) {
+                $errors['cmidnumber'] = get_string('idnumbertaken');
+            }
+        }
+
+        // Completion: Don't let them choose automatic completion without turning
+        // on some conditions
+        if (array_key_exists('completion', $data) && $data['completion']==COMPLETION_TRACKING_AUTOMATIC) {
+            if (empty($data['completionview']) && empty($data['completionusegrade']) &&
+                !$this->completion_rule_enabled($data)) {
+                $errors['completion'] = get_string('badautocompletion', 'completion');
+            }
+        }
+
+        // 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. Usually new entry defaults are stored directly in
+     * form definition (new entry form); this function is used to load in data where values
+     * already exist and data is being edited (edit entry form).
+     *
+     * @param mixed $default_values object or array of default values
+     */
+    function set_data($default_values) {
+        if (is_object($default_values)) {
+            $default_values = (array)$default_values;
+        }
+
+        $this->data_preprocessing($default_values);
+        parent::set_data($default_values);
+    }
+
+    /**
+     * Adds all the standard elements to a form to edit the settings for an activity module.
+     */
+    function standard_coursemodule_elements(){
+        global $COURSE, $CFG, $DB;
+        $mform =& $this->_form;
+
+        $this->_outcomesused = false;
+        if ($this->_features->outcomes) {
+            if ($outcomes = grade_outcome::fetch_all_available($COURSE->id)) {
+                $this->_outcomesused = true;
+                $mform->addElement('header', 'modoutcomes', get_string('outcomes', 'grades'));
+                foreach($outcomes as $outcome) {
+                    $mform->addElement('advcheckbox', 'outcome_'.$outcome->id, $outcome->get_name());
+                }
+            }
+        }
+
+
+        if ($this->_features->rating) {
+            require_once($CFG->dirroot.'/rating/lib.php');
+            $rm = new rating_manager();
+
+            $mform->addElement('header', 'modstandardratings', get_string('ratings', 'rating'));
+
+            $permission=CAP_ALLOW;
+            $rolenamestring = null;
+            if (!empty($this->_cm)) {
+                $context = context_module::instance($this->_cm->id);
+
+                $rolenames = get_role_names_with_caps_in_context($context, array('moodle/rating:rate', 'mod/'.$this->_cm->modname.':rate'));
+                $rolenamestring = implode(', ', $rolenames);
+            } else {
+                $rolenamestring = get_string('capabilitychecknotavailable','rating');
+            }
+            $mform->addElement('static', 'rolewarning', get_string('rolewarning','rating'), $rolenamestring);
+            $mform->addHelpButton('rolewarning', 'rolewarning', 'rating');
+
+            $mform->addElement('select', 'assessed', get_string('aggregatetype', 'rating') , $rm->get_aggregate_types());
+            $mform->setDefault('assessed', 0);
+            $mform->addHelpButton('assessed', 'aggregatetype', 'rating');
+
+            $mform->addElement('modgrade', 'scale', get_string('scale'), false);
+            $mform->disabledIf('scale', 'assessed', 'eq', 0);
+
+            $mform->addElement('checkbox', 'ratingtime', get_string('ratingtime', 'rating'));
+            $mform->disabledIf('ratingtime', 'assessed', 'eq', 0);
+
+            $mform->addElement('date_time_selector', 'assesstimestart', get_string('from'));
+            $mform->disabledIf('assesstimestart', 'assessed', 'eq', 0);
+            $mform->disabledIf('assesstimestart', 'ratingtime');
+
+            $mform->addElement('date_time_selector', 'assesstimefinish', get_string('to'));
+            $mform->disabledIf('assesstimefinish', 'assessed', 'eq', 0);
+            $mform->disabledIf('assesstimefinish', 'ratingtime');
+        }
+
+        //doing this here means splitting up the grade related settings on the lesson settings page
+        //$this->standard_grading_coursemodule_elements();
+
+        $mform->addElement('header', 'modstandardelshdr', get_string('modstandardels', 'form'));
+        if ($this->_features->groups) {
+            $options = array(NOGROUPS       => get_string('groupsnone'),
+                             SEPARATEGROUPS => get_string('groupsseparate'),
+                             VISIBLEGROUPS  => get_string('groupsvisible'));
+            $mform->addElement('select', 'groupmode', get_string('groupmode', 'group'), $options, NOGROUPS);
+            $mform->addHelpButton('groupmode', 'groupmode', 'group');
+        }
+
+        if ($this->_features->groupings or $this->_features->groupmembersonly) {
+            //groupings selector - used for normal grouping mode or also when restricting access with groupmembersonly
+            $options = array();
+            $options[0] = get_string('none');
+            if ($groupings = $DB->get_records('groupings', array('courseid'=>$COURSE->id))) {
+                foreach ($groupings as $grouping) {
+                    $options[$grouping->id] = format_string($grouping->name);
+                }
+            }
+            $mform->addElement('select', 'groupingid', get_string('grouping', 'group'), $options);
+            $mform->addHelpButton('groupingid', 'grouping', 'group');
+            $mform->setAdvanced('groupingid');
+        }
+
+        if ($this->_features->groupmembersonly) {
+            $mform->addElement('checkbox', 'groupmembersonly', get_string('groupmembersonly', 'group'));
+            $mform->addHelpButton('groupmembersonly', 'groupmembersonly', 'group');
+            $mform->setAdvanced('groupmembersonly');
+        }
+
+        $mform->addElement('modvisible', 'visible', get_string('visible'));
+        if (!empty($this->_cm)) {
+            $context = context_module::instance($this->_cm->id);
+            if (!has_capability('moodle/course:activityvisibility', $context)) {
+                $mform->hardFreeze('visible');
+            }
+        }
+
+        if ($this->_features->idnumber) {
+            $mform->addElement('text', 'cmidnumber', get_string('idnumbermod'));
+            $mform->addHelpButton('cmidnumber', 'idnumbermod');
+        }
+
+        if (!empty($CFG->enableavailability)) {
+            // String used by conditions
+            $strnone = get_string('none','condition');
+            // Conditional availability
+
+            // 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']);
+
+            // From/until controls
+            $mform->addElement('header', 'availabilityconditionsheader',
+                    get_string('availabilityconditions', 'condition'));
+            $mform->addElement('date_time_selector', 'availablefrom',
+                    get_string('availablefrom', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
+            $mform->addHelpButton('availablefrom', 'availablefrom', 'condition');
+            $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) {
+                // Do not include grades for current item
+                if (!empty($this->_cm) && $this->_cm->instance == $item->iteminstance
+                    && $this->_cm->modname == $item->itemmodule
+                    && $item->itemtype == 'mod') {
+                    continue;
+                }
+                $gradeoptions[$id] = $item->get_name();
+            }
+            asort($gradeoptions);
+            $gradeoptions = array(0 => $strnone) + $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 version with condition info and store it so we don't ask
+            // twice
+            if(!empty($this->_cm)) {
+                $ci = new condition_info($this->_cm, CONDITION_MISSING_EXTRATABLE);
+                $this->_cm = $ci->get_full_course_module();
+                $count = count($this->_cm->conditionsgrade)+1;
+                $fieldcount = count($this->_cm->conditionsfield) + 1;
+            } else {
+                $count = 1;
+                $fieldcount = 1;
+            }
+
+            $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 => $strnone) + $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);
+
+            $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) is not the same as current course-module
+                    if ($cm->completion && (empty($this->_cm) || $this->_cm->id != $id)) {
+                        $completionoptions[$id]=$cm->name;
+                    }
+                }
+                asort($completionoptions);
+                $completionoptions = array(0 => $strnone) + $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 = empty($this->_cm) ? 1 : count($this->_cm->conditionscompletion)+1;
+                $this->repeat_elements(array($group),$count,array(),
+                    'conditioncompletionrepeats','conditioncompletionadds',2,
+                    get_string('addcompletions','condition'),true);
+                $mform->addHelpButton('conditioncompletiongroup[0]', 'completioncondition', 'condition');
+            }
+
+            // Do we display availability info to students?
+            $mform->addElement('select', 'showavailability', get_string('showavailability', 'condition'),
+                    array(CONDITION_STUDENTVIEW_SHOW=>get_string('showavailability_show', 'condition'),
+                    CONDITION_STUDENTVIEW_HIDE=>get_string('showavailability_hide', 'condition')));
+            $mform->setDefault('showavailability', CONDITION_STUDENTVIEW_SHOW);
+        }
+
+        // Conditional activities: completion tracking section
+        if(!isset($completion)) {
+            $completion = new completion_info($COURSE);
+        }
+        if ($completion->is_enabled()) {
+            $mform->addElement('header', 'activitycompletionheader', get_string('activitycompletion', 'completion'));
+
+            // Unlock button for if people have completed it (will
+            // be removed in definition_after_data if they haven't)
+            $mform->addElement('submit', 'unlockcompletion', get_string('unlockcompletion', 'completion'));
+            $mform->registerNoSubmitButton('unlockcompletion');
+            $mform->addElement('hidden', 'completionunlocked', 0);
+            $mform->setType('completionunlocked', PARAM_INT);
+
+            $mform->addElement('select', 'completion', get_string('completion', 'completion'),
+                array(COMPLETION_TRACKING_NONE=>get_string('completion_none', 'completion'),
+                COMPLETION_TRACKING_MANUAL=>get_string('completion_manual', 'completion')));
+            $mform->setDefault('completion', $this->_features->defaultcompletion
+                ? COMPLETION_TRACKING_MANUAL
+                : COMPLETION_TRACKING_NONE);
+            $mform->addHelpButton('completion', 'completion', 'completion');
+
+            // Automatic completion once you view it
+            $gotcompletionoptions = false;
+            if (plugin_supports('mod', $this->_modname, FEATURE_COMPLETION_TRACKS_VIEWS, false)) {
+                $mform->addElement('checkbox', 'completionview', get_string('completionview', 'completion'),
+                    get_string('completionview_desc', 'completion'));
+                $mform->disabledIf('completionview', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
+                $gotcompletionoptions = true;
+            }
+
+            // Automatic completion once it's graded
+            if (plugin_supports('mod', $this->_modname, FEATURE_GRADE_HAS_GRADE, false)) {
+                $mform->addElement('checkbox', 'completionusegrade', get_string('completionusegrade', 'completion'),
+                    get_string('completionusegrade_desc', 'completion'));
+                $mform->disabledIf('completionusegrade', 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
+                $mform->addHelpButton('completionusegrade', 'completionusegrade', 'completion');
+                $gotcompletionoptions = true;
+            }
+
+            // Automatic completion according to module-specific rules
+            $this->_customcompletionelements = $this->add_completion_rules();
+            foreach ($this->_customcompletionelements as $element) {
+                $mform->disabledIf($element, 'completion', 'ne', COMPLETION_TRACKING_AUTOMATIC);
+            }
+
+            $gotcompletionoptions = $gotcompletionoptions ||
+                count($this->_customcompletionelements)>0;
+
+            // Automatic option only appears if possible
+            if ($gotcompletionoptions) {
+                $mform->getElement('completion')->addOption(
+                    get_string('completion_automatic', 'completion'),
+                    COMPLETION_TRACKING_AUTOMATIC);
+            }
+
+            // Completion expected at particular date? (For progress tracking)
+            $mform->addElement('date_selector', 'completionexpected', get_string('completionexpected', 'completion'), array('optional'=>true));
+            $mform->addHelpButton('completionexpected', 'completionexpected', 'completion');
+            $mform->disabledIf('completionexpected', 'completion', 'eq', COMPLETION_TRACKING_NONE);
+        }
+
+        $this->standard_hidden_coursemodule_elements();
+    }
+
+    /**
+     * Can be overridden to add custom completion rules if the module wishes
+     * them. If overriding this, you should also override completion_rule_enabled.
+     * <p>
+     * Just add elements to the form as needed and return the list of IDs. The
+     * system will call disabledIf and handle other behaviour for each returned
+     * ID.
+     * @return array Array of string IDs of added items, empty array if none
+     */
+    function add_completion_rules() {
+        return array();
+    }
+
+    /**
+     * Called during validation. Override to indicate, based on the data, whether
+     * a custom completion rule is enabled (selected).
+     *
+     * @param array $data Input data (not yet validated)
+     * @return bool True if one or more rules is enabled, false if none are;
+     *   default returns false
+     */
+    function completion_rule_enabled($data) {
+        return false;
+    }
+
+    function standard_hidden_coursemodule_elements(){
+        $mform =& $this->_form;
+        $mform->addElement('hidden', 'course', 0);
+        $mform->setType('course', PARAM_INT);
+
+        $mform->addElement('hidden', 'coursemodule', 0);
+        $mform->setType('coursemodule', PARAM_INT);
+
+        $mform->addElement('hidden', 'section', 0);
+        $mform->setType('section', PARAM_INT);
+
+        $mform->addElement('hidden', 'module', 0);
+        $mform->setType('module', PARAM_INT);
+
+        $mform->addElement('hidden', 'modulename', '');
+        $mform->setType('modulename', PARAM_PLUGIN);
+
+        $mform->addElement('hidden', 'instance', 0);
+        $mform->setType('instance', PARAM_INT);
+
+        $mform->addElement('hidden', 'add', 0);
+        $mform->setType('add', PARAM_ALPHA);
+
+        $mform->addElement('hidden', 'update', 0);
+        $mform->setType('update', PARAM_INT);
+
+        $mform->addElement('hidden', 'return', 0);
+        $mform->setType('return', PARAM_BOOL);
+
+        $mform->addElement('hidden', 'sr', 0);
+        $mform->setType('sr', PARAM_INT);
+    }
+
+    public function standard_grading_coursemodule_elements() {
+        global $COURSE, $CFG;
+        $mform =& $this->_form;
+
+        if ($this->_features->hasgrades) {
+
+            if (!$this->_features->rating || $this->_features->gradecat) {
+                $mform->addElement('header', 'modstandardgrade', get_string('grade'));
+            }
+
+            //if supports grades and grades arent being handled via ratings
+            if (!$this->_features->rating) {
+                $mform->addElement('modgrade', 'grade', get_string('grade'));
+                $mform->setDefault('grade', 100);
+            }
+
+            if ($this->_features->advancedgrading
+                    and !empty($this->current->_advancedgradingdata['methods'])
+                    and !empty($this->current->_advancedgradingdata['areas'])) {
+
+                if (count($this->current->_advancedgradingdata['areas']) == 1) {
+                    // if there is just one gradable area (most cases), display just the selector
+                    // without its name to make UI simplier
+                    $areadata = reset($this->current->_advancedgradingdata['areas']);
+                    $areaname = key($this->current->_advancedgradingdata['areas']);
+                    $mform->addElement('select', 'advancedgradingmethod_'.$areaname,
+                        get_string('gradingmethod', 'core_grading'), $this->current->_advancedgradingdata['methods']);
+                    $mform->addHelpButton('advancedgradingmethod_'.$areaname, 'gradingmethod', 'core_grading');
+
+                } else {
+                    // the module defines multiple gradable areas, display a selector
+                    // for each of them together with a name of the area
+                    $areasgroup = array();
+                    foreach ($this->current->_advancedgradingdata['areas'] as $areaname => $areadata) {
+                        $areasgroup[] = $mform->createElement('select', 'advancedgradingmethod_'.$areaname,
+                            $areadata['title'], $this->current->_advancedgradingdata['methods']);
+                        $areasgroup[] = $mform->createElement('static', 'advancedgradingareaname_'.$areaname, '', $areadata['title']);
+                    }
+                    $mform->addGroup($areasgroup, 'advancedgradingmethodsgroup', get_string('gradingmethods', 'core_grading'),
+                        array(' ', '<br />'), false);
+                }
+            }
+
+            if ($this->_features->gradecat) {
+                $mform->addElement('select', 'gradecat',
+                        get_string('gradecategoryonmodform', 'grades'),
+                        grade_get_categories_menu($COURSE->id, $this->_outcomesused));
+                $mform->addHelpButton('gradecat', 'gradecategoryonmodform', 'grades');
+            }
+        }
+    }
+
+    function add_intro_editor($required=false, $customlabel=null) {
+        if (!$this->_features->introeditor) {
+            // intro editor not supported in this module
+            return;
+        }
+
+        $mform = $this->_form;
+        $label = is_null($customlabel) ? get_string('moduleintro') : $customlabel;
+
+        $mform->addElement('editor', 'introeditor', $label, null, array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'noclean'=>true, 'context'=>$this->context));
+        $mform->setType('introeditor', PARAM_RAW); // no XSS prevention here, users must be trusted
+        if ($required) {
+            $mform->addRule('introeditor', get_string('required'), 'required', null, 'client');
+        }
+
+        // If the 'show description' feature is enabled, this checkbox appears
+        // below the intro.
+        if ($this->_features->showdescription) {
+            $mform->addElement('checkbox', 'showdescription', get_string('showdescription'));
+            $mform->addHelpButton('showdescription', 'showdescription');
+        }
+    }
+
+    /**
+     * Overriding formslib's add_action_buttons() method, to add an extra submit "save changes and return" button.
+     *
+     * @param bool $cancel show cancel button
+     * @param string $submitlabel null means default, false means none, string is label text
+     * @param string $submit2label  null means default, false means none, string is label text
+     * @return void
+     */
+    function add_action_buttons($cancel=true, $submitlabel=null, $submit2label=null) {
+        if (is_null($submitlabel)) {
+            $submitlabel = get_string('savechangesanddisplay');
+        }
+
+        if (is_null($submit2label)) {
+            $submit2label = get_string('savechangesandreturntocourse');
+        }
+
+        $mform = $this->_form;
+
+        // elements in a row need a group
+        $buttonarray = array();
+
+        if ($submit2label !== false) {
+            $buttonarray[] = &$mform->createElement('submit', 'submitbutton2', $submit2label);
+        }
+
+        if ($submitlabel !== false) {
+            $buttonarray[] = &$mform->createElement('submit', 'submitbutton', $submitlabel);
+        }
+
+        if ($cancel) {
+            $buttonarray[] = &$mform->createElement('cancel');
+        }
+
+        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+        $mform->setType('buttonar', PARAM_RAW);
+        $mform->closeHeaderBefore('buttonar');
+    }
+}
+
+
diff --git a/pending.php b/pending.php
new file mode 100644 (file)
index 0000000..3d6cb18
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.org                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program 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 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program 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:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * Allow the administrator to look through a list of course requests and approve or reject them.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package course
+ */
+
+require_once(dirname(__FILE__) . '/../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->dirroot . '/course/lib.php');
+require_once($CFG->dirroot . '/course/request_form.php');
+
+require_login();
+require_capability('moodle/site:approvecourse', context_system::instance());
+
+$approve = optional_param('approve', 0, PARAM_INT);
+$reject = optional_param('reject', 0, PARAM_INT);
+
+$baseurl = $CFG->wwwroot . '/course/pending.php';
+admin_externalpage_setup('coursespending');
+
+/// Process approval of a course.
+if (!empty($approve) and confirm_sesskey()) {
+    /// Load the request.
+    $course = new course_request($approve);
+    $courseid = $course->approve();
+
+    if ($courseid !== false) {
+        redirect($CFG->wwwroot.'/course/edit.php?id=' . $courseid);
+    } else {
+        print_error('courseapprovedfailed');
+    }
+}
+
+/// Process rejection of a course.
+if (!empty($reject)) {
+    // Load the request.
+    $course = new course_request($reject);
+
+    // Prepare the form.
+    $rejectform = new reject_request_form($baseurl);
+    $default = new stdClass();
+    $default->reject = $course->id;
+    $rejectform->set_data($default);
+
+/// Standard form processing if statement.
+    if ($rejectform->is_cancelled()){
+        redirect($baseurl);
+
+    } else if ($data = $rejectform->get_data()) {
+
+        /// Reject the request
+        $course->reject($data->rejectnotice);
+
+        /// Redirect back to the course listing.
+        redirect($baseurl, get_string('courserejected'));
+    }
+
+/// Display the form for giving a reason for rejecting the request.
+    echo $OUTPUT->header($rejectform->focus());
+    $rejectform->display();
+    echo $OUTPUT->footer();
+    exit;
+}
+
+/// Print a list of all the pending requests.
+echo $OUTPUT->header();
+
+$pending = $DB->get_records('course_request');
+if (empty($pending)) {
+    echo $OUTPUT->heading(get_string('nopendingcourses'));
+} else {
+    echo $OUTPUT->heading(get_string('coursespending'));
+
+/// Build a table of all the requests.
+    $table = new html_table();
+    $table->attributes['class'] = 'pendingcourserequests generaltable';
+    $table->align = array('center', 'center', 'center', 'center', 'center', 'center');
+    $table->head = array(get_string('shortnamecourse'), get_string('fullnamecourse'), get_string('requestedby'),
+            get_string('summary'), get_string('category'), get_string('requestreason'), get_string('action'));
+
+    foreach ($pending as $course) {
+        $course = new course_request($course);
+
+        // Check here for shortname collisions and warn about them.
+        $course->check_shortname_collision();
+
+        // Retreiving category name.
+        // If the category was not set (can happen after upgrade) or if the user does not have the capability
+        // to change the category, we fallback on the default one.
+        // Else, the category proposed is fetched, but we fallback on the default one if we can't find it.
+        // It is just a matter of displaying the right information because the logic when approving the category
+        // proceeds the same way. The system context level is used as moodle/site:approvecourse uses it.
+        if (empty($course->category) || !has_capability('moodle/course:changecategory', context_system::instance()) ||
+                (!$category = get_course_category($course->category))) {
+            $category = get_course_category($CFG->defaultrequestcategory);
+        }
+
+        $row = array();
+        $row[] = format_string($course->shortname);
+        $row[] = format_string($course->fullname);
+        $row[] = fullname($course->get_requester());
+        $row[] = $course->summary;
+        $row[] = format_string($category->name);
+        $row[] = format_string($course->reason);
+        $row[] = $OUTPUT->single_button(new moodle_url($baseurl, array('approve' => $course->id, 'sesskey' => sesskey())), get_string('approve'), 'get') .
+                 $OUTPUT->single_button(new moodle_url($baseurl, array('reject' => $course->id)), get_string('rejectdots'), 'get');
+
+    /// Add the row to the table.
+        $table->data[] = $row;
+    }
+
+/// Display the table.
+    echo html_writer::table($table);
+
+/// Message about name collisions, if necessary.
+    if (!empty($collision)) {
+        print_string('shortnamecollisionwarning');
+    }
+}
+
+/// Finish off the page.
+echo $OUTPUT->single_button($CFG->wwwroot . '/course/index.php', get_string('backtocourselisting'));
+echo $OUTPUT->footer();
diff --git a/publish/backup.php b/publish/backup.php
new file mode 100644 (file)
index 0000000..15e091a
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// This file is part of Moodle - http://moodle.org/                      //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//                                                                       //
+// Moodle is free software: you can redistribute it and/or modify        //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation, either version 3 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// Moodle is distributed in the hope that it will be useful,             //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details.                          //
+//                                                                       //
+// You should have received a copy of the GNU General Public License     //
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.       //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+ * @package    course
+ * @subpackage publish
+ * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
+ *
+ * This page display the publication backup form
+ */
+
+require_once('../../config.php');
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
+require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
+require_once($CFG->dirroot . '/course/publish/lib.php');
+require_once($CFG->libdir . '/filelib.php');
+
+
+//retrieve initial page parameters
+$id = required_param('id', PARAM_INT);
+$hubcourseid = required_param('hubcourseid', PARAM_INT);
+$huburl = required_param('huburl', PARAM_URL);
+$hubname = optional_param('hubname', '', PARAM_TEXT);
+
+//some permissions and parameters checking
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+require_login($course);
+if (!has_capability('moodle/course:publish', context_course::instance($id))
+        or !confirm_sesskey()) {
+    throw new moodle_exception('nopermission');
+}
+
+//page settings
+$PAGE->set_url('/course/publish/backup.php');
+$PAGE->set_pagelayout('course');
+$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+$PAGE->set_heading($course->fullname);
+
+//BEGIN backup processing
+$backupid = optional_param('backup', false, PARAM_ALPHANUM);
+if (!($bc = backup_ui::load_controller($backupid))) {
+    $bc = new backup_controller(backup::TYPE_1COURSE, $id, backup::FORMAT_MOODLE,
+                    backup::INTERACTIVE_YES, backup::MODE_HUB, $USER->id);
+}
+$backup = new backup_ui($bc,
+        array('id' => $id, 'hubcourseid' => $hubcourseid, 'huburl' => $huburl, 'hubname' => $hubname));
+$backup->process();
+if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
+    $backup->execute();
+} else {
+    $backup->save_controller();
+}
+
+if ($backup->get_stage() !== backup_ui::STAGE_COMPLETE) {
+    $renderer = $PAGE->get_renderer('core', 'backup');
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('publishcourseon', 'hub', !empty($hubname)?$hubname:$huburl), 3, 'main');
+    if ($backup->enforce_changed_dependencies()) {
+        debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
+    }
+    echo $renderer->progress_bar($backup->get_progress_bar());
+    echo $backup->display($renderer);
+    echo $OUTPUT->footer();
+    die();
+}
+
+//$backupfile = $backup->get_stage_results();
+$backupfile = $bc->get_results();
+$backupfile = $backupfile['backup_destination'];
+//END backup processing
+
+//retrieve the token to call the hub
+$registrationmanager = new registration_manager();
+$registeredhub = $registrationmanager->get_registeredhub($huburl);
+
+//display the sending file page
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('sendingcourse', 'hub'), 3, 'main');
+$renderer = $PAGE->get_renderer('core', 'publish');
+echo $renderer->sendingbackupinfo($backupfile);
+if (ob_get_level()) {
+    ob_flush();
+}
+flush();
+
+//send backup file to the hub
+$curl = new curl();
+$params = array();
+$params['filetype'] = HUB_BACKUP_FILE_TYPE;
+$params['courseid'] = $hubcourseid;
+$params['file'] = $backupfile;
+$params['token'] = $registeredhub->token;
+$curl->post($huburl . "/local/hub/webservice/upload.php", $params);
+
+//delete the temp backup file from user_tohub aera
+$backupfile->delete();
+$bc->destroy();
+
+//Output sending success
+echo $renderer->sentbackupinfo($id, $huburl, $hubname);
+
+echo $OUTPUT->footer();
diff --git a/publish/forms.php b/publish/forms.php
new file mode 100644 (file)
index 0000000..3b5e8df
--- /dev/null
@@ -0,0 +1,392 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// This file is part of Moodle - http://moodle.org/                      //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//                                                                       //
+// Moodle is free software: you can redistribute it and/or modify        //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation, either version 3 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// Moodle is distributed in the hope that it will be useful,             //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details.                          //
+//                                                                       //
+// You should have received a copy of the GNU General Public License     //
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.       //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+ * @package    course
+ * @subpackage publish
+ * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
+ *
+ * The forms used for course publication
+ */
+
+
+require_once($CFG->libdir . '/formslib.php');
+require_once($CFG->dirroot . "/" . $CFG->admin . "/registration/lib.php");
+require_once($CFG->dirroot . "/course/publish/lib.php");
+
+/*
+ * Hub selector to choose on which hub we want to publish.
+ */
+
+class hub_publish_selector_form extends moodleform {
+
+    public function definition() {
+        global $CFG;
+        $mform = & $this->_form;
+        $share = $this->_customdata['share'];
+
+        $mform->addElement('header', 'site', get_string('selecthub', 'hub'));
+
+        $mform->addElement('static', 'info', '', get_string('selecthubinfo', 'hub') . html_writer::empty_tag('br'));
+
+        $registrationmanager = new registration_manager();
+        $registeredhubs = $registrationmanager->get_registered_on_hubs();
+
+        //Public hub list
+        $options = array();
+        foreach ($registeredhubs as $hub) {
+
+            $hubname = $hub->hubname;
+            $mform->addElement('hidden', clean_param($hub->huburl, PARAM_ALPHANUMEXT), $hubname);
+            if (empty($hubname)) {
+                $hubname = $hub->huburl;
+            }
+            $mform->addElement('radio', 'huburl', null, ' ' . $hubname, $hub->huburl);
+            if ($hub->huburl == HUB_MOODLEORGHUBURL) {
+                $mform->setDefault('huburl', $hub->huburl);
+            }
+        }
+
+        $mform->addElement('hidden', 'id', $this->_customdata['id']);
+
+        if ($share) {
+            $buttonlabel = get_string('shareonhub', 'hub');
+            $mform->addElement('hidden', 'share', true);
+        } else {
+            $buttonlabel = get_string('advertiseonhub', 'hub');
+            $mform->addElement('hidden', 'advertise', true);
+        }
+
+        $this->add_action_buttons(false, $buttonlabel);
+    }
+
+}
+
+/*
+ * Course publication form
+ */
+
+class course_publication_form extends moodleform {
+
+    public function definition() {
+        global $CFG, $DB, $USER, $OUTPUT;
+
+        $strrequired = get_string('required');
+        $mform = & $this->_form;
+        $huburl = $this->_customdata['huburl'];
+        $hubname = $this->_customdata['hubname'];
+        $course = $this->_customdata['course'];
+        $advertise = $this->_customdata['advertise'];
+        $share = $this->_customdata['share'];
+        $page = $this->_customdata['page'];
+        $site = get_site();
+
+        //hidden parameters
+        $mform->addElement('hidden', 'huburl', $huburl);
+        $mform->addElement('hidden', 'hubname', $hubname);
+
+        //check on the hub if the course has already been published
+        $registrationmanager = new registration_manager();
+        $registeredhub = $registrationmanager->get_registeredhub($huburl);
+        $publicationmanager = new course_publish_manager();
+        $publications = $publicationmanager->get_publications($registeredhub->huburl, $course->id, $advertise);
+
+        if (!empty($publications)) {
+            //get the last publication of this course
+            $publication = array_pop($publications);
+
+            $function = 'hub_get_courses';
+            $options = new stdClass();
+            $options->ids = array($publication->hubcourseid);
+            $options->allsitecourses = 1;
+            $params = array('search' => '', 'downloadable' => $share,
+                'enrollable' => !$share, 'options' => $options);
+            $serverurl = $huburl . "/local/hub/webservice/webservices.php";
+            require_once($CFG->dirroot . "/webservice/xmlrpc/lib.php");
+            $xmlrpcclient = new webservice_xmlrpc_client($serverurl, $registeredhub->token);
+            try {
+                $result = $xmlrpcclient->call($function, $params);
+                $publishedcourses = $result['courses'];
+            } catch (Exception $e) {
+                $error = $OUTPUT->notification(get_string('errorcourseinfo', 'hub', $e->getMessage()));
+                $mform->addElement('static', 'errorhub', '', $error);
+            }
+        }
+
+        if (!empty($publishedcourses)) {
+            $publishedcourse = $publishedcourses[0];
+            $hubcourseid = $publishedcourse['id'];
+            $defaultfullname = $publishedcourse['fullname'];
+            $defaultshortname = $publishedcourse['shortname'];
+            $defaultsummary = $publishedcourse['description'];
+            $defaultlanguage = $publishedcourse['language'];
+            $defaultpublishername = $publishedcourse['publishername'];
+            $defaultpublisheremail = $publishedcourse['publisheremail'];
+            $defaultcontributornames = $publishedcourse['contributornames'];
+            $defaultcoverage = $publishedcourse['coverage'];
+            $defaultcreatorname = $publishedcourse['creatorname'];
+            $defaultlicenceshortname = $publishedcourse['licenceshortname'];
+            $defaultsubject = $publishedcourse['subject'];
+            $defaultaudience = $publishedcourse['audience'];
+            $defaulteducationallevel = $publishedcourse['educationallevel'];
+            $defaultcreatornotes = $publishedcourse['creatornotes'];
+            $defaultcreatornotesformat = $publishedcourse['creatornotesformat'];
+            $screenshotsnumber = $publishedcourse['screenshots'];
+            $privacy = $publishedcourse['privacy'];
+            if (($screenshotsnumber > 0) and !empty($privacy)) {
+                $page->requires->yui_module('moodle-block_community-imagegallery',
+                        'M.blocks_community.init_imagegallery',
+                        array(array('imageids' => array($hubcourseid),
+                                'imagenumbers' => array($screenshotsnumber),
+                                'huburl' => $huburl)));
+            }
+        } else {
+            $defaultfullname = $course->fullname;
+            $defaultshortname = $course->shortname;
+            $defaultsummary = clean_param($course->summary, PARAM_TEXT);
+            if (empty($course->lang)) {
+                $language = get_site()->lang;
+                if (empty($language)) {
+                    $defaultlanguage = current_language();
+                } else {
+                    $defaultlanguage = $language;
+                }
+            } else {
+                $defaultlanguage = $course->lang;
+            }
+            $defaultpublishername = $USER->firstname . ' ' . $USER->lastname;
+            $defaultpublisheremail = $USER->email;
+            $defaultcontributornames = '';
+            $defaultcoverage = '';
+            $defaultcreatorname = $USER->firstname . ' ' . $USER->lastname;
+            $defaultlicenceshortname = 'cc';
+            $defaultsubject = 'none';
+            $defaultaudience = HUB_AUDIENCE_STUDENTS;
+            $defaulteducationallevel = HUB_EDULEVEL_TERTIARY;
+            $defaultcreatornotes = '';
+            $defaultcreatornotesformat = FORMAT_HTML;
+            $screenshotsnumber = 0;
+        }
+
+        //the input parameters
+        $mform->addElement('header', 'moodle', get_string('publicationinfo', 'hub'));
+
+        $mform->addElement('text', 'name', get_string('coursename', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->addRule('name', $strrequired, 'required', null, 'client');
+        $mform->setType('name', PARAM_TEXT);
+        $mform->setDefault('name', $defaultfullname);
+        $mform->addHelpButton('name', 'name', 'hub');
+
+        $mform->addElement('hidden', 'id', $this->_customdata['id']);
+
+        if ($share) {
+            $buttonlabel = get_string('shareon', 'hub', !empty($hubname) ? $hubname : $huburl);
+
+            $mform->addElement('hidden', 'share', $share);
+
+            $mform->addElement('text', 'demourl', get_string('demourl', 'hub'),
+                    array('class' => 'metadatatext'));
+            $mform->setType('demourl', PARAM_URL);
+            $mform->setDefault('demourl', new moodle_url("/course/view.php?id=" . $course->id));
+            $mform->addHelpButton('demourl', 'demourl', 'hub');
+        }
+
+        if ($advertise) {
+            if (empty($publishedcourses)) {
+                $buttonlabel = get_string('advertiseon', 'hub', !empty($hubname) ? $hubname : $huburl);
+            } else {
+                $buttonlabel = get_string('readvertiseon', 'hub', !empty($hubname) ? $hubname : $huburl);
+            }
+            $mform->addElement('hidden', 'advertise', $advertise);
+            $mform->addElement('hidden', 'courseurl', $CFG->wwwroot . "/course/view.php?id=" . $course->id);
+            $mform->addElement('static', 'courseurlstring', get_string('courseurl', 'hub'));
+            $mform->setDefault('courseurlstring', new moodle_url("/course/view.php?id=" . $course->id));
+            $mform->addHelpButton('courseurlstring', 'courseurl', 'hub');
+        }
+
+        $mform->addElement('text', 'courseshortname', get_string('courseshortname', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->setDefault('courseshortname', $defaultshortname);
+        $mform->addHelpButton('courseshortname', 'courseshortname', 'hub');
+
+        $mform->addElement('textarea', 'description', get_string('description'), array('rows' => 10,
+            'cols' => 57));
+        $mform->addRule('description', $strrequired, 'required', null, 'client');
+        $mform->setDefault('description', $defaultsummary);
+        $mform->setType('description', PARAM_TEXT);
+        $mform->addHelpButton('description', 'description', 'hub');
+
+        $languages = get_string_manager()->get_list_of_languages();
+        collatorlib::asort($languages);
+        $mform->addElement('select', 'language', get_string('language'), $languages);
+        $mform->setDefault('language', $defaultlanguage);
+        $mform->addHelpButton('language', 'language', 'hub');
+
+
+        $mform->addElement('text', 'publishername', get_string('publishername', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->setDefault('publishername', $defaultpublishername);
+        $mform->addRule('publishername', $strrequired, 'required', null, 'client');
+        $mform->addHelpButton('publishername', 'publishername', 'hub');
+
+        $mform->addElement('text', 'publisheremail', get_string('publisheremail', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->setDefault('publisheremail', $defaultpublisheremail);
+        $mform->addRule('publisheremail', $strrequired, 'required', null, 'client');
+        $mform->addHelpButton('publisheremail', 'publisheremail', 'hub');
+
+        $mform->addElement('text', 'creatorname', get_string('creatorname', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->addRule('creatorname', $strrequired, 'required', null, 'client');
+        $mform->setType('creatorname', PARAM_TEXT);
+        $mform->setDefault('creatorname', $defaultcreatorname);
+        $mform->addHelpButton('creatorname', 'creatorname', 'hub');
+
+        $mform->addElement('text', 'contributornames', get_string('contributornames', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->setDefault('contributornames', $defaultcontributornames);
+        $mform->addHelpButton('contributornames', 'contributornames', 'hub');
+
+        $mform->addElement('text', 'coverage', get_string('tags', 'hub'),
+                array('class' => 'metadatatext'));
+        $mform->setType('coverage', PARAM_TEXT);
+        $mform->setDefault('coverage', $defaultcoverage);
+        $mform->addHelpButton('coverage', 'tags', 'hub');
+
+
+
+        require_once($CFG->libdir . "/licenselib.php");
+        $licensemanager = new license_manager();
+        $licences = $licensemanager->get_licenses();
+        $options = array();
+        foreach ($licences as $license) {
+            $options[$license->shortname] = get_string($license->shortname, 'license');
+        }
+        $mform->addElement('select', 'licence', get_string('license'), $options);
+        $mform->setDefault('licence', $defaultlicenceshortname);
+        unset($options);
+        $mform->addHelpButton('licence', 'licence', 'hub');
+
+        $options = $publicationmanager->get_sorted_subjects();
+
+        //prepare data for the smartselect
+        foreach ($options as $key => &$option) {
+            $keylength = strlen($key);
+            if ($keylength == 10) {
+                $option = "&nbsp;&nbsp;" . $option;
+            } else if ($keylength == 12) {
+                $option = "&nbsp;&nbsp;&nbsp;&nbsp;" . $option;
+            }
+        }
+
+        $options = array('none' => get_string('none', 'hub')) + $options;
+        $mform->addElement('select', 'subject', get_string('subject', 'hub'), $options);
+        unset($options);
+        $mform->addHelpButton('subject', 'subject', 'hub');
+        $mform->setDefault('subject', $defaultsubject);
+        $mform->addRule('subject', $strrequired, 'required', null, 'client');
+        $this->init_javascript_enhancement('subject', 'smartselect', array('selectablecategories' => false, 'mode' => 'compact'));
+
+        $options = array();
+        $options[HUB_AUDIENCE_EDUCATORS] = get_string('audienceeducators', 'hub');
+        $options[HUB_AUDIENCE_STUDENTS] = get_string('audiencestudents', 'hub');
+        $options[HUB_AUDIENCE_ADMINS] = get_string('audienceadmins', 'hub');
+        $mform->addElement('select', 'audience', get_string('audience', 'hub'), $options);
+        $mform->setDefault('audience', $defaultaudience);
+        unset($options);
+        $mform->addHelpButton('audience', 'audience', 'hub');
+
+        $options = array();
+        $options[HUB_EDULEVEL_PRIMARY] = get_string('edulevelprimary', 'hub');
+        $options[HUB_EDULEVEL_SECONDARY] = get_string('edulevelsecondary', 'hub');
+        $options[HUB_EDULEVEL_TERTIARY] = get_string('eduleveltertiary', 'hub');
+        $options[HUB_EDULEVEL_GOVERNMENT] = get_string('edulevelgovernment', 'hub');
+        $options[HUB_EDULEVEL_ASSOCIATION] = get_string('edulevelassociation', 'hub');
+        $options[HUB_EDULEVEL_CORPORATE] = get_string('edulevelcorporate', 'hub');
+        $options[HUB_EDULEVEL_OTHER] = get_string('edulevelother', 'hub');
+        $mform->addElement('select', 'educationallevel', get_string('educationallevel', 'hub'), $options);
+        $mform->setDefault('educationallevel', $defaulteducationallevel);
+        unset($options);
+        $mform->addHelpButton('educationallevel', 'educationallevel', 'hub');
+
+        $editoroptions = array('maxfiles' => 0, 'maxbytes' => 0, 'trusttext' => false, 'forcehttps' => false);
+        $mform->addElement('editor', 'creatornotes', get_string('creatornotes', 'hub'), '', $editoroptions);
+        $mform->addRule('creatornotes', $strrequired, 'required', null, 'client');
+        $mform->setType('creatornotes', PARAM_CLEANHTML);
+        $mform->addHelpButton('creatornotes', 'creatornotes', 'hub');
+
+        if ($advertise) {
+            if (!empty($screenshotsnumber)) {
+
+                if (!empty($privacy)) {
+                    $baseurl = new moodle_url($huburl . '/local/hub/webservice/download.php',
+                                    array('courseid' => $hubcourseid, 'filetype' => HUB_SCREENSHOT_FILE_TYPE));
+                    $screenshothtml = html_writer::empty_tag('img',
+                                    array('src' => $baseurl, 'alt' => $defaultfullname));
+                    $screenshothtml = html_writer::tag('div', $screenshothtml,
+                                    array('class' => 'coursescreenshot',
+                                        'id' => 'image-' . $hubcourseid));
+                } else {
+                    $screenshothtml = get_string('existingscreenshotnumber', 'hub', $screenshotsnumber);
+                }
+                $mform->addElement('static', 'existingscreenshots', get_string('existingscreenshots', 'hub'), $screenshothtml);
+                $mform->addHelpButton('existingscreenshots', 'deletescreenshots', 'hub');
+                $mform->addElement('checkbox', 'deletescreenshots', '', ' ' . get_string('deletescreenshots', 'hub'));
+            }
+
+            $mform->addElement('hidden', 'existingscreenshotnumber', $screenshotsnumber);
+        }
+
+        $mform->addElement('filemanager', 'screenshots', get_string('addscreenshots', 'hub'), null,
+                array('subdirs' => 0,
+                    'maxbytes' => 1000000,
+                    'maxfiles' => 3
+        ));
+        $mform->addHelpButton('screenshots', 'screenshots', 'hub');
+
+        $this->add_action_buttons(false, $buttonlabel);
+
+        //set default value for creatornotes editor
+        $data = new stdClass();
+        $data->creatornotes = array();
+        $data->creatornotes['text'] = $defaultcreatornotes;
+        $data->creatornotes['format'] = $defaultcreatornotesformat;
+        $this->set_data($data);
+    }
+
+    function validation($data, $files) {
+        global $CFG;
+
+        $errors = array();
+
+        if ($this->_form->_submitValues['subject'] == 'none') {
+            $errors['subject'] = get_string('mustselectsubject', 'hub');
+        }
+
+        return $errors;
+    }
+
+}
+
diff --git a/publish/hubselector.php b/publish/hubselector.php
new file mode 100644 (file)
index 0000000..095e33b
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/*
+ * @package    course
+ * @subpackage publish
+ * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
+ *
+ * On this page the user selects where he wants to publish the course
+*/
+
+require('../../config.php');
+
+require_once($CFG->dirroot.'/' . $CFG->admin . '/registration/lib.php');
+require_once($CFG->dirroot.'/course/publish/forms.php');
+
+$id = required_param('id', PARAM_INT);
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+require_login($course);
+
+$PAGE->set_url('/course/publish/hubselector.php', array('id' => $course->id));
+$PAGE->set_pagelayout('course');
+$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+$PAGE->set_heading($course->fullname);
+
+$registrationmanager = new registration_manager();
+$registeredhubs = $registrationmanager->get_registered_on_hubs();
+if (empty($registeredhubs)) {
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('publishon', 'hub'), 3, 'main');
+    echo $OUTPUT->box(get_string('notregisteredonhub', 'hub'));
+    echo $OUTPUT->footer();
+    die();
+}
+
+
+$share = optional_param('share', false, PARAM_BOOL);
+$advertise = optional_param('advertise', false, PARAM_BOOL);
+$hubselectorform = new hub_publish_selector_form('',
+        array('id' => $id, 'share' => $share, 'advertise' => $advertise));
+$fromform = $hubselectorform->get_data();
+
+//// Redirect to the registration form if an URL has been chosen ////
+$huburl = optional_param('huburl', false, PARAM_URL);
+
+//redirect
+if (!empty($huburl) and confirm_sesskey()) {
+    $hubname = optional_param(clean_param($huburl, PARAM_ALPHANUMEXT), '', PARAM_TEXT);
+    $params = array('sesskey' => sesskey(), 'id' => $id,
+            'huburl' => $huburl, 'hubname' => $hubname, 'share' => $share, 'advertise' => $advertise);
+    redirect(new moodle_url($CFG->wwwroot."/course/publish/metadata.php",
+            $params));
+}
+
+
+//// OUTPUT ////
+
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading(get_string('publishon', 'hub'), 3, 'main');
+$hubselectorform->display();
+echo $OUTPUT->footer();
\ No newline at end of file
diff --git a/publish/index.php b/publish/index.php
new file mode 100644 (file)
index 0000000..d401e5c
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/*
+ * @package    course
+ * @subpackage publish
+ * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
+ *
+ * The user selects if he wants to publish the course on Moodle.org hub or
+ * on a specific hub. The site must be registered on a hub to be able to
+ * publish a course on it.
+*/
+
+require('../../config.php');
+require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
+require_once($CFG->dirroot . '/course/publish/lib.php');
+
+$id = required_param('id', PARAM_INT);
+$hubname = optional_param('hubname', 0, PARAM_TEXT);
+$huburl = optional_param('huburl', 0, PARAM_URL);
+
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+
+require_login($course);
+$context = context_course::instance($course->id);
+$shortname = format_string($course->shortname, true, array('context' => $context));
+
+$PAGE->set_url('/course/publish/index.php', array('id' => $course->id));
+$PAGE->set_pagelayout('course');
+$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+$PAGE->set_heading($course->fullname);
+
+//check that the PHP xmlrpc extension is enabled
+if (!extension_loaded('xmlrpc')) {
+    $notificationerror = $OUTPUT->doc_link('admin/environment/php_extension/xmlrpc', '');
+    $notificationerror .= get_string('xmlrpcdisabledpublish', 'hub');
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('publishcourse', 'hub', $shortname), 3, 'main');
+    echo $OUTPUT->notification($notificationerror);
+    echo $OUTPUT->footer();
+    die();
+}
+
+if (has_capability('moodle/course:publish', context_course::instance($id))) {
+
+    $publicationmanager = new course_publish_manager();
+    $confirmmessage = '';
+
+    //update the courses status
+    $updatestatusid = optional_param('updatestatusid', false, PARAM_INT);
+    if (!empty($updatestatusid) and confirm_sesskey()) {
+        //get the communication token from the publication
+        $hub = $publicationmanager->get_registeredhub_by_publication($updatestatusid);
+        if (empty($hub)) {
+            $confirmmessage = $OUTPUT->notification(get_string('nocheckstatusfromunreghub', 'hub'));
+        } else {
+            //get all site courses registered on this hub
+            $function = 'hub_get_courses';
+            $params = array('search' => '', 'downloadable' => 1,
+                'enrollable' => 1, 'options' => array( 'allsitecourses' => 1));
+            $serverurl = $hub->huburl."/local/hub/webservice/webservices.php";
+            require_once($CFG->dirroot."/webservice/xmlrpc/lib.php");
+            $xmlrpcclient = new webservice_xmlrpc_client($serverurl, $hub->token);
+            $result = $xmlrpcclient->call($function, $params);
+            $sitecourses = $result['courses'];
+
+            //update status for all these course
+            foreach ($sitecourses as $sitecourse) {
+                //get the publication from the hub course id
+                $publication = $publicationmanager->get_publication($sitecourse['id'], $hub->huburl);
+                if (!empty($publication)) {
+                    $publication->status = $sitecourse['privacy'];
+                    $publication->timechecked = time();
+                    $publicationmanager->update_publication($publication);
+                } else {
+                    $msgparams = new stdClass();
+                    $msgparams->id = $sitecourse['id'];
+                    $msgparams->hubname = html_writer::tag('a', $hub->hubname, array('href' => $hub->huburl));
+                    $confirmmessage .= $OUTPUT->notification(
+                            get_string('detectednotexistingpublication', 'hub', $msgparams));
+                }
+            }
+        }
+    }
+
+    //if the site os registered on no hub display an error page
+    $registrationmanager = new registration_manager();
+    $registeredhubs = $registrationmanager->get_registered_on_hubs();
+    if (empty($registeredhubs)) {
+        echo $OUTPUT->header();
+        echo $OUTPUT->heading(get_string('publishon', 'hub'), 3, 'main');
+        echo $OUTPUT->box(get_string('notregisteredonhub', 'hub'));
+        echo $OUTPUT->footer();
+        die();
+    }
+
+    $renderer = $PAGE->get_renderer('core', 'publish');
+
+    /// UNPUBLISH
+    $cancel = optional_param('cancel', 0, PARAM_BOOL);
+    if (!empty($cancel) and confirm_sesskey()) {
+        $confirm = optional_param('confirm', 0, PARAM_BOOL);
+        $hubcourseid = optional_param('hubcourseid', 0, PARAM_INT);
+        $publicationid = optional_param('publicationid', 0, PARAM_INT);
+        $timepublished = optional_param('timepublished', 0, PARAM_INT);
+        $publication->courseshortname = $course->shortname;
+        $publication->courseid = $course->id;
+        $publication->hubname = $hubname;
+        $publication->huburl = $huburl;
+        $publication->hubcourseid = $hubcourseid;
+        $publication->timepublished = $timepublished;
+        if (empty($publication->hubname)) {
+             $publication->hubname = $huburl;
+        }
+        $publication->id = $publicationid;
+        if($confirm) {
+            //unpublish the publication by web service
+            $registeredhub = $registrationmanager->get_registeredhub($huburl);
+            $function = 'hub_unregister_courses';
+            $params = array('courseids' => array( $publication->hubcourseid));
+            $serverurl = $huburl."/local/hub/webservice/webservices.php";
+            require_once($CFG->dirroot."/webservice/xmlrpc/lib.php");
+            $xmlrpcclient = new webservice_xmlrpc_client($serverurl, $registeredhub->token);
+            $result = $xmlrpcclient->call($function, $params);
+
+            //delete the publication from the database
+            $publicationmanager->delete_publication($publicationid);
+
+            //display confirmation message
+            $confirmmessage = $OUTPUT->notification(get_string('courseunpublished', 'hub', $publication), 'notifysuccess');
+
+        } else {
+            //display confirmation page for unpublishing
+
+            echo $OUTPUT->header();
+            echo $OUTPUT->heading(get_string('unpublishcourse', 'hub', $shortname), 3, 'main');
+            echo $renderer->confirmunpublishing($publication);
+            echo $OUTPUT->footer();
+            die();
+        }
+    }
+
+    //check if a course was published
+    if (optional_param('published', 0, PARAM_TEXT)) {
+        $confirmmessage = $OUTPUT->notification(get_string('coursepublished', 'hub',
+                empty($hubname)?$huburl:$hubname), 'notifysuccess');
+    }
+
+
+    /// OUTPUT
+    echo $OUTPUT->header();
+    echo $confirmmessage;
+
+    echo $OUTPUT->heading(get_string('publishcourse', 'hub', $shortname), 3, 'main');
+    echo $renderer->publicationselector($course->id);
+
+    $publications = $publicationmanager->get_course_publications($course->id);
+    if (!empty($publications)) {
+        echo $OUTPUT->heading(get_string('publishedon', 'hub'), 3, 'main');
+        echo $renderer->registeredonhublisting($course->id, $publications);
+    }
+
+    echo $OUTPUT->footer();
+
+}
diff --git a/publish/lib.php b/publish/lib.php
new file mode 100644 (file)
index 0000000..fe13219
--- /dev/null
@@ -0,0 +1,299 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+/// TIME PERIOD ///
+
+define('HUB_LASTMODIFIED_WEEK', 7);
+
+define('HUB_LASTMODIFIED_FORTEENNIGHT', 14);
+
+define('HUB_LASTMODIFIED_MONTH', 30);
+
+
+
+//// AUDIENCE ////
+
+/**
+ * Audience: educators
+ */
+define('HUB_AUDIENCE_EDUCATORS', 'educators');
+
+/**
+ * Audience: students
+ */
+define('HUB_AUDIENCE_STUDENTS', 'students');
+
+/**
+ * Audience: admins
+ */
+define('HUB_AUDIENCE_ADMINS', 'admins');
+
+
+
+///// EDUCATIONAL LEVEL /////
+
+/**
+ * Educational level: primary
+ */
+define('HUB_EDULEVEL_PRIMARY', 'primary');
+
+/**
+ * Educational level: secondary
+ */
+define('HUB_EDULEVEL_SECONDARY', 'secondary');
+
+/**
+ * Educational level: tertiary
+ */
+define('HUB_EDULEVEL_TERTIARY', 'tertiary');
+
+/**
+ * Educational level: government
+ */
+define('HUB_EDULEVEL_GOVERNMENT', 'government');
+
+/**
+ * Educational level: association
+ */
+define('HUB_EDULEVEL_ASSOCIATION', 'association');
+
+/**
+ * Educational level: corporate
+ */
+define('HUB_EDULEVEL_CORPORATE', 'corporate');
+
+/**
+ * Educational level: other
+ */
+define('HUB_EDULEVEL_OTHER', 'other');
+
+
+
+///// FILE TYPES /////
+
+/**
+ * FILE TYPE: COURSE SCREENSHOT
+ */
+define('HUB_SCREENSHOT_FILE_TYPE', 'screenshot');
+
+/**
+ * FILE TYPE: HUB SCREENSHOT
+ */
+define('HUB_HUBSCREENSHOT_FILE_TYPE', 'hubscreenshot');
+
+/**
+ * FILE TYPE: BACKUP
+ */
+define('HUB_BACKUP_FILE_TYPE', 'backup');
+
+/**
+ *
+ * Course publication library
+ *
+ * @package   course
+ * @copyright 2010 Moodle Pty Ltd (http://moodle.com)
+ * @author    Jerome Mouneyrac
+ * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_publish_manager {
+
+    /**
+     * Record a course publication
+     * @param int $hubid the hub id from the 'registered on hub' table
+     * @param int $courseid the course id from site point of view
+     * @param int $enrollable if the course is enrollable = 1, if downloadable = 0
+     * @param int $hubcourseid the course id from the hub point of view
+     */
+    public function add_course_publication($huburl, $courseid, $enrollable, $hubcourseid) {
+        global $DB;
+        $publication = new stdClass();
+        $publication->huburl = $huburl;
+        $publication->courseid = $courseid;
+        $publication->hubcourseid = $hubcourseid;
+        $publication->enrollable = (int) $enrollable;
+        $publication->timepublished = time();
+        $DB->insert_record('course_published', $publication);
+    }
+
+    /**
+     * Update a enrollable course publication
+     * @param int $publicationid
+     */
+    public function update_enrollable_course_publication($publicationid) {
+        global $DB;
+        $publication = new stdClass();
+        $publication->id = $publicationid;
+        $publication->timepublished = time();
+        $DB->update_record('course_published', $publication);
+    }
+
+    /**
+     * Update a course publication
+     * @param object $publication
+     */
+    public function update_publication($publication) {
+        global $DB;
+        $DB->update_record('course_published', $publication);
+    }
+
+    /**
+     * Get courses publications
+     * @param int $hubid specify a hub
+     * @param int $courseid specify a course
+     * @param int $enrollable specify type of publication (enrollable or downloadable)
+     * @return array of publications
+     */
+    public function get_publications($huburl = null, $courseid = null, $enrollable = -1) {
+        global $DB;
+        $params = array();
+
+        if (!empty($huburl)) {
+            $params['huburl'] = $huburl;
+        }
+
+        if (!empty($courseid)) {
+            $params['courseid'] = $courseid;
+        }
+
+        if ($enrollable != -1) {
+            $params['enrollable'] = (int) $enrollable;
+        }
+
+        return $DB->get_records('course_published', $params);
+    }
+
+    /**
+     * Get a publication for a course id on a hub
+     * (which is either the id of the unique possible enrollable publication of a course,
+     * either an id of one of the downloadable publication)
+     * @param int $hubcourseid
+     * @param string $huburl
+     * @return object publication
+     */
+    public function get_publication($hubcourseid, $huburl) {
+        global $DB;
+        return $DB->get_record('course_published',
+                array('hubcourseid' => $hubcourseid, 'huburl' => $huburl));
+    }
+
+    /**
+     * Get all publication for a course
+     * @param int $courseid
+     * @return array of publication
+     */
+    public function get_course_publications($courseid) {
+        global $DB;
+        $sql = 'SELECT cp.id, cp.status, cp.timechecked, cp.timepublished, rh.hubname,
+                       rh.huburl, cp.courseid, cp.enrollable, cp.hubcourseid
+                FROM {course_published} cp, {registration_hubs} rh
+                WHERE cp.huburl = rh.huburl and cp.courseid = :courseid
+                ORDER BY cp.enrollable DESC, rh.hubname, cp.timepublished';
+        $params = array('courseid' => $courseid);
+        return $DB->get_records_sql($sql, $params);
+    }
+
+    /**
+     * Get the hub concerned by a publication
+     * @param int $publicationid
+     * @return object the hub (id, name, url, token)
+     */
+    public function get_registeredhub_by_publication($publicationid) {
+        global $DB;
+        $sql = 'SELECT rh.huburl, rh.hubname, rh.token
+                FROM {course_published} cp, {registration_hubs} rh
+                WHERE cp.huburl = rh.huburl and cp.id = :publicationid';
+        $params = array('publicationid' => $publicationid);
+        return $DB->get_record_sql($sql, $params);
+    }
+
+    /**
+     * Delete a publication
+     * @param int $publicationid
+     */
+    public function delete_publication($publicationid) {
+        global $DB;
+        $DB->delete_records('course_published', array('id' => $publicationid));
+    }
+
+    /**
+     * Delete publications for a hub
+     * @param string $huburl
+     * @param int $enrollable
+     */
+    public function delete_hub_publications($huburl, $enrollable = -1) {
+        global $DB;
+
+        $params = array();
+
+        if (!empty($huburl)) {
+            $params['huburl'] = $huburl;
+        }
+
+        if ($enrollable != -1) {
+            $params['enrollable'] = (int) $enrollable;
+        }
+
+        $DB->delete_records('course_published', $params);
+    }
+
+    /**
+     * Get an array of all block instances for a given context
+     * @param int $contextid a context id
+     * @return array of block instances.
+     */
+    public function get_block_instances_by_context($contextid, $sort = '') {
+        global $DB;
+        return $DB->get_records('block_instances', array('parentcontextid' => $contextid), $sort);
+    }
+
+    /**
+     * Retrieve all the sorted course subjects
+     * @return array $subjects
+     */
+    public function get_sorted_subjects() {
+        $subjects = get_string_manager()->load_component_strings('edufields', current_language());
+
+        //sort the subjects
+        asort($subjects);
+        foreach ($subjects as $key => $option) {
+            $keylength = strlen($key);
+            if ($keylength == 8) {
+                $toplevel[$key] = $option;
+            } else if ($keylength == 10) {
+                $sublevel[substr($key, 0, 8)][$key] = $option;
+            } else if ($keylength == 12) {
+                $subsublevel[substr($key, 0, 8)][substr($key, 0, 10)][$key] = $option;
+            }
+        }
+
+        //recreate the initial structure returned by get_string_manager()
+        $subjects = array();
+        foreach ($toplevel as $key => $name) {
+            $subjects[$key] = $name;
+            foreach ($sublevel[$key] as $subkey => $subname) {
+                $subjects[$subkey] = $subname;
+                foreach ($subsublevel[$key][$subkey] as $subsubkey => $subsubname) {
+                    $subjects[$subsubkey] = $subsubname;
+                }
+            }
+        }
+
+        return $subjects;
+    }
+
+}
+?>
diff --git a/publish/metadata.php b/publish/metadata.php
new file mode 100644 (file)
index 0000000..298e5f3
--- /dev/null
@@ -0,0 +1,274 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// This file is part of Moodle - http://moodle.org/                      //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//                                                                       //
+// Moodle is free software: you can redistribute it and/or modify        //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation, either version 3 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// Moodle is distributed in the hope that it will be useful,             //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details.                          //
+//                                                                       //
+// You should have received a copy of the GNU General Public License     //
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.       //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/*
+ * @package    course
+ * @subpackage publish
+ * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
+ * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
+ *
+ * This page display the publication metadata form
+ */
+
+require_once('../../config.php');
+require_once($CFG->dirroot . '/course/publish/forms.php');
+require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
+require_once($CFG->dirroot . '/course/publish/lib.php');
+require_once($CFG->libdir . '/filelib.php');
+
+
+//check user access capability to this page
+$id = required_param('id', PARAM_INT);
+
+$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
+require_login($course);
+
+//page settings
+$PAGE->set_url('/course/publish/metadata.php', array('id' => $course->id));
+$PAGE->set_pagelayout('course');
+$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+$PAGE->set_heading($course->fullname);
+
+//check that the PHP xmlrpc extension is enabled
+if (!extension_loaded('xmlrpc')) {
+    $errornotification = $OUTPUT->doc_link('admin/environment/php_extension/xmlrpc', '');
+    $errornotification .= get_string('xmlrpcdisabledpublish', 'hub');
+    $context = context_course::instance($course->id);
+    $shortname = format_string($course->shortname, true, array('context' => $context));
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('publishcourse', 'hub', $shortname), 3, 'main');
+    echo $OUTPUT->notification($errornotification);
+    echo $OUTPUT->footer();
+    die();
+}
+
+if (has_capability('moodle/course:publish', context_course::instance($id))) {
+
+    //retrieve hub name and hub url
+    $huburl = optional_param('huburl', '', PARAM_URL);
+    $hubname = optional_param('hubname', '', PARAM_TEXT);
+    if (empty($huburl) or !confirm_sesskey()) {
+        throw new moodle_exception('missingparameter');
+    }
+
+    //set the publication form
+    $advertise = optional_param('advertise', false, PARAM_BOOL);
+    $share = optional_param('share', false, PARAM_BOOL);
+    $coursepublicationform = new course_publication_form('',
+                    array('huburl' => $huburl, 'hubname' => $hubname, 'sesskey' => sesskey(),
+                        'course' => $course, 'advertise' => $advertise, 'share' => $share,
+                        'id' => $id, 'page' => $PAGE));
+    $fromform = $coursepublicationform->get_data();
+
+    //retrieve the token to call the hub
+    $registrationmanager = new registration_manager();
+    $registeredhub = $registrationmanager->get_registeredhub($huburl);
+
+    //setup web service xml-rpc client
+    $serverurl = $huburl . "/local/hub/webservice/webservices.php";
+    require_once($CFG->dirroot . "/webservice/xmlrpc/lib.php");
+    $xmlrpcclient = new webservice_xmlrpc_client($serverurl, $registeredhub->token);
+
+    if (!empty($fromform)) {
+
+        $publicationmanager = new course_publish_manager();
+
+        //retrieve the course information
+        $courseinfo = new stdClass();
+        $courseinfo->fullname = $fromform->name;
+        $courseinfo->shortname = $fromform->courseshortname;
+        $courseinfo->description = $fromform->description;
+        $courseinfo->language = $fromform->language;
+        $courseinfo->publishername = $fromform->publishername;
+        $courseinfo->publisheremail = $fromform->publisheremail;
+        $courseinfo->contributornames = $fromform->contributornames;
+        $courseinfo->coverage = $fromform->coverage;
+        $courseinfo->creatorname = $fromform->creatorname;
+        $courseinfo->licenceshortname = $fromform->licence;
+        $courseinfo->subject = $fromform->subject;
+        $courseinfo->audience = $fromform->audience;
+        $courseinfo->educationallevel = $fromform->educationallevel;
+        $creatornotes = $fromform->creatornotes;
+        $courseinfo->creatornotes = $creatornotes['text'];
+        $courseinfo->creatornotesformat = $creatornotes['format'];
+        $courseinfo->sitecourseid = $id;
+        if (!empty($fromform->deletescreenshots)) {
+            $courseinfo->deletescreenshots = $fromform->deletescreenshots;
+        }
+        if ($share) {
+            $courseinfo->demourl = $fromform->demourl;
+            $courseinfo->enrollable = false;
+        } else {
+            $courseinfo->courseurl = $fromform->courseurl;
+            $courseinfo->enrollable = true;
+        }
+
+        //retrieve the outcomes of this course
+        require_once($CFG->libdir . '/grade/grade_outcome.php');
+        $outcomes = grade_outcome::fetch_all_available($id);
+        if (!empty($outcomes)) {
+            foreach ($outcomes as $outcome) {
+                $sentoutcome = new stdClass();
+                $sentoutcome->fullname = $outcome->fullname;
+                $courseinfo->outcomes[] = $sentoutcome;
+            }
+        }
+
+        //retrieve the content information from the course
+        $coursecontext = context_course::instance($course->id);
+        $courseblocks = $publicationmanager->get_block_instances_by_context($coursecontext->id, 'blockname');
+
+        if (!empty($courseblocks)) {
+            $blockname = '';
+            foreach ($courseblocks as $courseblock) {
+                if ($courseblock->blockname != $blockname) {
+                    if (!empty($blockname)) {
+                        $courseinfo->contents[] = $content;
+                    }
+
+                    $blockname = $courseblock->blockname;
+                    $content = new stdClass();
+                    $content->moduletype = 'block';
+                    $content->modulename = $courseblock->blockname;
+                    $content->contentcount = 1;
+                } else {
+                    $content->contentcount = $content->contentcount + 1;
+                }
+            }
+            $courseinfo->contents[] = $content;
+        }
+
+        $activities = get_fast_modinfo($course, $USER->id);
+        foreach ($activities->instances as $activityname => $activitydetails) {
+            $content = new stdClass();
+            $content->moduletype = 'activity';
+            $content->modulename = $activityname;
+            $content->contentcount = count($activities->instances[$activityname]);
+            $courseinfo->contents[] = $content;
+        }
+
+        //save into screenshots field the references to the screenshot content hash
+        //(it will be like a unique id from the hub perspective)
+        if (!empty($fromform->deletescreenshots) or $share) {
+            $courseinfo->screenshots = 0;
+        } else {
+            $courseinfo->screenshots = $fromform->existingscreenshotnumber;
+        }
+        if (!empty($fromform->screenshots)) {
+            $screenshots = $fromform->screenshots;
+            $fs = get_file_storage();
+            $files = $fs->get_area_files(context_user::instance($USER->id)->id, 'user', 'draft', $screenshots);
+            if (!empty($files)) {
+                $courseinfo->screenshots = $courseinfo->screenshots + count($files) - 1; //minus the ./ directory
+            }
+        }
+
+        // PUBLISH ACTION
+
+        //publish the course information
+        $function = 'hub_register_courses';
+        $params = array('courses' => array($courseinfo));
+        try {
+            $courseids = $xmlrpcclient->call($function, $params);
+        } catch (Exception $e) {
+            throw new moodle_exception('errorcoursepublish', 'hub',
+                    new moodle_url('/course/view.php', array('id' => $id)), $e->getMessage());
+        }
+
+        if (count($courseids) != 1) {
+            throw new moodle_exception('errorcoursewronglypublished', 'hub');
+        }
+
+        //save the record into the published course table
+        $publication = $publicationmanager->get_publication($courseids[0], $huburl);
+        if (empty($publication)) {
+            //if never been published or if we share, we need to save this new publication record
+            $publicationmanager->add_course_publication($registeredhub->huburl, $course->id, !$share, $courseids[0]);
+        } else {
+            //if we update the enrollable course publication we update the publication record
+            $publicationmanager->update_enrollable_course_publication($publication->id);
+        }
+
+        // SEND FILES
+        $curl = new curl();
+
+        // send screenshots
+        if (!empty($fromform->screenshots)) {
+
+            if (!empty($fromform->deletescreenshots) or $share) {
+                $screenshotnumber = 0;
+            } else {
+                $screenshotnumber = $fromform->existingscreenshotnumber;
+            }
+            foreach ($files as $file) {
+                if ($file->is_valid_image()) {
+                    $screenshotnumber = $screenshotnumber + 1;
+                    $params = array();
+                    $params['filetype'] = HUB_SCREENSHOT_FILE_TYPE;
+                    $params['file'] = $file;
+                    $params['courseid'] = $courseids[0];
+                    $params['filename'] = $file->get_filename();
+                    $params['screenshotnumber'] = $screenshotnumber;
+                    $params['token'] = $registeredhub->token;
+                    $curl->post($huburl . "/local/hub/webservice/upload.php", $params);
+                }
+            }
+        }
+
+        //redirect to the backup process page
+        if ($share) {
+            $params = array('sesskey' => sesskey(), 'id' => $id, 'hubcourseid' => $courseids[0],
+                'huburl' => $huburl, 'hubname' => $hubname);
+            $backupprocessurl = new moodle_url("/course/publish/backup.php", $params);
+            redirect($backupprocessurl);
+        } else {
+            //redirect to the index publis page
+            redirect(new moodle_url('/course/publish/index.php',
+                            array('sesskey' => sesskey(), 'id' => $id, 'published' => true,
+                                'hubname' => $hubname, 'huburl' => $huburl)));
+        }
+    }
+
+    /////// OUTPUT SECTION /////////////
+
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('publishcourseon', 'hub', !empty($hubname) ? $hubname : $huburl), 3, 'main');
+
+    //display hub information (logo, name, description)
+    $function = 'hub_get_info';
+    $params = array();
+    try {
+        $hubinfo = $xmlrpcclient->call($function, $params);
+    } catch (Exception $e) {
+        //only print error log in apache (for backward compatibility)
+        error_log(print_r($e->getMessage(), true));
+    }
+    $renderer = $PAGE->get_renderer('core', 'publish');
+    if (!empty($hubinfo)) {
+        echo $renderer->hubinfo($hubinfo);
+    }
+
+    //display metadata form
+    $coursepublicationform->display();
+    echo $OUTPUT->footer();
+}
diff --git a/publish/renderer.php b/publish/renderer.php
new file mode 100644 (file)
index 0000000..970fed5
--- /dev/null
@@ -0,0 +1,218 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// This file is part of Moodle - http://moodle.org/                      //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//                                                                       //
+// Moodle is free software: you can redistribute it and/or modify        //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation, either version 3 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// Moodle is distributed in the hope that it will be useful,             //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details.                          //
+//                                                                       //
+// You should have received a copy of the GNU General Public License     //
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.       //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * Course publish renderer.
+ * @package   course
+ * @subpackage publish
+ * @copyright 2010 Moodle Pty Ltd (http://moodle.com)
+ * @author    Jerome Mouneyrac
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_publish_renderer extends plugin_renderer_base {
+
+    /**
+     * Display the selector to advertise or publish a course
+     */
+    public function publicationselector($courseid) {
+        $text = '';
+
+        $advertiseurl = new moodle_url("/course/publish/hubselector.php",
+                        array('sesskey' => sesskey(), 'id' => $courseid, 'advertise' => true));
+        $advertisebutton = new single_button($advertiseurl, get_string('advertise', 'hub'));
+        $text .= $this->output->render($advertisebutton);
+        $text .= html_writer::tag('div', get_string('advertisepublication_help', 'hub'),
+                        array('class' => 'publishhelp'));
+
+        $text .= html_writer::empty_tag('br');  /// TODO Delete
+
+        $uploadurl = new moodle_url("/course/publish/hubselector.php",
+                        array('sesskey' => sesskey(), 'id' => $courseid, 'share' => true));
+        $uploadbutton = new single_button($uploadurl, get_string('share', 'hub'));
+        $text .= $this->output->render($uploadbutton);
+        $text .= html_writer::tag('div', get_string('sharepublication_help', 'hub'),
+                        array('class' => 'publishhelp'));
+
+        return $text;
+    }
+
+    /**
+     * Display the listing of hub where a course is registered on
+     */
+    public function registeredonhublisting($courseid, $publications) {
+        global $CFG;
+        $table = new html_table();
+        $table->head = array(get_string('type', 'hub'), get_string('hub', 'hub'),
+            get_string('date'), get_string('status', 'hub'), get_string('operation', 'hub'));
+        $table->size = array('10%', '40%', '20%', '%10', '%15');
+
+        $brtag = html_writer::empty_tag('br');
+
+        foreach ($publications as $publication) {
+
+            $updatebuttonhtml = '';
+
+            $params = array('sesskey' => sesskey(), 'id' => $publication->courseid,
+                'hubcourseid' => $publication->hubcourseid,
+                'huburl' => $publication->huburl, 'hubname' => $publication->hubname,
+                'cancel' => true, 'publicationid' => $publication->id,
+                'timepublished' => $publication->timepublished);
+            $cancelurl = new moodle_url("/course/publish/index.php", $params);
+            $cancelbutton = new single_button($cancelurl, get_string('removefromhub', 'hub'));
+            $cancelbutton->class = 'centeredbutton';
+            $cancelbuttonhtml = $this->output->render($cancelbutton);
+
+            if ($publication->enrollable) {
+                $params = array('sesskey' => sesskey(), 'id' => $publication->courseid,
+                    'huburl' => $publication->huburl, 'hubname' => $publication->hubname,
+                    'share' => !$publication->enrollable, 'advertise' => $publication->enrollable);
+                $updateurl = new moodle_url("/course/publish/metadata.php", $params);
+                $updatebutton = new single_button($updateurl, get_string('update', 'hub'));
+                $updatebutton->class = 'centeredbutton';
+                $updatebuttonhtml = $this->output->render($updatebutton);
+
+                $operations = $updatebuttonhtml . $brtag . $cancelbuttonhtml;
+            } else {
+                $operations = $cancelbuttonhtml;
+            }
+
+            $hubname = html_writer::tag('a',
+                            $publication->hubname ? $publication->hubname : $publication->huburl,
+                            array('href' => $publication->huburl));
+            //if the publication check time if bigger than May 2010, it has been checked
+            if ($publication->timechecked > 1273127954) {
+                if ($publication->status == 0) {
+                    $status = get_string('statusunpublished', 'hub');
+                } else {
+                    $status = get_string('statuspublished', 'hub');
+                }
+
+                $status .= $brtag . html_writer::tag('a', get_string('updatestatus', 'hub'),
+                                array('href' => $CFG->wwwroot . '/course/publish/index.php?id='
+                                    . $courseid . "&updatestatusid=" . $publication->id
+                                    . "&sesskey=" . sesskey())) .
+                        $brtag . get_string('lasttimechecked', 'hub') . ": "
+                        . format_time(time() - $publication->timechecked);
+            } else {
+                $status = get_string('neverchecked', 'hub') . $brtag
+                        . html_writer::tag('a', get_string('updatestatus', 'hub'),
+                                array('href' => $CFG->wwwroot . '/course/publish/index.php?id='
+                                    . $courseid . "&updatestatusid=" . $publication->id
+                                    . "&sesskey=" . sesskey()));
+            }
+            //add button cells
+            $cells = array($publication->enrollable ?
+                        get_string('advertised', 'hub') : get_string('shared', 'hub'),
+                $hubname, userdate($publication->timepublished,
+                        get_string('strftimedatetimeshort')), $status, $operations);
+            $row = new html_table_row($cells);
+            $table->data[] = $row;
+        }
+
+        $contenthtml = html_writer::table($table);
+
+        return $contenthtml;
+    }
+
+    /**
+     * Display unpublishing confirmation page
+     * @param object $publication
+     *      $publication->courseshortname
+      $publication->courseid
+      $publication->hubname
+      $publication->huburl
+      $publication->id
+     */
+    public function confirmunpublishing($publication) {
+        $optionsyes = array('sesskey' => sesskey(), 'id' => $publication->courseid,
+            'hubcourseid' => $publication->hubcourseid,
+            'huburl' => $publication->huburl, 'hubname' => $publication->hubname,
+            'cancel' => true, 'publicationid' => $publication->id, 'confirm' => true);
+        $optionsno = array('sesskey' => sesskey(), 'id' => $publication->courseid);
+        $publication->hubname = html_writer::tag('a', $publication->hubname,
+                        array('href' => $publication->huburl));
+        $formcontinue = new single_button(new moodle_url("/course/publish/index.php",
+                                $optionsyes), get_string('unpublish', 'hub'), 'post');
+        $formcancel = new single_button(new moodle_url("/course/publish/index.php",
+                                $optionsno), get_string('cancel'), 'get');
+        return $this->output->confirm(get_string('unpublishconfirmation', 'hub', $publication),
+                $formcontinue, $formcancel);
+    }
+
+    /**
+     * Display waiting information about backup size during uploading backup process
+     * @param object $backupfile the backup stored_file
+     * @return $html string
+     */
+    public function sendingbackupinfo($backupfile) {
+        $sizeinfo = new stdClass();
+        $sizeinfo->total = number_format($backupfile->get_filesize() / 1000000, 2);
+        $html = html_writer::tag('div', get_string('sendingsize', 'hub', $sizeinfo),
+                        array('class' => 'courseuploadtextinfo'));
+        return $html;
+    }
+
+    /**
+     * Display upload successfull message and a button to the publish index page
+     * @param int $id the course id
+     * @param string $huburl the hub url where the course is published
+     * @param string $hubname the hub name where the course is published
+     * @return $html string
+     */
+    public function sentbackupinfo($id, $huburl, $hubname) {
+        $html = html_writer::tag('div', get_string('sent', 'hub'),
+                        array('class' => 'courseuploadtextinfo'));
+        $publishindexurl = new moodle_url('/course/publish/index.php',
+                        array('sesskey' => sesskey(), 'id' => $id,
+                            'published' => true, 'huburl' => $huburl, 'hubname' => $hubname));
+        $continue = $this->output->render(
+                        new single_button($publishindexurl, get_string('continue', 'hub')));
+        $html .= html_writer::tag('div', $continue, array('class' => 'sharecoursecontinue'));
+        return $html;
+    }
+
+    /**
+     * Hub information (logo - name - description - link)
+     * @param object $hubinfo
+     * @return string html code
+     */
+    public function hubinfo($hubinfo) {
+        $params = array('filetype' => HUB_HUBSCREENSHOT_FILE_TYPE);
+        $imgurl = new moodle_url($hubinfo['url'] .
+                        "/local/hub/webservice/download.php", $params);
+        $screenshothtml = html_writer::empty_tag('img',
+                        array('src' => $imgurl, 'alt' => $hubinfo['name']));
+        $hubdescription = html_writer::tag('div', $screenshothtml,
+                        array('class' => 'hubscreenshot'));
+
+        $hubdescription .= html_writer::tag('a', $hubinfo['name'],
+                        array('class' => 'hublink', 'href' => $hubinfo['url'],
+                            'onclick' => 'this.target="_blank"'));
+
+        $hubdescription .= html_writer::tag('div', format_text($hubinfo['description'], FORMAT_PLAIN),
+                        array('class' => 'hubdescription'));
+        $hubdescription = html_writer::tag('div', $hubdescription, array('class' => 'hubinfo'));
+
+        return $hubdescription;
+    }
+
+}
diff --git a/recent.php b/recent.php
new file mode 100644 (file)
index 0000000..df0f0d5
--- /dev/null
@@ -0,0 +1,294 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Display all recent activity in a flexible way
+ *
+ * @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('recent_form.php');
+
+$id = required_param('id', PARAM_INT);
+
+$PAGE->set_url('/course/recent.php', array('id'=>$id));
+
+if (!$course = $DB->get_record('course', array('id'=>$id))) {
+    print_error("That's an invalid course id");
+}
+
+require_login($course);
+
+add_to_log($course->id, "course", "recent", "recent.php?id=$course->id", $course->id);
+
+$context = context_course::instance($course->id);
+
+$lastlogin = time() - COURSE_MAX_RECENT_PERIOD;
+if (!isguestuser() and !empty($USER->lastcourseaccess[$COURSE->id])) {
+    if ($USER->lastcourseaccess[$COURSE->id] > $lastlogin) {
+        $lastlogin = $USER->lastcourseaccess[$COURSE->id];
+    }
+}
+
+$param = new stdClass();
+$param->user   = 0;
+$param->modid  = 'all';
+$param->group  = 0;
+$param->sortby = 'default';
+$param->date   = $lastlogin;
+$param->id     = $COURSE->id;
+
+$mform = new recent_form();
+$mform->set_data($param);
+if ($formdata = $mform->get_data()) {
+    $param = $formdata;
+}
+
+$userinfo = get_string('allparticipants');
+$dateinfo = get_string('alldays');
+
+if (!empty($param->user)) {
+    if (!$u = $DB->get_record('user', array('id'=>$param->user))) {
+        print_error("That's an invalid user!");
+    }
+    $userinfo = fullname($u);
+}
+
+$strrecentactivity = get_string('recentactivity');
+$PAGE->navbar->add($strrecentactivity, new moodle_url('/course/recent.php', array('id'=>$course->id)));
+$PAGE->navbar->add($userinfo);
+$PAGE->set_title("$course->shortname: $strrecentactivity");
+$PAGE->set_heading($course->fullname);
+echo $OUTPUT->header();
+echo $OUTPUT->heading(format_string($course->fullname) . ": $userinfo", 2);
+
+$mform->display();
+
+$modinfo = get_fast_modinfo($course);
+$modnames = get_module_types_names();
+
+if (has_capability('moodle/course:viewhiddensections', $context)) {
+    $hiddenfilter = "";
+} else {
+    $hiddenfilter = "AND cs.visible = 1";
+}
+$sections = array();
+foreach ($modinfo->get_section_info_all() as $i => $section) {
+    if (!empty($section->uservisible)) {
+        $sections[$i] = $section;
+    }
+}
+
+if ($param->modid === 'all') {
+    // ok
+
+} else if (strpos($param->modid, 'mod/') === 0) {
+    $modname = substr($param->modid, strlen('mod/'));
+    if (array_key_exists($modname, $modnames) and file_exists("$CFG->dirroot/mod/$modname/lib.php")) {
+        $filter = $modname;
+    }
+
+} else if (strpos($param->modid, 'section/') === 0) {
+    $sectionid = substr($param->modid, strlen('section/'));
+    if (isset($sections[$sectionid])) {
+        $sections = array($sectionid=>$sections[$sectionid]);
+    }
+
+} else if (is_numeric($param->modid)) {
+    $sectionnum = $modinfo->cms[$param->modid]->sectionnum;
+    $filter_modid = $param->modid;
+    $sections = array($sectionnum => $sections[$sectionnum]);
+}
+
+
+$modinfo->get_groups(); // load all my groups and cache it in modinfo
+
+$activities = array();
+$index = 0;
+
+foreach ($sections as $sectionnum => $section) {
+
+    $activity = new stdClass();
+    $activity->type = 'section';
+    if ($section->section > 0) {
+        $activity->name = get_section_name($course, $section);
+    } else {
+        $activity->name = '';
+    }
+
+    $activity->visible = $section->visible;
+    $activities[$index++] = $activity;
+
+    if (empty($modinfo->sections[$sectionnum])) {
+        continue;
+    }
+
+    foreach ($modinfo->sections[$sectionnum] as $cmid) {
+        $cm = $modinfo->cms[$cmid];
+
+        if (!$cm->uservisible) {
+            continue;
+        }
+
+        if (!empty($filter) and $cm->modname != $filter) {
+            continue;
+        }
+
+        if (!empty($filter_modid) and $cmid != $filter_modid) {
+            continue;
+        }
+
+        $libfile = "$CFG->dirroot/mod/$cm->modname/lib.php";
+
+        if (file_exists($libfile)) {
+            require_once($libfile);
+            $get_recent_mod_activity = $cm->modname."_get_recent_mod_activity";
+
+            if (function_exists($get_recent_mod_activity)) {
+                $activity = new stdClass();
+                $activity->type    = 'activity';
+                $activity->cmid    = $cmid;
+                $activities[$index++] = $activity;
+                $get_recent_mod_activity($activities, $index, $param->date, $course->id, $cmid, $param->user, $param->group);
+            }
+        }
+    }
+}
+
+$detail = true;
+
+switch ($param->sortby) {
+    case 'datedesc' : usort($activities, 'compare_activities_by_time_desc'); break;
+    case 'dateasc'  : usort($activities, 'compare_activities_by_time_asc'); break;
+    case 'default'  :
+    default         : $detail = false; $param->sortby = 'default';
+
+}
+
+if (!empty($activities)) {
+
+    $newsection   = true;
+    $lastsection  = '';
+    $newinstance  = true;
+    $lastinstance = '';
+    $inbox        = false;
+
+    $section = 0;
+
+    $activity_count = count($activities);
+    $viewfullnames  = array();
+
+    foreach ($activities as $key => $activity) {
+
+        if ($activity->type == 'section') {
+            if ($param->sortby != 'default') {
+                continue; // no section if ordering by date
+            }
+            if ($activity_count == ($key + 1) or $activities[$key+1]->type == 'section') {
+            // peak at next activity.  If it's another section, don't print this one!
+            // this means there are no activities in the current section
+                continue;
+            }
+        }
+
+        if (($activity->type == 'section') && ($param->sortby == 'default')) {
+            if ($inbox) {
+                echo $OUTPUT->box_end();
+                echo $OUTPUT->spacer(array('height'=>30, 'br'=>true)); // should be done with CSS instead
+            }
+            echo $OUTPUT->box_start();
+            if (!empty($activity->name)) {
+                echo html_writer::tag('h2', $activity->name);
+            }
+            $inbox = true;
+
+        } else if ($activity->type == 'activity') {
+
+            if ($param->sortby == 'default') {
+                $cm = $modinfo->cms[$activity->cmid];
+
+                if ($cm->visible) {
+                    $class = '';
+                } else {
+                    $class = 'dimmed';
+                }
+                $name        = format_string($cm->name);
+                $modfullname = $modnames[$cm->modname];
+
+                $image = $OUTPUT->pix_icon('icon', $modfullname, $cm->modname, array('class' => 'icon smallicon'));
+                $link = html_writer::link(new moodle_url("/mod/$cm->modname/view.php",
+                            array("id" => $cm->id)), $name, array('class' => $class));
+                echo html_writer::tag('h3', "$image $modfullname $link");
+           }
+
+        } else {
+
+            if (!isset($viewfullnames[$activity->cmid])) {
+                $cm_context = context_module::instance($activity->cmid);
+                $viewfullnames[$activity->cmid] = has_capability('moodle/site:viewfullnames', $cm_context);
+            }
+
+            if (!$inbox) {
+                echo $OUTPUT->box_start();
+                $inbox = true;
+            }
+
+            $print_recent_mod_activity = $activity->type.'_print_recent_mod_activity';
+
+            if (function_exists($print_recent_mod_activity)) {
+                $print_recent_mod_activity($activity, $course->id, $detail, $modnames, $viewfullnames[$activity->cmid]);
+            }
+        }
+    }
+
+    if ($inbox) {
+        echo $OUTPUT->box_end();
+    }
+
+
+} else {
+
+    echo html_writer::tag('h3', get_string('norecentactivity'), array('class' => 'mdl-align'));
+
+}
+
+echo $OUTPUT->footer();
+
+function compare_activities_by_time_desc($a, $b) {
+    // make sure the activities actually have a timestamp property
+    if ((!array_key_exists('timestamp', $a)) or (!array_key_exists('timestamp', $b))) {
+      return 0;
+    }
+    if ($a->timestamp == $b->timestamp)
+        return 0;
+    return ($a->timestamp > $b->timestamp) ? -1 : 1;
+}
+
+function compare_activities_by_time_asc($a, $b) {
+    // make sure the activities actually have a timestamp property
+    if ((!array_key_exists('timestamp', $a)) or (!array_key_exists('timestamp', $b))) {
+      return 0;
+    }
+    if ($a->timestamp == $b->timestamp)
+        return 0;
+    return ($a->timestamp < $b->timestamp) ? -1 : 1;
+}
+
diff --git a/recent_form.php b/recent_form.php
new file mode 100644 (file)
index 0000000..cfdbffa
--- /dev/null
@@ -0,0 +1,169 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Display all recent activity in a flexible way
+ *
+ * @copyright 1999 Martin Dougiamas  http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->libdir.'/formslib.php');
+
+class recent_form extends moodleform {
+    function definition() {
+        global $CFG, $COURSE, $USER;
+
+        $mform =& $this->_form;
+        $context = context_course::instance($COURSE->id);
+        $modinfo = get_fast_modinfo($COURSE);
+
+        $mform->addElement('header', 'filters', get_string('managefilters')); //TODO: add better string
+
+        $groupoptions = array();
+        if (groups_get_course_groupmode($COURSE) == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context)) {
+            // limited group access
+            $groups = groups_get_user_groups($COURSE->id);
+            $allgroups = groups_get_all_groups($COURSE->id);
+            if (!empty($groups[$COURSE->defaultgroupingid])) {
+                foreach ($groups[$COURSE->defaultgroupingid] AS $groupid) {
+                    $groupoptions[$groupid] = format_string($allgroups[$groupid]->name, true, array('context'=>$context));
+                }
+            }
+        } else {
+            $groupoptions = array('0'=>get_string('allgroups'));
+            if (has_capability('moodle/site:accessallgroups', $context)) {
+                // user can see all groups
+                $allgroups = groups_get_all_groups($COURSE->id);
+            } else {
+                // user can see course level groups
+                $allgroups = groups_get_all_groups($COURSE->id, 0, $COURSE->defaultgroupingid);
+            }
+            foreach($allgroups as $group) {
+                $groupoptions[$group->id] = format_string($group->name, true, array('context'=>$context));
+            }
+        }
+
+        if ($COURSE->id == SITEID) {
+            $viewparticipants = has_capability('moodle/site:viewparticipants', context_system::instance());
+        } else {
+            $viewparticipants = has_capability('moodle/course:viewparticipants', $context);
+        }
+
+        if ($viewparticipants) {
+            $viewfullnames = has_capability('moodle/site:viewfullnames', context_course::instance($COURSE->id));
+
+            $options = array();
+            $options[0] = get_string('allparticipants');
+            $options[$CFG->siteguest] = get_string('guestuser');
+
+            if (isset($groupoptions[0])) {
+                // can see all enrolled users
+                if ($enrolled = get_enrolled_users($context, null, 0, user_picture::fields('u'))) {
+                    foreach ($enrolled as $euser) {
+                        $options[$euser->id] = fullname($euser, $viewfullnames);
+                    }
+                }
+            } else {
+                // can see users from some groups only
+                foreach ($groupoptions as $groupid=>$unused) {
+                    if ($enrolled = get_enrolled_users($context, null, $groupid, user_picture::fields('u'))) {
+                        foreach ($enrolled as $euser) {
+                            if (!array_key_exists($euser->id, $options)) {
+                                $options[$euser->id] = fullname($euser, $viewfullnames);
+                            }
+                        }
+                    }
+                }
+            }
+
+            $mform->addElement('select', 'user', get_string('participants'), $options);
+            $mform->setAdvanced('user');
+        } else {
+            // Default to no user.
+            $mform->addElement('hidden', 'user', 0);
+        }
+
+        $options = array(''=>get_string('allactivities'));
+        $modsused = array();
+
+        foreach($modinfo->cms as $cm) {
+            if (!$cm->uservisible) {
+                continue;
+            }
+            $modsused[$cm->modname] = true;
+        }
+
+        foreach ($modsused as $modname=>$unused) {
+            $libfile = "$CFG->dirroot/mod/$modname/lib.php";
+            if (!file_exists($libfile)) {
+                unset($modsused[$modname]);
+                continue;
+            }
+            include_once($libfile);
+            $libfunction = $modname."_get_recent_mod_activity";
+            if (!function_exists($libfunction)) {
+                unset($modsused[$modname]);
+                continue;
+            }
+            $options["mod/$modname"] = get_string('allmods', '', get_string('modulenameplural', $modname));
+        }
+
+        foreach ($modinfo->sections as $section=>$cmids) {
+            $options["section/$section"] = "-- ".get_section_name($COURSE, $section)." --";
+            foreach ($cmids as $cmid) {
+                $cm = $modinfo->cms[$cmid];
+                if (empty($modsused[$cm->modname]) or !$cm->uservisible) {
+                    continue;
+                }
+                $options[$cm->id] = format_string($cm->name);
+            }
+        }
+        $mform->addElement('select', 'modid', get_string('activities'), $options);
+        $mform->setAdvanced('modid');
+
+
+        if ($groupoptions) {
+            $mform->addElement('select', 'group', get_string('groups'), $groupoptions);
+            $mform->setAdvanced('group');
+        } else {
+            // no access to groups in separate mode
+            $mform->addElement('hidden','group');
+            $mform->setType('group', PARAM_INT);
+            $mform->setConstants(array('group'=>-1));
+        }
+
+        $options = array('default'  => get_string('bycourseorder'),
+                         'dateasc'  => get_string('datemostrecentlast'),
+                         'datedesc' => get_string('datemostrecentfirst'));
+        $mform->addElement('select', 'sortby', get_string('sortby'), $options);
+        $mform->setAdvanced('sortby');
+
+        $mform->addElement('date_time_selector', 'date', get_string('since'), array('optional'=>true));
+
+        $mform->addElement('hidden','id');
+        $mform->setType('id', PARAM_INT);
+        $mform->setType('courseid', PARAM_INT);
+
+        $this->add_action_buttons(false, get_string('showrecent'));
+    }
+}
diff --git a/renderer.php b/renderer.php
new file mode 100644 (file)
index 0000000..33c15b4
--- /dev/null
@@ -0,0 +1,357 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Renderer for use with the course section and all the goodness that falls
+ * within it.
+ *
+ * This renderer should contain methods useful to courses, and categories.
+ *
+ * @package   moodlecore
+ * @copyright 2010 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * The core course renderer
+ *
+ * Can be retrieved with the following:
+ * $renderer = $PAGE->get_renderer('core','course');
+ */
+class core_course_renderer extends plugin_renderer_base {
+
+    /**
+     * A cache of strings
+     * @var stdClass
+     */
+    protected $strings;
+
+    /**
+     * Override the constructor so that we can initialise the string cache
+     *
+     * @param moodle_page $page
+     * @param string $target
+     */
+    public function __construct(moodle_page $page, $target) {
+        $this->strings = new stdClass;
+        parent::__construct($page, $target);
+    }
+
+    /**
+     * Renders course info box.
+     *
+     * @param stdClass $course
+     * @return string
+     */
+    public function course_info_box(stdClass $course) {
+        global $CFG;
+
+        $context = context_course::instance($course->id);
+
+        $content = '';
+        $content .= $this->output->box_start('generalbox info');
+
+        $summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', null);
+        $content .= format_text($summary, $course->summaryformat, array('overflowdiv'=>true), $course->id);
+        if (!empty($CFG->coursecontact)) {
+            $coursecontactroles = explode(',', $CFG->coursecontact);
+            foreach ($coursecontactroles as $roleid) {
+                if ($users = get_role_users($roleid, $context, true)) {
+                    foreach ($users as $teacher) {
+                        $role = new stdClass();
+                        $role->id = $teacher->roleid;
+                        $role->name = $teacher->rolename;
+                        $role->shortname = $teacher->roleshortname;
+                        $role->coursealias = $teacher->rolecoursealias;
+                        $fullname = fullname($teacher, has_capability('moodle/site:viewfullnames', $context));
+                        $namesarray[] = role_get_name($role, $context).': <a href="'.$CFG->wwwroot.'/user/view.php?id='.
+                            $teacher->id.'&amp;course='.SITEID.'">'.$fullname.'</a>';
+                    }
+                }
+            }
+
+            if (!empty($namesarray)) {
+                $content .= "<ul class=\"teachers\">\n<li>";
+                $content .= implode('</li><li>', $namesarray);
+                $content .= "</li></ul>";
+            }
+        }
+
+        $content .= $this->output->box_end();
+
+        return $content;
+    }
+
+    /**
+     * Renderers a structured array of courses and categories into a nice
+     * XHTML tree structure.
+     *
+     * This method was designed initially to display the front page course/category
+     * combo view. The structure can be retrieved by get_course_category_tree()
+     *
+     * @param array $structure
+     * @return string
+     */
+    public function course_category_tree(array $structure) {
+        $this->strings->summary = get_string('summary');
+
+        // Generate an id and the required JS call to make this a nice widget
+        $id = html_writer::random_id('course_category_tree');
+        $this->page->requires->js_init_call('M.util.init_toggle_class_on_click', array($id, '.category.with_children .category_label', 'collapsed', '.category.with_children'));
+
+        // Start content generation
+        $content = html_writer::start_tag('div', array('class'=>'course_category_tree', 'id'=>$id));
+        foreach ($structure as $category) {
+            $content .= $this->course_category_tree_category($category);
+        }
+        $content .= html_writer::start_tag('div', array('class'=>'controls'));
+        $content .= html_writer::tag('div', get_string('collapseall'), array('class'=>'addtoall expandall'));
+        $content .= html_writer::tag('div', get_string('expandall'), array('class'=>'removefromall collapseall'));
+        $content .= html_writer::end_tag('div');
+        $content .= html_writer::end_tag('div');
+
+        // Return the course category tree HTML
+        return $content;
+    }
+
+    /**
+     * Renderers a category for use with course_category_tree
+     *
+     * @param array $category
+     * @param int $depth
+     * @return string
+     */
+    protected function course_category_tree_category(stdClass $category, $depth=1) {
+        $content = '';
+        $hassubcategories = (isset($category->categories) && count($category->categories)>0);
+        $hascourses = (isset($category->courses) && count($category->courses)>0);
+        $classes = array('category');
+        if ($category->parent != 0) {
+            $classes[] = 'subcategory';
+        }
+        if (empty($category->visible)) {
+            $classes[] = 'dimmed_category';
+        }
+        if ($hassubcategories || $hascourses) {
+            $classes[] = 'with_children';
+            if ($depth > 1) {
+                $classes[] = 'collapsed';
+            }
+        }
+        $categoryname = format_string($category->name, true, array('context' => context_coursecat::instance($category->id)));
+
+        $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
+        $content .= html_writer::start_tag('div', array('class'=>'category_label'));
+        $content .= html_writer::link(new moodle_url('/course/category.php', array('id'=>$category->id)), $categoryname, array('class'=>'category_link'));
+        $content .= html_writer::end_tag('div');
+        if ($hassubcategories) {
+            $content .= html_writer::start_tag('div', array('class'=>'subcategories'));
+            foreach ($category->categories as $subcategory) {
+                $content .= $this->course_category_tree_category($subcategory, $depth+1);
+            }
+            $content .= html_writer::end_tag('div');
+        }
+        if ($hascourses) {
+            $content .= html_writer::start_tag('div', array('class'=>'courses'));
+            $coursecount = 0;
+            $strinfo = new lang_string('info');
+            foreach ($category->courses as $course) {
+                $classes = array('course');
+                $linkclass = 'course_link';
+                if (!$course->visible) {
+                    $linkclass .= ' dimmed';
+                }
+                $coursecount ++;
+                $classes[] = ($coursecount%2)?'odd':'even';
+                $content .= html_writer::start_tag('div', array('class'=>join(' ', $classes)));
+                $content .= html_writer::link(new moodle_url('/course/view.php', array('id'=>$course->id)), format_string($course->fullname), array('class'=>$linkclass));
+                $content .= html_writer::start_tag('div', array('class'=>'course_info clearfix'));
+
+                // print enrol info
+                if ($icons = enrol_get_course_info_icons($course)) {
+                    foreach ($icons as $pix_icon) {
+                        $content .= $this->render($pix_icon);
+                    }
+                }
+
+                if ($course->summary) {
+                    $url = new moodle_url('/course/info.php', array('id' => $course->id));
+                    $image = html_writer::empty_tag('img', array('src'=>$this->output->pix_url('i/info'), 'alt'=>$this->strings->summary));
+                    $content .= $this->action_link($url, $image, new popup_action('click', $url, 'courseinfo'), array('title' => $this->strings->summary));
+                }
+                $content .= html_writer::end_tag('div');
+                $content .= html_writer::end_tag('div');
+            }
+            $content .= html_writer::end_tag('div');
+        }
+        $content .= html_writer::end_tag('div');
+        return $content;
+    }
+
+    /**
+     * Build the HTML for the module chooser javascript popup
+     *
+     * @param array $modules A set of modules as returned form @see
+     * get_module_metadata
+     * @param object $course The course that will be displayed
+     * @return string The composed HTML for the module
+     */
+    public function course_modchooser($modules, $course) {
+        global $OUTPUT;
+
+        // Add the header
+        $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
+                array('class' => 'hd choosertitle'));
+
+        $formcontent = html_writer::start_tag('form', array('action' => new moodle_url('/course/jumpto.php'),
+                'id' => 'chooserform', 'method' => 'post'));
+        $formcontent .= html_writer::start_tag('div', array('id' => 'typeformdiv'));
+        $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'id' => 'course',
+                'name' => 'course', 'value' => $course->id));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'hidden', 'class' => 'jump', 'name' => 'jump', 'value' => ''));
+        $formcontent .= html_writer::tag('input', '', array('type' => 'hidden', 'name' => 'sesskey',
+                'value' => sesskey()));
+        $formcontent .= html_writer::end_tag('div');
+
+        // Put everything into one tag 'options'
+        $formcontent .= html_writer::start_tag('div', array('class' => 'options'));
+        $formcontent .= html_writer::tag('div', get_string('selectmoduletoviewhelp', 'moodle'),
+                array('class' => 'instruction'));
+        // Put all options into one tag 'alloptions' to allow us to handle scrolling
+        $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
+
+         // Activities
+        $activities = array_filter($modules, function($mod) {
+            return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
+        });
+        if (count($activities)) {
+            $formcontent .= $this->course_modchooser_title('activities');
+            $formcontent .= $this->course_modchooser_module_types($activities);
+        }
+
+        // Resources
+        $resources = array_filter($modules, function($mod) {
+            return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
+        });
+        if (count($resources)) {
+            $formcontent .= $this->course_modchooser_title('resources');
+            $formcontent .= $this->course_modchooser_module_types($resources);
+        }
+
+        $formcontent .= html_writer::end_tag('div'); // modoptions
+        $formcontent .= html_writer::end_tag('div'); // types
+
+        $formcontent .= html_writer::start_tag('div', array('class' => 'submitbuttons'));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'submit', 'name' => 'submitbutton', 'class' => 'submitbutton', 'value' => get_string('add')));
+        $formcontent .= html_writer::tag('input', '',
+                array('type' => 'submit', 'name' => 'addcancel', 'class' => 'addcancel', 'value' => get_string('cancel')));
+        $formcontent .= html_writer::end_tag('div');
+        $formcontent .= html_writer::end_tag('form');
+
+        // Wrap the whole form in a div
+        $formcontent = html_writer::tag('div', $formcontent, array('id' => 'chooseform'));
+
+        // Put all of the content together
+        $content = $formcontent;
+
+        $content = html_writer::tag('div', $content, array('class' => 'choosercontainer'));
+        return $header . html_writer::tag('div', $content, array('class' => 'chooserdialoguebody'));
+    }
+
+    /**
+     * Build the HTML for a specified set of modules
+     *
+     * @param array $modules A set of modules as used by the
+     * course_modchooser_module function
+     * @return string The composed HTML for the module
+     */
+    protected function course_modchooser_module_types($modules) {
+        $return = '';
+        foreach ($modules as $module) {
+            if (!isset($module->types)) {
+                $return .= $this->course_modchooser_module($module);
+            } else {
+                $return .= $this->course_modchooser_module($module, array('nonoption'));
+                foreach ($module->types as $type) {
+                    $return .= $this->course_modchooser_module($type, array('option', 'subtype'));
+                }
+            }
+        }
+        return $return;
+    }
+
+    /**
+     * Return the HTML for the specified module adding any required classes
+     *
+     * @param object $module An object containing the title, and link. An
+     * icon, and help text may optionally be specified. If the module
+     * contains subtypes in the types option, then these will also be
+     * displayed.
+     * @param array $classes Additional classes to add to the encompassing
+     * div element
+     * @return string The composed HTML for the module
+     */
+    protected function course_modchooser_module($module, $classes = array('option')) {
+        $output = '';
+        $output .= html_writer::start_tag('div', array('class' => implode(' ', $classes)));
+        $output .= html_writer::start_tag('label', array('for' => 'module_' . $module->name));
+        if (!isset($module->types)) {
+            $output .= html_writer::tag('input', '', array('type' => 'radio',
+                    'name' => 'jumplink', 'id' => 'module_' . $module->name, 'value' => $module->link));
+        }
+
+        $output .= html_writer::start_tag('span', array('class' => 'modicon'));
+        if (isset($module->icon)) {
+            // Add an icon if we have one
+            $output .= $module->icon;
+        }
+        $output .= html_writer::end_tag('span');
+
+        $output .= html_writer::tag('span', $module->title, array('class' => 'typename'));
+        if (!isset($module->help)) {
+            // Add help if found
+            $module->help = get_string('nohelpforactivityorresource', 'moodle');
+        }
+
+        // Format the help text using markdown with the following options
+        $options = new stdClass();
+        $options->trusted = false;
+        $options->noclean = false;
+        $options->smiley = false;
+        $options->filter = false;
+        $options->para = true;
+        $options->newlines = false;
+        $options->overflowdiv = false;
+        $module->help = format_text($module->help, FORMAT_MARKDOWN, $options);
+        $output .= html_writer::tag('span', $module->help, array('class' => 'typesummary'));
+        $output .= html_writer::end_tag('label');
+        $output .= html_writer::end_tag('div');
+
+        return $output;
+    }
+
+    protected function course_modchooser_title($title, $identifier = null) {
+        $module = new stdClass();
+        $module->name = $title;
+        $module->types = array();
+        $module->title = get_string($title, $identifier);
+        $module->help = '';
+        return $this->course_modchooser_module($module, array('moduletypetitle'));
+    }
+}
diff --git a/report.php b/report.php
new file mode 100644 (file)
index 0000000..5cf8d7a
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+      // Display all the interfaces for importing data into a specific course
+
+    require_once('../config.php');
+
+    $id = required_param('id', PARAM_INT);   // course id to import TO
+    $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+
+    $PAGE->set_pagelayout('standard');
+    require_login($course);
+
+    $context = context_course::instance($course->id);
+    require_capability('moodle/site:viewreports', $context); // basic capability for listing of reports
+
+    $strreports = get_string('reports');
+
+    $PAGE->set_url(new moodle_url('/course/report.php', array('id'=>$id)));
+    $PAGE->set_title($course->fullname.': '.$strreports);
+    $PAGE->set_heading($course->fullname.': '.$strreports);
+    echo $OUTPUT->header();
+
+    $reports = get_plugin_list('coursereport');
+
+    foreach ($reports as $report => $reportdirectory) {
+        $pluginfile = $reportdirectory.'/mod.php';
+        if (file_exists($pluginfile)) {
+            ob_start();
+            include($pluginfile);  // Fragment for listing
+            $html = ob_get_contents();
+            ob_end_clean();
+            // add div only if plugin accessible
+            if ($html !== '') {
+                echo '<div class="plugin">';
+                echo $html;
+                echo '</div>';
+            }
+        }
+    }
+
+    echo $OUTPUT->footer();
+
diff --git a/report/lib.php b/report/lib.php
new file mode 100644 (file)
index 0000000..3ed43f7
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains functions used by course reports
+ *
+ * @since 2.1
+ * @package course-report
+ * @copyright 2011 Andrew Davis
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+/**
+ * Return a list of page types
+ * @param string $pagetype current page type
+ * @param stdClass $parentcontext Block's parent context
+ * @param stdClass $currentcontext Current context of block
+ */
+function coursereport_page_type_list($pagetype, $parentcontext, $currentcontext) {
+    $array = array(
+        '*' => get_string('page-x', 'pagetype'),
+        'course-report-*' => get_string('page-course-report-x', 'pagetype')
+    );
+    return $array;
+}
\ No newline at end of file
diff --git a/request.php b/request.php
new file mode 100644 (file)
index 0000000..bf6fefc
--- /dev/null
@@ -0,0 +1,73 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Allows a user to request a course be created for them.
+ *
+ * @copyright 1999 Martin Dougiamas  http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require_once(dirname(__FILE__) . '/../config.php');
+require_once($CFG->dirroot . '/course/lib.php');
+require_once($CFG->dirroot . '/course/request_form.php');
+
+$PAGE->set_url('/course/request.php');
+
+/// Where we came from. Used in a number of redirects.
+$returnurl = $CFG->wwwroot . '/course/index.php';
+
+
+/// Check permissions.
+require_login();
+if (isguestuser()) {
+    print_error('guestsarenotallowed', '', $returnurl);
+}
+if (empty($CFG->enablecourserequests)) {
+    print_error('courserequestdisabled', '', $returnurl);
+}
+$context = context_system::instance();
+$PAGE->set_context($context);
+require_capability('moodle/course:request', $context);
+
+/// Set up the form.
+$data = course_request::prepare();
+$requestform = new course_request_form($CFG->wwwroot . '/course/request.php', compact('editoroptions'));
+$requestform->set_data($data);
+
+$strtitle = get_string('courserequest');
+$PAGE->set_title($strtitle);
+$PAGE->set_heading($strtitle);
+
+/// Standard form processing if statement.
+if ($requestform->is_cancelled()){
+    redirect($returnurl);
+
+} else if ($data = $requestform->get_data()) {
+    $request = course_request::create($data);
+
+    // and redirect back to the course listing.
+    notice(get_string('courserequestsuccess'), $returnurl);
+}
+
+$PAGE->navbar->add($strtitle);
+echo $OUTPUT->header();
+echo $OUTPUT->heading($strtitle);
+// Show the request form.
+$requestform->display();
+echo $OUTPUT->footer();
diff --git a/request_form.php b/request_form.php
new file mode 100644 (file)
index 0000000..867a684
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// NOTICE OF COPYRIGHT                                                   //
+//                                                                       //
+// Moodle - Modular Object-Oriented Dynamic Learning Environment         //
+//          http://moodle.org                                            //
+//                                                                       //
+// Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
+//                                                                       //
+// This program 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 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program 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:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+/**
+ * Forms associated with requesting courses, and having requests approved.
+ * Note that several related forms are defined in this one file.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
+ * @package course
+ */
+
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once($CFG->libdir.'/formslib.php');
+
+/**
+ * A form for a user to request a course.
+ */
+class course_request_form extends moodleform {
+    function definition() {
+        global $CFG, $DB, $USER;
+
+        $mform =& $this->_form;
+
+        if ($pending = $DB->get_records('course_request', array('requester' => $USER->id))) {
+            $mform->addElement('header', 'pendinglist', get_string('coursespending'));
+            $list = array();
+            foreach ($pending as $cp) {
+                $list[] = format_string($cp->fullname);
+            }
+            $list = implode(', ', $list);
+            $mform->addElement('static', 'pendingcourses', get_string('courses'), $list);
+        }
+
+        $mform->addElement('header','coursedetails', get_string('courserequestdetails'));
+
+        $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);
+
+        $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($CFG->requestcategoryselection)) {
+            $displaylist = array();
+            $parentlist = array();
+            make_categories_list($displaylist, $parentlist, '');
+            $mform->addElement('select', 'category', get_string('category'), $displaylist);
+            $mform->setDefault('category', $CFG->defaultrequestcategory);
+            $mform->addHelpButton('category', 'category');
+        }
+
+        $mform->addElement('editor', 'summary_editor', get_string('summary'), null, course_request::summary_editor_options());
+        $mform->addHelpButton('summary_editor', 'coursesummary');
+        $mform->setType('summary_editor', PARAM_RAW);
+
+        $mform->addElement('header','requestreason', get_string('courserequestreason'));
+
+        $mform->addElement('textarea', 'reason', get_string('courserequestsupport'), array('rows'=>'15', 'cols'=>'50'));
+        $mform->addRule('reason', get_string('missingreqreason'), 'required', null, 'client');
+        $mform->setType('reason', PARAM_TEXT);
+
+        $this->add_action_buttons(true, get_string('requestcourse'));
+    }
+
+    function validation($data, $files) {
+        global $DB;
+
+        $errors = parent::validation($data, $files);
+        $foundcourses = null;
+        $foundreqcourses = null;
+
+        if (!empty($data['shortname'])) {
+            $foundcourses = $DB->get_records('course', array('shortname'=>$data['shortname']));
+            $foundreqcourses = $DB->get_records('course_request', array('shortname'=>$data['shortname']));
+        }
+        if (!empty($foundreqcourses)) {
+            if (!empty($foundcourses)) {
+                $foundcourses = array_merge($foundcourses, $foundreqcourses);
+            } else {
+                $foundcourses = $foundreqcourses;
+            }
+        }
+
+        if (!empty($foundcourses)) {
+            foreach ($foundcourses as $foundcourse) {
+                if (!empty($foundcourse->requester)) {
+                    $pending = 1;
+                    $foundcoursenames[] = $foundcourse->fullname.' [*]';
+                } else {
+                    $foundcoursenames[] = $foundcourse->fullname;
+                }
+            }
+            $foundcoursenamestring = implode(',', $foundcoursenames);
+
+            $errors['shortname'] = get_string('shortnametaken', '', $foundcoursenamestring);
+            if (!empty($pending)) {
+                $errors['shortname'] .= get_string('starpending');
+            }
+        }
+
+        return $errors;
+    }
+}
+
+/**
+ * A form for an administrator to reject a course request.
+ */
+class reject_request_form extends moodleform {
+    function definition() {
+        $mform =& $this->_form;
+
+        $mform->addElement('hidden', 'reject', 0);
+        $mform->setType('reject', PARAM_INT);
+
+        $mform->addElement('header','coursedetails', get_string('coursereasonforrejecting'));
+
+        $mform->addElement('textarea', 'rejectnotice', get_string('coursereasonforrejectingemail'), array('rows'=>'15', 'cols'=>'50'));
+        $mform->addRule('rejectnotice', get_string('missingreqreason'), 'required', null, 'client');
+        $mform->setType('rejectnotice', PARAM_TEXT);
+
+        $this->add_action_buttons(true, get_string('reject'));
+    }
+}
+
diff --git a/reset.php b/reset.php
new file mode 100644 (file)
index 0000000..09a440b
--- /dev/null
+++ b/reset.php
@@ -0,0 +1,106 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The purpose of this feature is to quickly remove all user related data from a course
+ * in order to make it available for a new semester.  This feature can handle the removal
+ * of general course data like students, teachers, logs, events and groups as well as module
+ * specific data.  Each module must be modified to take advantage of this new feature.
+ * The feature will also reset the start date of the course if necessary.
+ *
+ * @copyright Mark Flach and moodle.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require('../config.php');
+require_once('reset_form.php');
+
+$id = required_param('id', PARAM_INT);
+
+if (!$course = $DB->get_record('course', array('id'=>$id))) {
+    print_error("invalidcourseid");
+}
+
+$PAGE->set_url('/course/reset.php', array('id'=>$id));
+
+require_login($course);
+require_capability('moodle/course:reset', context_course::instance($course->id));
+
+$strreset       = get_string('reset');
+$strresetcourse = get_string('resetcourse');
+$strremove      = get_string('remove');
+
+$PAGE->navbar->add($strresetcourse);
+$PAGE->set_title($course->fullname.': '.$strresetcourse);
+$PAGE->set_heading($course->fullname.': '.$strresetcourse);
+
+$mform = new course_reset_form();
+
+if ($mform->is_cancelled()) {
+    redirect($CFG->wwwroot.'/course/view.php?id='.$id);
+
+} else if ($data = $mform->get_data()) { // no magic quotes
+
+    if (isset($data->selectdefault)) {
+        $_POST = array();
+        $mform = new course_reset_form();
+        $mform->load_defaults();
+
+    } else if (isset($data->deselectall)) {
+        $_POST = array();
+        $mform = new course_reset_form();
+
+    } else {
+        echo $OUTPUT->header();
+        echo $OUTPUT->heading($strresetcourse);
+
+        $data->reset_start_date_old = $course->startdate;
+        $status = reset_course_userdata($data);
+
+        $data = array();;
+        foreach ($status as $item) {
+            $line = array();
+            $line[] = $item['component'];
+            $line[] = $item['item'];
+            $line[] = ($item['error']===false) ? get_string('ok') : '<div class="notifyproblem">'.$item['error'].'</div>';
+            $data[] = $line;
+        }
+
+        $table = new html_table();
+        $table->head  = array(get_string('resetcomponent'), get_string('resettask'), get_string('resetstatus'));
+        $table->size  = array('20%', '40%', '40%');
+        $table->align = array('left', 'left', 'left');
+        $table->width = '80%';
+        $table->data  = $data;
+        echo html_writer::table($table);
+
+        echo $OUTPUT->continue_button('view.php?id='.$course->id);  // Back to course page
+        echo $OUTPUT->footer();
+        exit;
+    }
+}
+
+echo $OUTPUT->header();
+echo $OUTPUT->heading($strresetcourse);
+
+echo $OUTPUT->box(get_string('resetinfo'));
+
+$mform->display();
+echo $OUTPUT->footer();
+
+
diff --git a/reset_form.php b/reset_form.php
new file mode 100644 (file)
index 0000000..ea619fe
--- /dev/null
@@ -0,0 +1,135 @@
+<?php
+if (!defined('MOODLE_INTERNAL')) {
+    die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
+}
+
+require_once $CFG->libdir.'/formslib.php';
+
+class course_reset_form extends moodleform {
+    function definition (){
+        global $CFG, $COURSE, $DB;
+
+        $mform =& $this->_form;
+
+        $mform->addElement('header', 'generalheader', get_string('general'));
+
+        $mform->addElement('date_selector', 'reset_start_date', get_string('startdate'), array('optional'=>true));
+        $mform->addHelpButton('reset_start_date', 'startdate');
+        $mform->addElement('checkbox', 'reset_events', get_string('deleteevents', 'calendar'));
+        $mform->addElement('checkbox', 'reset_logs', get_string('deletelogs'));
+        $mform->addElement('checkbox', 'reset_notes', get_string('deletenotes', 'notes'));
+        $mform->addElement('checkbox', 'reset_comments', get_string('deleteallcomments', 'moodle'));
+        $mform->addElement('checkbox', 'reset_completion', get_string('deletecompletiondata', 'completion'));
+        $mform->addElement('checkbox', 'delete_blog_associations', get_string('deleteblogassociations', 'blog'));
+        $mform->addHelpButton('delete_blog_associations', 'deleteblogassociations', 'blog');
+
+
+        $mform->addElement('header', 'rolesheader', get_string('roles'));
+
+        $roles = get_assignable_roles(context_course::instance($COURSE->id));
+        $roles[0] = get_string('noroles', 'role');
+        $roles = array_reverse($roles, true);
+
+        $mform->addElement('select', 'unenrol_users', get_string('unenrolroleusers', 'enrol'), $roles, array('multiple' => 'multiple'));
+        $mform->addElement('checkbox', 'reset_roles_overrides', get_string('deletecourseoverrides', 'role'));
+        $mform->setAdvanced('reset_roles_overrides');
+        $mform->addElement('checkbox', 'reset_roles_local', get_string('deletelocalroles', 'role'));
+
+
+        $mform->addElement('header', 'gradebookheader', get_string('gradebook', 'grades'));
+
+        $mform->addElement('checkbox', 'reset_gradebook_items', get_string('removeallcourseitems', 'grades'));
+        $mform->addElement('checkbox', 'reset_gradebook_grades', get_string('removeallcoursegrades', 'grades'));
+        $mform->disabledIf('reset_gradebook_grades', 'reset_gradebook_items', 'checked');
+
+
+        $mform->addElement('header', 'groupheader', get_string('groups'));
+
+        $mform->addElement('checkbox', 'reset_groups_remove', get_string('deleteallgroups', 'group'));
+        $mform->setAdvanced('reset_groups_remove');
+        $mform->addElement('checkbox', 'reset_groups_members', get_string('removegroupsmembers', 'group'));
+        $mform->setAdvanced('reset_groups_members');
+        $mform->disabledIf('reset_groups_members', 'reset_groups_remove', 'checked');
+
+        $mform->addElement('checkbox', 'reset_groupings_remove', get_string('deleteallgroupings', 'group'));
+        $mform->setAdvanced('reset_groupings_remove');
+        $mform->addElement('checkbox', 'reset_groupings_members', get_string('removegroupingsmembers', 'group'));
+        $mform->setAdvanced('reset_groupings_members');
+        $mform->disabledIf('reset_groupings_members', 'reset_groupings_remove', 'checked');
+
+        $unsupported_mods = array();
+        if ($allmods = $DB->get_records('modules') ) {
+            foreach ($allmods as $mod) {
+                $modname = $mod->name;
+                $modfile = $CFG->dirroot."/mod/$modname/lib.php";
+                $mod_reset_course_form_definition = $modname.'_reset_course_form_definition';
+                $mod_reset__userdata = $modname.'_reset_userdata';
+                if (file_exists($modfile)) {
+                    if (!$DB->count_records($modname, array('course'=>$COURSE->id))) {
+                        continue; // Skip mods with no instances
+                    }
+                    include_once($modfile);
+                    if (function_exists($mod_reset_course_form_definition)) {
+                        $mod_reset_course_form_definition($mform);
+                    } else if (!function_exists($mod_reset__userdata)) {
+                        $unsupported_mods[] = $mod;
+                    }
+                } else {
+                    debugging('Missing lib.php in '.$modname.' module');
+                }
+            }
+        }
+        // mention unsupported mods
+        if (!empty($unsupported_mods)) {
+            $mform->addElement('header', 'unsupportedheader', get_string('resetnotimplemented'));
+            foreach($unsupported_mods as $mod) {
+                $mform->addElement('static', 'unsup'.$mod->name, get_string('modulenameplural', $mod->name));
+                $mform->setAdvanced('unsup'.$mod->name);
+            }
+        }
+
+        $mform->addElement('hidden', 'id', $COURSE->id);
+        $mform->setType('id', PARAM_INT);
+
+        $buttonarray = array();
+        $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('resetcourse'));
+        $buttonarray[] = &$mform->createElement('submit', 'selectdefault', get_string('selectdefault'));
+        $buttonarray[] = &$mform->createElement('submit', 'deselectall', get_string('deselectall'));
+        $buttonarray[] = &$mform->createElement('cancel');
+        $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
+        $mform->closeHeaderBefore('buttonar');
+    }
+
+    function load_defaults() {
+        global $CFG, $COURSE, $DB;
+
+        $mform =& $this->_form;
+
+        $defaults = array ('reset_events'=>1, 'reset_logs'=>1, 'reset_roles_local'=>1, 'reset_gradebook_grades'=>1, 'reset_notes'=>1);
+
+        // Set student as default in unenrol user list, if role with student archetype exist.
+        if ($studentrole = get_archetype_roles('student')) {
+            $defaults['unenrol_users'] = array_keys($studentrole);
+        }
+
+        if ($allmods = $DB->get_records('modules') ) {
+            foreach ($allmods as $mod) {
+                $modname = $mod->name;
+                $modfile = $CFG->dirroot."/mod/$modname/lib.php";
+                $mod_reset_course_form_defaults = $modname.'_reset_course_form_defaults';
+                if (file_exists($modfile)) {
+                    @include_once($modfile);
+                    if (function_exists($mod_reset_course_form_defaults)) {
+                        if ($moddefs = $mod_reset_course_form_defaults($COURSE)) {
+                            $defaults = $defaults + $moddefs;
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach ($defaults as $element=>$default) {
+            $mform->setDefault($element, $default);
+        }
+    }
+}
diff --git a/resources.php b/resources.php
new file mode 100644 (file)
index 0000000..6debe85
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * List of all resource type modules in course
+ *
+ * @package   moodlecore
+ * @copyright 2009 Petr Skoda (http://skodak.org)
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once("$CFG->libdir/resourcelib.php");
+
+$id = required_param('id', PARAM_INT); // course id
+
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+$PAGE->set_pagelayout('course');
+require_course_login($course, true);
+
+// get list of all resource-like modules
+$allmodules = $DB->get_records('modules', array('visible'=>1));
+$modules = array();
+foreach ($allmodules as $key=>$module) {
+    $modname = $module->name;
+    $libfile = "$CFG->dirroot/mod/$modname/lib.php";
+    if (!file_exists($libfile)) {
+        continue;
+    }
+    $archetype = plugin_supports('mod', $modname, FEATURE_MOD_ARCHETYPE, MOD_ARCHETYPE_OTHER);
+    if ($archetype != MOD_ARCHETYPE_RESOURCE) {
+        continue;
+    }
+
+    $modules[$modname] = get_string('modulename', $modname);
+    //some hacky nasic logging
+    add_to_log($course->id, $modname, 'view all', "index.php?id=$course->id", '');
+}
+
+$strresources    = get_string('resources');
+$strsectionname  = get_string('sectionname', 'format_'.$course->format);
+$strname         = get_string('name');
+$strintro        = get_string('moduleintro');
+$strlastmodified = get_string('lastmodified');
+
+$PAGE->set_url('/course/resources.php', array('id' => $course->id));
+$PAGE->set_title($course->shortname.': '.$strresources);
+$PAGE->set_heading($course->fullname);
+$PAGE->navbar->add($strresources);
+echo $OUTPUT->header();
+
+$modinfo = get_fast_modinfo($course);
+$usesections = course_format_uses_sections($course->format);
+$cms = array();
+$resources = array();
+foreach ($modinfo->cms as $cm) {
+    if (!$cm->uservisible) {
+        continue;
+    }
+    if (!array_key_exists($cm->modname, $modules)) {
+        continue;
+    }
+    if (!$cm->has_view()) {
+        // Exclude label and similar
+        continue;
+    }
+    $cms[$cm->id] = $cm;
+    $resources[$cm->modname][] = $cm->instance;
+}
+
+// preload instances
+foreach ($resources as $modname=>$instances) {
+    $resources[$modname] = $DB->get_records_list($modname, 'id', $instances, 'id', 'id,name,intro,introformat,timemodified');
+}
+
+if (!$cms) {
+    notice(get_string('thereareno', 'moodle', $strresources), "$CFG->wwwroot/course/view.php?id=$course->id");
+    exit;
+}
+
+$table = new html_table();
+$table->attributes['class'] = 'generaltable mod_index';
+
+if ($usesections) {
+    $table->head  = array ($strsectionname, $strname, $strintro);
+    $table->align = array ('center', 'left', 'left');
+} else {
+    $table->head  = array ($strlastmodified, $strname, $strintro);
+    $table->align = array ('left', 'left', 'left');
+}
+
+$currentsection = '';
+foreach ($cms as $cm) {
+    if (!isset($resources[$cm->modname][$cm->instance])) {
+        continue;
+    }
+    $resource = $resources[$cm->modname][$cm->instance];
+    if ($usesections) {
+        $printsection = '';
+        if ($cm->sectionnum !== $currentsection) {
+            if ($cm->sectionnum) {
+                $printsection = get_section_name($course, $cm->sectionnum);
+            }
+            if ($currentsection !== '') {
+                $table->data[] = 'hr';
+            }
+            $currentsection = $cm->sectionnum;
+        }
+    } else {
+        $printsection = '<span class="smallinfo">'.userdate($resource->timemodified)."</span>";
+    }
+
+    $extra = empty($cm->extra) ? '' : $cm->extra;
+    if (!empty($cm->icon)) {
+        $icon = '<img src="'.$OUTPUT->pix_url($cm->icon).'" class="activityicon" alt="'.get_string('modulename', $cm->modname).'" /> ';
+    } else {
+        $icon = '<img src="'.$OUTPUT->pix_url('icon', $cm->modname).'" class="activityicon" alt="'.get_string('modulename', $cm->modname).'" /> ';
+    }
+
+    $class = $cm->visible ? '' : 'class="dimmed"'; // hidden modules are dimmed
+    $table->data[] = array (
+        $printsection,
+        "<a $class $extra href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id=$cm->id\">".$icon.format_string($resource->name)."</a>",
+        format_module_intro('resource', $resource, $cm->id));
+}
+
+echo html_writer::table($table);
+
+echo $OUTPUT->footer();
diff --git a/rest.php b/rest.php
new file mode 100644 (file)
index 0000000..6272903
--- /dev/null
+++ b/rest.php
@@ -0,0 +1,239 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Provide interface for topics AJAX course formats
+ *
+ * @copyright 1999 Martin Dougiamas  http://dougiamas.com
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+if (!defined('AJAX_SCRIPT')) {
+    define('AJAX_SCRIPT', true);
+}
+require_once(dirname(__FILE__) . '/../config.php');
+require_once($CFG->dirroot.'/course/lib.php');
+
+// Initialise ALL the incoming parameters here, up front.
+$courseid   = required_param('courseId', PARAM_INT);
+$class      = required_param('class', PARAM_ALPHA);
+$field      = optional_param('field', '', PARAM_ALPHA);
+$instanceid = optional_param('instanceId', 0, PARAM_INT);
+$sectionid  = optional_param('sectionId', 0, PARAM_INT);
+$beforeid   = optional_param('beforeId', 0, PARAM_INT);
+$value      = optional_param('value', 0, PARAM_INT);
+$column     = optional_param('column', 0, PARAM_ALPHA);
+$id         = optional_param('id', 0, PARAM_INT);
+$summary    = optional_param('summary', '', PARAM_RAW);
+$sequence   = optional_param('sequence', '', PARAM_SEQUENCE);
+$visible    = optional_param('visible', 0, PARAM_INT);
+$pageaction = optional_param('action', '', PARAM_ALPHA); // Used to simulate a DELETE command
+$title      = optional_param('title', '', PARAM_TEXT);
+
+$PAGE->set_url('/course/rest.php', array('courseId'=>$courseid,'class'=>$class));
+
+//NOTE: when making any changes here please make sure it is using the same access control as course/mod.php !!
+
+$course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+// Check user is logged in and set contexts if we are dealing with resource
+if (in_array($class, array('resource'))) {
+    $cm = get_coursemodule_from_id(null, $id, $course->id, false, MUST_EXIST);
+    require_login($course, false, $cm);
+    $modcontext = context_module::instance($cm->id);
+} else {
+    require_login($course);
+}
+$coursecontext = context_course::instance($course->id);
+require_sesskey();
+
+echo $OUTPUT->header(); // send headers
+
+// OK, now let's process the parameters and do stuff
+// MDL-10221 the DELETE method is not allowed on some web servers, so we simulate it with the action URL param
+$requestmethod = $_SERVER['REQUEST_METHOD'];
+if ($pageaction == 'DELETE') {
+    $requestmethod = 'DELETE';
+}
+
+switch($requestmethod) {
+    case 'POST':
+
+        switch ($class) {
+            case 'section':
+
+                if (!$DB->record_exists('course_sections', array('course'=>$course->id, 'section'=>$id))) {
+                    throw new moodle_exception('AJAX commands.php: Bad Section ID '.$id);
+                }
+
+                switch ($field) {
+                    case 'visible':
+                        require_capability('moodle/course:sectionvisibility', $coursecontext);
+                        $resourcestotoggle = set_section_visible($course->id, $id, $value);
+                        echo json_encode(array('resourcestotoggle' => $resourcestotoggle));
+                        break;
+
+                    case 'move':
+                        require_capability('moodle/course:movesections', $coursecontext);
+                        move_section_to($course, $id, $value);
+                        // See if format wants to do something about it
+                        $response = course_get_format($course)->ajax_section_move();
+                        if ($response !== null) {
+                            echo json_encode($response);
+                        }
+                        break;
+                }
+                break;
+
+            case 'resource':
+                switch ($field) {
+                    case 'visible':
+                        require_capability('moodle/course:activityvisibility', $modcontext);
+                        set_coursemodule_visible($cm->id, $value);
+                        break;
+
+                    case 'groupmode':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        set_coursemodule_groupmode($cm->id, $value);
+                        break;
+
+                    case 'indent':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        $cm->indent = $value;
+                        if ($cm->indent >= 0) {
+                            $DB->update_record('course_modules', $cm);
+                            rebuild_course_cache($cm->course);
+                        }
+                        break;
+
+                    case 'move':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        if (!$section = $DB->get_record('course_sections', array('course'=>$course->id, 'section'=>$sectionid))) {
+                            throw new moodle_exception('AJAX commands.php: Bad section ID '.$sectionid);
+                        }
+
+                        if ($beforeid > 0){
+                            $beforemod = get_coursemodule_from_id('', $beforeid, $course->id);
+                            $beforemod = $DB->get_record('course_modules', array('id'=>$beforeid));
+                        } else {
+                            $beforemod = NULL;
+                        }
+
+                        moveto_module($cm, $section, $beforemod);
+                        echo json_encode(array('visible' => $cm->visible));
+                        break;
+                    case 'gettitle':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+                        $module = new stdClass();
+                        $module->id = $cm->instance;
+
+                        // Don't pass edit strings through multilang filters - we need the entire string
+                        echo json_encode(array('instancename' => $cm->name));
+                        break;
+                    case 'updatetitle':
+                        require_capability('moodle/course:manageactivities', $modcontext);
+                        require_once($CFG->libdir . '/gradelib.php');
+                        $cm = get_coursemodule_from_id('', $id, 0, false, MUST_EXIST);
+                        $module = new stdClass();
+                        $module->id = $cm->instance;
+
+                        // Escape strings as they would be by mform
+                        if (!empty($CFG->formatstringstriptags)) {
+                            $module->name = clean_param($title, PARAM_TEXT);
+                        } else {
+                            $module->name = clean_param($title, PARAM_CLEANHTML);
+                        }
+
+                        if (!empty($module->name)) {
+                            $DB->update_record($cm->modname, $module);
+                            rebuild_course_cache($cm->course);
+                        } else {
+                            $module->name = $cm->name;
+                        }
+
+                        // Attempt to update the grade item if relevant
+                        $grademodule = $DB->get_record($cm->modname, array('id' => $cm->instance));
+                        $grademodule->cmidnumber = $cm->idnumber;
+                        $grademodule->modname = $cm->modname;
+                        grade_update_mod_grades($grademodule);
+
+                        // We need to return strings after they've been through filters for multilang
+                        $stringoptions = new stdClass;
+                        $stringoptions->context = $coursecontext;
+                        echo json_encode(array('instancename' => html_entity_decode(format_string($module->name, true,  $stringoptions))));
+                        break;
+                }
+                break;
+
+            case 'course':
+                switch($field) {
+                    case 'marker':
+                        require_capability('moodle/course:setcurrentsection', $coursecontext);
+                        course_set_marker($course->id, $value);
+                        break;
+                }
+                break;
+        }
+        break;
+
+    case 'DELETE':
+        switch ($class) {
+            case 'resource':
+                require_capability('moodle/course:manageactivities', $modcontext);
+                $modlib = "$CFG->dirroot/mod/$cm->modname/lib.php";
+
+                if (file_exists($modlib)) {
+                    include_once($modlib);
+                } else {
+                    throw new moodle_exception("Ajax rest.php: This module is missing mod/$cm->modname/lib.php");
+                }
+                $deleteinstancefunction = $cm->modname."_delete_instance";
+
+                // Run the module's cleanup funtion.
+                if (!$deleteinstancefunction($cm->instance)) {
+                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name (instance)");
+                    die;
+                }
+
+                // remove all module files in case modules forget to do that
+                $fs = get_file_storage();
+                $fs->delete_area_files($modcontext->id);
+
+                if (!delete_course_module($cm->id)) {
+                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name (coursemodule)");
+                }
+                // Remove the course_modules entry.
+                if (!delete_mod_from_section($cm->id, $cm->section)) {
+                    throw new moodle_exception("Ajax rest.php: Could not delete the $cm->modname $cm->name from section");
+                }
+
+                // Trigger a mod_deleted event with information about this module.
+                $eventdata = new stdClass();
+                $eventdata->modulename = $cm->modname;
+                $eventdata->cmid       = $cm->id;
+                $eventdata->courseid   = $course->id;
+                $eventdata->userid     = $USER->id;
+                events_trigger('mod_deleted', $eventdata);
+
+                add_to_log($courseid, "course", "delete mod",
+                           "view.php?id=$courseid",
+                           "$cm->modname $cm->instance", $cm->id);
+                break;
+        }
+        break;
+}
diff --git a/scales.php b/scales.php
new file mode 100644 (file)
index 0000000..d212489
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Allows a creator to edit custom scales, and also display help about scales
+ *
+ * @copyright 1999 Martin Dougiamas  http://dougiamas.com
+ * @deprecated - TODO remove this file or replace it with an alternative solution for scales overview
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require_once("../config.php");
+require_once("lib.php");
+
+$id   = required_param('id', PARAM_INT);               // course id
+$scaleid  = optional_param('scaleid', 0, PARAM_INT);   // scale id (show only this one)
+
+$url = new moodle_url('/course/scales.php', array('id'=>$id));
+if ($scaleid !== 0) {
+    $url->param('scaleid', $scaleid);
+}
+$PAGE->set_url($url);
+
+$context = null;
+if ($course = $DB->get_record('course', array('id'=>$id))) {
+    require_login($course);
+    $context = context_course::instance($course->id);
+} else {
+    //$id will be 0 for site level scales
+    require_login();
+    $context = context_system::instance();
+}
+
+$PAGE->set_context($context);
+require_capability('moodle/course:viewscales', $context);
+
+$strscales = get_string("scales");
+$strcustomscales = get_string("scalescustom");
+$strstandardscales = get_string("scalesstandard");
+
+$PAGE->set_title($strscales);
+if (!empty($course)) {
+    $PAGE->set_heading($course->fullname);
+} else {
+    $PAGE->set_heading($SITE->fullname);
+}
+echo $OUTPUT->header();
+
+if ($scaleid) {
+    if ($scale = $DB->get_record("scale", array('id'=>$scaleid))) {
+        if ($scale->courseid == 0 || $scale->courseid == $course->id) {
+
+            $scalemenu = make_menu_from_list($scale->scale);
+
+            echo $OUTPUT->box_start();
+            echo $OUTPUT->heading($scale->name);
+            echo "<center>";
+            echo html_writer::label(get_string('scales'), 'scaleunused'. $scaleid, false, array('class' => 'accesshide'));
+            echo html_writer::select($scalemenu, 'unused', '', array('' => 'choosedots'), array('id' => 'scaleunused'.$scaleid));
+            echo "</center>";
+            echo text_to_html($scale->description);
+            echo $OUTPUT->box_end();
+            echo $OUTPUT->close_window_button();
+            echo $OUTPUT->footer();
+            exit;
+        }
+    }
+}
+
+$systemcontext = context_system::instance();
+
+if ($scales = $DB->get_records("scale", array("courseid"=>$course->id), "name ASC")) {
+    echo $OUTPUT->heading($strcustomscales);
+
+    if (has_capability('moodle/course:managescales', $context)) {
+        echo "<p align=\"center\">(";
+        print_string('scalestip2');
+        echo ")</p>";
+    }
+
+    foreach ($scales as $scale) {
+
+        $scale->description = file_rewrite_pluginfile_urls($scale->description, 'pluginfile.php', $systemcontext->id, 'grade', 'scale', $scale->id);
+
+        $scalemenu = make_menu_from_list($scale->scale);
+
+        echo $OUTPUT->box_start();
+        echo $OUTPUT->heading($scale->name);
+        echo "<center>";
+        echo html_writer::label(get_string('scales'), 'courseunused' . $scale->id, false, array('class' => 'accesshide'));
+        echo html_writer::select($scalemenu, 'unused', '', array('' => 'choosedots'), array('id' => 'courseunused' . $scale->id));
+        echo "</center>";
+        echo text_to_html($scale->description);
+        echo $OUTPUT->box_end();
+        echo "<hr />";
+    }
+
+} else {
+    if (has_capability('moodle/course:managescales', $context)) {
+        echo "<p align=\"center\">(";
+        print_string("scalestip2");
+        echo ")</p>";
+    }
+}
+
+if ($scales = $DB->get_records("scale", array("courseid"=>0), "name ASC")) {
+    echo $OUTPUT->heading($strstandardscales);
+    foreach ($scales as $scale) {
+
+        $scale->description = file_rewrite_pluginfile_urls($scale->description, 'pluginfile.php', $systemcontext->id, 'grade', 'scale', $scale->id);
+
+        $scalemenu = make_menu_from_list($scale->scale);
+
+        echo $OUTPUT->box_start();
+        echo $OUTPUT->heading($scale->name);
+        echo "<center>";
+        echo html_writer::label(get_string('scales'), 'sitescale' . $scale->id, false, array('class' => 'accesshide'));
+        echo html_writer::select($scalemenu, 'unused', '', array('' => 'choosedots'), array('id' => 'sitescale' . $scale->id));
+        echo "</center>";
+        echo text_to_html($scale->description);
+        echo $OUTPUT->box_end();
+        echo "<hr />";
+    }
+}
+
+echo $OUTPUT->close_window_button();
+echo $OUTPUT->footer();
+
diff --git a/search.php b/search.php
new file mode 100644 (file)
index 0000000..721d968
--- /dev/null
@@ -0,0 +1,435 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Displays external information about a course
+ * @package    core
+ * @category   course
+ * @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($CFG->dirroot.'/course/lib.php');
+
+$search    = optional_param('search', '', PARAM_RAW);  // search words
+$page      = optional_param('page', 0, PARAM_INT);     // which page to show
+$perpage   = optional_param('perpage', 10, PARAM_INT); // how many per page
+$moveto    = optional_param('moveto', 0, PARAM_INT);   // move to category
+$edit      = optional_param('edit', -1, PARAM_BOOL);
+$hide      = optional_param('hide', 0, PARAM_INT);
+$show      = optional_param('show', 0, PARAM_INT);
+$blocklist = optional_param('blocklist', 0, PARAM_INT);
+$modulelist= optional_param('modulelist', '', PARAM_PLUGIN);
+
+// List of minimum capabilities which user need to have for editing/moving course
+$capabilities = array('moodle/course:create', 'moodle/category:manage');
+
+// List of category id's in which current user has course:create and category:manage capability.
+$usercatlist = array();
+
+// List of parent category id's
+$catparentlist = array();
+
+// Populate usercatlist with list of category id's with required capabilities.
+make_categories_list($usercatlist, $catparentlist, $capabilities);
+
+$search = trim(strip_tags($search)); // trim & clean raw searched string
+if ($search) {
+    $searchterms = explode(" ", $search);    // Search for words independently
+    foreach ($searchterms as $key => $searchterm) {
+        if (strlen($searchterm) < 2) {
+            unset($searchterms[$key]);
+        }
+    }
+    $search = trim(implode(" ", $searchterms));
+}
+
+$site = get_site();
+
+$urlparams = array();
+foreach (array('search', 'page', 'blocklist', 'modulelist', 'edit') as $param) {
+    if (!empty($$param)) {
+        $urlparams[$param] = $$param;
+    }
+}
+if ($perpage != 10) {
+    $urlparams['perpage'] = $perpage;
+}
+$PAGE->set_url('/course/search.php', $urlparams);
+$PAGE->set_context(context_system::instance());
+$PAGE->set_pagelayout('standard');
+
+if ($CFG->forcelogin) {
+    require_login();
+}
+
+// Editing is possible if user has system or category level create and manage capability
+if (can_edit_in_category() || !empty($usercatlist)) {
+    if ($edit !== -1) {
+        $USER->editing = $edit;
+    }
+    $adminediting = $PAGE->user_is_editing();
+
+    // Set perpage if user can edit in category
+    if ($perpage != 99999) {
+        $perpage = 30;
+    }
+} else {
+    $adminediting = false;
+}
+
+// Editing functions
+if (has_capability('moodle/course:visibility', context_system::instance())) {
+    // Hide or show a course
+    if (($hide || $show) && confirm_sesskey()) {
+        if ($hide) {
+            $course = $DB->get_record("course", array("id" => $hide));
+            $visible = 0;
+        } else {
+            $course = $DB->get_record("course", array("id" => $show));
+            $visible = 1;
+        }
+        if ($course) {
+            $DB->set_field("course", "visible", $visible, array("id" => $course->id));
+        }
+    }
+}
+
+$displaylist = array();
+$parentlist = array();
+make_categories_list($displaylist, $parentlist);
+
+$strcourses = new lang_string("courses");
+$strsearch = new lang_string("search");
+$strsearchresults = new lang_string("searchresults");
+$strcategory = new lang_string("category");
+$strselect   = new lang_string("select");
+$strselectall = new lang_string("selectall");
+$strdeselectall = new lang_string("deselectall");
+$stredit = new lang_string("edit");
+$strfrontpage = new lang_string('frontpage', 'admin');
+$strnovalidcourses = new lang_string('novalidcourses');
+
+if (empty($search) and empty($blocklist) and empty($modulelist) and empty($moveto) and ($edit != -1)) {
+    $PAGE->navbar->add($strcourses, new moodle_url('/course/index.php'));
+    $PAGE->navbar->add($strsearch);
+    $PAGE->set_title("$site->fullname : $strsearch");
+    $PAGE->set_heading($site->fullname);
+
+    echo $OUTPUT->header();
+    echo $OUTPUT->box_start();
+    echo "<center>";
+    echo "<br />";
+    print_course_search("", false, "plain");
+    echo "<br /><p>";
+    print_string("searchhelp");
+    echo "</p>";
+    echo "</center>";
+    echo $OUTPUT->box_end();
+    echo $OUTPUT->footer();
+    exit;
+}
+
+$courses = array();
+if (!empty($moveto) and $data = data_submitted() and confirm_sesskey()) {   // Some courses are being moved
+    if (!$destcategory = $DB->get_record("course_categories", array("id" => $moveto))) {
+        print_error('cannotfindcategory', '', '', $moveto);
+    }
+
+    // User should have manage and create capablity on destination category.
+    require_capability('moodle/category:manage', context_coursecat::instance($moveto));
+    require_capability('moodle/course:create', context_coursecat::instance($moveto));
+
+    foreach ( $data as $key => $value ) {
+        if (preg_match('/^c\d+$/', $key)) {
+            $courseid = substr($key, 1);
+            // user must have category:manage and course:create capability for the course to be moved.
+            $coursecontext = context_course::instance($courseid);
+            foreach ($capabilities as $capability) {
+                // Require capability here will result in a fatal error should the user not
+                // have the requried category ensuring that no moves occur if they are
+                // trying to move multiple courses.
+                require_capability($capability, $coursecontext);
+                array_push($courses, $courseid);
+            }
+        }
+    }
+    move_courses($courses, $moveto);
+}
+
+// get list of courses containing blocks if required
+if (!empty($blocklist) and confirm_sesskey()) {
+    $blockname = $DB->get_field('block', 'name', array('id' => $blocklist));
+    $courses = array();
+    list($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+    $sql = "SELECT c.* $select FROM {course} c
+            $join JOIN {block_instances} bi ON bi.parentcontextid = ctx.id
+            WHERE bi.blockname = ?";
+    $courses = $DB->get_records_sql($sql, array($blockname));
+    $totalcount = count($courses);
+    // Keep only chunk of array which you want to display
+    if ($totalcount > $perpage) {
+        $courses = array_chunk($courses, $perpage, true);
+        $courses = $courses[$page];
+    }
+    foreach ($courses as $course) {
+        $courses[$course->id] = $course;
+    }
+} elseif (!empty($modulelist) and confirm_sesskey()) { // get list of courses containing modules
+    $modulename = $modulelist;
+    list($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
+    $sql = "SELECT c.* $select FROM {course} c $join
+            WHERE c.id IN (SELECT DISTINCT cc.id FROM {".$modulelist."} module, {course} cc
+                           WHERE module.course = cc.id)";
+    $courselist = $DB->get_records_sql($sql);
+    $courses = array();
+    if (!empty($courselist)) {
+        $firstcourse = $page*$perpage;
+        $lastcourse = $page*$perpage + $perpage -1;
+        $i = 0;
+        foreach ($courselist as $course) {
+            if ($i >= $firstcourse && $i <= $lastcourse) {
+                $courses[$course->id] = $course;
+            }
+            $i++;
+        }
+    }
+    $totalcount = count($courselist);
+} else if (!empty($searchterm)) {
+    // Donot do search for empty search request.
+    $courses = get_courses_search($searchterms, "fullname ASC", $page, $perpage, $totalcount);
+}
+
+$searchform = '';
+// Turn editing should be visible if user have system or category level capability
+if (!empty($courses) && (can_edit_in_category() || !empty($usercatlist))) {
+    if ($PAGE->user_is_editing()) {
+        $string = new lang_string("turneditingoff");
+        $edit = "off";
+    } else {
+        $string = new lang_string("turneditingon");
+        $edit = "on";
+    }
+    $params = array_merge($urlparams, array('sesskey' => sesskey(), 'edit' => $edit));
+    $aurl = new moodle_url("$CFG->wwwroot/course/search.php", $params);
+    $searchform = $OUTPUT->single_button($aurl, $string, 'get');
+} else {
+    $searchform = print_course_search($search, true, "navbar");
+}
+
+$PAGE->navbar->add($strcourses, new moodle_url('/course/index.php'));
+$PAGE->navbar->add($strsearch, new moodle_url('/course/search.php'));
+if (!empty($search)) {
+    $PAGE->navbar->add(s($search));
+}
+$PAGE->set_title("$site->fullname : $strsearchresults");
+$PAGE->set_heading($site->fullname);
+$PAGE->set_button($searchform);
+
+echo $OUTPUT->header();
+
+$lastcategory = -1;
+if ($courses) {
+    echo $OUTPUT->heading("$strsearchresults: $totalcount");
+    $encodedsearch = urlencode($search);
+
+    // add the module/block parameter to the paging bar if they exists
+    $modulelink = "";
+    if (!empty($modulelist) and confirm_sesskey()) {
+        $modulelink = "&amp;modulelist=".$modulelist."&amp;sesskey=".sesskey();
+    } else if (!empty($blocklist) and confirm_sesskey()) {
+        $modulelink = "&amp;blocklist=".$blocklist."&amp;sesskey=".sesskey();
+    }
+
+    print_navigation_bar($totalcount, $page, $perpage, $encodedsearch, $modulelink);
+
+    // Show list of courses
+    if (!$adminediting) { //Not editing mode
+        foreach ($courses as $course) {
+            // front page don't belong to any category and block can exist.
+            if ($course->category > 0) {
+                $course->summary .= "<br /><p class=\"category\">";
+                $course->summary .= "$strcategory: <a href=\"category.php?id=$course->category\">";
+                $course->summary .= $displaylist[$course->category];
+                $course->summary .= "</a></p>";
+            }
+            print_course($course, $search);
+            echo $OUTPUT->spacer(array('height'=>5, 'width'=>5, 'br'=>true)); // should be done with CSS instead
+        }
+    } else {
+        // Editing mode
+        echo "<form id=\"movecourses\" action=\"search.php\" method=\"post\">\n";
+        echo "<div><input type=\"hidden\" name=\"sesskey\" value=\"".sesskey()."\" />\n";
+        echo "<input type=\"hidden\" name=\"search\" value=\"".s($search)."\" />\n";
+        echo "<input type=\"hidden\" name=\"page\" value=\"$page\" />\n";
+        echo "<input type=\"hidden\" name=\"perpage\" value=\"$perpage\" /></div>\n";
+        if (!empty($modulelist) and confirm_sesskey()) {
+            echo "<input type=\"hidden\" name=\"modulelist\" value=\"$modulelist\" /></div>\n";
+        } else if (!empty($blocklist) and confirm_sesskey()) {
+            echo "<input type=\"hidden\" name=\"blocklist\" value=\"$blocklist\" /></div>\n";
+        }
+        echo "<table border=\"0\" cellspacing=\"2\" cellpadding=\"4\" class=\"generalbox boxaligncenter\">\n<tr>\n";
+        echo "<th scope=\"col\">$strcourses</th>\n";
+        echo "<th scope=\"col\">$strcategory</th>\n";
+        echo "<th scope=\"col\">$strselect</th>\n";
+        echo "<th scope=\"col\">$stredit</th></tr>\n";
+
+        foreach ($courses as $course) {
+
+            context_helper::preload_from_record($course);
+            $coursecontext = context_course::instance($course->id);
+
+            $linkcss = $course->visible ? "" : " class=\"dimmed\" ";
+
+            // are we displaying the front page (courseid=1)?
+            if ($course->id == 1) {
+                echo "<tr>\n";
+                echo "<td><a href=\"$CFG->wwwroot\">$strfrontpage</a></td>\n";
+
+                // can't do anything else with the front page
+                echo "  <td>&nbsp;</td>\n"; // category place
+                echo "  <td>&nbsp;</td>\n"; // select place
+                echo "  <td>&nbsp;</td>\n"; // edit place
+                echo "</tr>\n";
+                continue;
+            }
+
+            echo "<tr>\n";
+            echo "<td><a $linkcss href=\"view.php?id=$course->id\">"
+                . highlight($search, $coursecontext->get_context_name(false)) . "</a></td>\n";
+            echo "<td>".$displaylist[$course->category]."</td>\n";
+            echo "<td>\n";
+
+            // If user has all required capabilities to move course then show selectable checkbox
+            if (has_all_capabilities($capabilities, $coursecontext)) {
+                echo "<input type=\"checkbox\" name=\"c$course->id\" />\n";
+            } else {
+                echo "<input type=\"checkbox\" name=\"c$course->id\" disabled=\"disabled\" />\n";
+            }
+
+            echo "</td>\n";
+            echo "<td>\n";
+
+            // checks whether user can update course settings
+            if (has_capability('moodle/course:update', $coursecontext)) {
+                echo "<a title=\"".get_string("settings")."\" href=\"$CFG->wwwroot/course/edit.php?id=$course->id\">\n<img".
+                    " src=\"" . $OUTPUT->pix_url('t/edit') . "\" class=\"iconsmall\" alt=\"".get_string("settings")."\" /></a>\n ";
+            }
+
+            // checks whether user can do role assignment
+            if (has_capability('moodle/course:enrolreview', $coursecontext)) {
+                echo'<a title="'.get_string('enrolledusers', 'enrol').'" href="'.$CFG->wwwroot.'/enrol/users.php?id='.$course->id.'">';
+                echo '<img src="'.$OUTPUT->pix_url('i/enrolusers') . '" class="iconsmall" alt="'.get_string('enrolledusers', 'enrol').'" /></a> ' . "\n";
+            }
+
+            // checks whether user can delete course
+            if (has_capability('moodle/course:delete', $coursecontext)) {
+                echo "<a title=\"".get_string("delete")."\" href=\"delete.php?id=$course->id\">\n<img".
+                    " src=\"" . $OUTPUT->pix_url('t/delete') . "\" class=\"iconsmall\" alt=\"".get_string("delete")."\" /></a>\n ";
+            }
+
+            // checks whether user can change visibility
+            if (has_capability('moodle/course:visibility', $coursecontext)) {
+                if (!empty($course->visible)) {
+                    echo "<a title=\"".get_string("hide")."\" href=\"search.php?search=$encodedsearch&amp;perpage=$perpage&amp;page=$page&amp;hide=$course->id&amp;sesskey=".sesskey()."\">\n<img".
+                        " src=\"" . $OUTPUT->pix_url('t/hide') . "\" class=\"iconsmall\" alt=\"".get_string("hide")."\" /></a>\n ";
+                } else {
+                    echo "<a title=\"".get_string("show")."\" href=\"search.php?search=$encodedsearch&amp;perpage=$perpage&amp;page=$page&amp;show=$course->id&amp;sesskey=".sesskey()."\">\n<img".
+                        " src=\"" . $OUTPUT->pix_url('t/show') . "\" class=\"iconsmall\" alt=\"".get_string("show")."\" /></a>\n ";
+                }
+            }
+
+            // checks whether user can do site backup
+            if (has_capability('moodle/backup:backupcourse', $coursecontext)) {
+                $backupurl = new moodle_url('/backup/backup.php', array('id' => $course->id));
+                echo "<a title=\"".get_string("backup")."\" href=\"".$backupurl."\">\n<img".
+                    " src=\"" . $OUTPUT->pix_url('t/backup') . "\" class=\"iconsmall\" alt=\"".get_string("backup")."\" /></a>\n ";
+            }
+
+            // checks whether user can do restore
+            if (has_capability('moodle/restore:restorecourse', $coursecontext)) {
+                $restoreurl = new moodle_url('/backup/restorefile.php', array('contextid' => $coursecontext->id));
+                echo "<a title=\"".get_string("restore")."\" href=\"".$restoreurl."\">\n<img".
+                    " src=\"" . $OUTPUT->pix_url('t/restore') . "\" class=\"iconsmall\" alt=\"".get_string("restore")."\" /></a>\n ";
+            }
+
+            echo "</td>\n</tr>\n";
+        }
+        echo "<tr>\n<td colspan=\"4\" style=\"text-align:center\">\n";
+        echo "<br />";
+        echo "<input type=\"button\" onclick=\"checkall()\" value=\"$strselectall\" />\n";
+        echo "<input type=\"button\" onclick=\"checknone()\" value=\"$strdeselectall\" />\n";
+        // Select box should only show categories in which user has min capability to move course.
+        echo html_writer::label(get_string('moveselectedcoursesto'), 'movetoid', false, array('class' => 'accesshide'));
+        echo html_writer::select($usercatlist, 'moveto', '', array(''=>get_string('moveselectedcoursesto')), array('id'=>'movetoid', 'class' => 'autosubmit'));
+        $PAGE->requires->yui_module('moodle-core-formautosubmit',
+            'M.core.init_formautosubmit',
+            array(array('selectid' => 'movetoid', 'nothing' => false))
+        );
+        echo "</td>\n</tr>\n";
+        echo "</table>\n</form>";
+
+    }
+
+    print_navigation_bar($totalcount,$page,$perpage,$encodedsearch,$modulelink);
+
+} else {
+    if (!empty($search)) {
+        echo $OUTPUT->heading(get_string("nocoursesfound",'', s($search)));
+    }
+    else {
+        echo $OUTPUT->heading($strnovalidcourses);
+    }
+}
+
+echo "<br /><br />";
+
+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 "<center><p>";
+        echo "<a href=\"search.php?search=$encodedsearch".$modulelink."&amp;perpage=99999\">".get_string("showall", "", $totalcount)."</a>";
+        echo "</p></center>";
+    } else if ($perpage === 99999) {
+        $defaultperpage = 10;
+        // If user has course:create or category:manage capability the show 30 records.
+        $capabilities = array('moodle/course:create', 'moodle/category:manage');
+        if (has_any_capability($capabilities, context_system::instance())) {
+            $defaultperpage = 30;
+        }
+
+        echo "<center><p>";
+        echo "<a href=\"search.php?search=$encodedsearch".$modulelink."&amp;perpage=".$defaultperpage."\">".get_string("showperpage", "", $defaultperpage)."</a>";
+        echo "</p></center>";
+    }
+}
diff --git a/style.css b/style.css
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/switchrole.php b/switchrole.php
new file mode 100644 (file)
index 0000000..2b756b6
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * The purpose of this file is to allow the user to switch roles and be redirected
+ * back to the page that they were on.
+ *
+ * This functionality is also supported in {@link /course/view.php} in order to comply
+ * with backwards compatibility
+ * The reason that we created this file was so that user didn't get redirected back
+ * to the course view page only to be redirected again.
+ *
+ * @since 2.0
+ * @package course
+ * @copyright 2009 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once($CFG->dirroot.'/course/lib.php');
+
+$id         = required_param('id', PARAM_INT);
+$switchrole = optional_param('switchrole',-1, PARAM_INT);
+$returnurl  = optional_param('returnurl', false, PARAM_LOCALURL);
+
+$PAGE->set_url('/course/switchrole.php', array('id'=>$id));
+
+if (!confirm_sesskey()) {
+    print_error('confirmsesskeybad', 'error');
+}
+
+if (! ($course = $DB->get_record('course', array('id'=>$id)))) {
+    print_error('invalidcourseid', 'error');
+}
+
+$context = context_course::instance($course->id);
+
+// Remove any switched roles before checking login
+if ($switchrole == 0) {
+    role_switch($switchrole, $context);
+}
+require_login($course);
+
+// Switchrole - sanity check in cost-order...
+if ($switchrole > 0 && has_capability('moodle/role:switchroles', $context)) {
+    // is this role assignable in this context?
+    // inquiring minds want to know...
+    $aroles = get_switchable_roles($context);
+    if (is_array($aroles) && isset($aroles[$switchrole])) {
+        role_switch($switchrole, $context);
+        // Double check that this role is allowed here
+        require_login($course);
+    }
+}
+
+// TODO: Using SESSION->returnurl is deprecated and should be removed in the future.
+// Till then this code remains to support any external applications calling this script.
+if (!empty($returnurl) && is_numeric($returnurl)) {
+    $returnurl = false;
+    if (!empty($SESSION->returnurl) && strpos($SESSION->returnurl, 'moodle_url')!==false) {
+        debugging('Code calling switchrole should be passing a URL as a param.', DEBUG_DEVELOPER);
+        $returnurl = @unserialize($SESSION->returnurl);
+        if (!($returnurl instanceof moodle_url)) {
+            $returnurl = false;
+        }
+    }
+}
+
+if ($returnurl === false) {
+    $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
+}
+
+redirect($returnurl);
diff --git a/tests/courselib_test.php b/tests/courselib_test.php
new file mode 100644 (file)
index 0000000..a59351b
--- /dev/null
@@ -0,0 +1,438 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course related unit tests
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/course/lib.php');
+
+class courselib_testcase extends advanced_testcase {
+
+    public function test_create_course() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+
+        $course = new stdClass();
+        $course->fullname = 'Apu loves Unit TÉ™sts';
+        $course->shortname = 'Spread the lÅ­ve';
+        $course->idnumber = '123';
+        $course->summary = 'Awesome!';
+        $course->summaryformat = FORMAT_PLAIN;
+        $course->format = 'topics';
+        $course->newsitems = 0;
+        $course->numsections = 5;
+        $course->category = $defaultcategory;
+
+        $created = create_course($course);
+        $context = context_course::instance($created->id);
+
+        // Compare original and created.
+        $original = (array) $course;
+        $this->assertEquals($original, array_intersect_key((array) $created, $original));
+
+        // Ensure default section is created.
+        $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
+        $this->assertTrue($sectioncreated);
+
+        // Ensure blocks have been associated to the course.
+        $blockcount = $DB->count_records('block_instances', array('parentcontextid' => $context->id));
+        $this->assertGreaterThan(0, $blockcount);
+    }
+
+    public function test_create_course_with_generator() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $course = $this->getDataGenerator()->create_course();
+
+        // Ensure default section is created.
+        $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
+        $this->assertTrue($sectioncreated);
+    }
+
+    public function test_create_course_sections() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course(
+                array('shortname' => 'GrowingCourse',
+                    'fullname' => 'Growing Course',
+                    'numsections' => 5),
+                array('createsections' => true));
+
+        // Ensure all 6 (0-5) sections were created and modinfo/sectioninfo cache works properly
+        $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
+        $this->assertEquals(range(0, $course->numsections), $sectionscreated);
+
+        // this will do nothing, section already exists
+        $this->assertFalse(course_create_sections_if_missing($course, $course->numsections));
+
+        // this will create new section
+        $this->assertTrue(course_create_sections_if_missing($course, $course->numsections + 1));
+
+        // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
+        $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
+        $this->assertEquals(range(0, $course->numsections + 1), $sectionscreated);
+    }
+
+    public function test_reorder_sections() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
+        $oldsections = array();
+        $sections = array();
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
+            $oldsections[$section->section] = $section->id;
+            $sections[$section->id] = $section->section;
+        }
+        ksort($oldsections);
+
+        $neworder = reorder_sections($sections, 2, 4);
+        $neworder = array_keys($neworder);
+        $this->assertEquals($oldsections[0], $neworder[0]);
+        $this->assertEquals($oldsections[1], $neworder[1]);
+        $this->assertEquals($oldsections[2], $neworder[4]);
+        $this->assertEquals($oldsections[3], $neworder[2]);
+        $this->assertEquals($oldsections[4], $neworder[3]);
+        $this->assertEquals($oldsections[5], $neworder[5]);
+        $this->assertEquals($oldsections[6], $neworder[6]);
+
+        $neworder = reorder_sections($sections, 4, 2);
+        $neworder = array_keys($neworder);
+        $this->assertEquals($oldsections[0], $neworder[0]);
+        $this->assertEquals($oldsections[1], $neworder[1]);
+        $this->assertEquals($oldsections[2], $neworder[3]);
+        $this->assertEquals($oldsections[3], $neworder[4]);
+        $this->assertEquals($oldsections[4], $neworder[2]);
+        $this->assertEquals($oldsections[5], $neworder[5]);
+        $this->assertEquals($oldsections[6], $neworder[6]);
+
+        $neworder = reorder_sections(1, 2, 4);
+        $this->assertFalse($neworder);
+    }
+
+    public function test_move_section_down() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
+        $oldsections = array();
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+            $oldsections[$section->section] = $section->id;
+        }
+        ksort($oldsections);
+
+        // Test move section down..
+        move_section_to($course, 2, 4);
+        $sections = array();
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+            $sections[$section->section] = $section->id;
+        }
+        ksort($sections);
+
+        $this->assertEquals($oldsections[0], $sections[0]);
+        $this->assertEquals($oldsections[1], $sections[1]);
+        $this->assertEquals($oldsections[2], $sections[4]);
+        $this->assertEquals($oldsections[3], $sections[2]);
+        $this->assertEquals($oldsections[4], $sections[3]);
+        $this->assertEquals($oldsections[5], $sections[5]);
+        $this->assertEquals($oldsections[6], $sections[6]);
+    }
+
+    public function test_move_section_up() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
+        $oldsections = array();
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+            $oldsections[$section->section] = $section->id;
+        }
+        ksort($oldsections);
+
+        // Test move section up..
+        move_section_to($course, 6, 4);
+        $sections = array();
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+            $sections[$section->section] = $section->id;
+        }
+        ksort($sections);
+
+        $this->assertEquals($oldsections[0], $sections[0]);
+        $this->assertEquals($oldsections[1], $sections[1]);
+        $this->assertEquals($oldsections[2], $sections[2]);
+        $this->assertEquals($oldsections[3], $sections[3]);
+        $this->assertEquals($oldsections[4], $sections[5]);
+        $this->assertEquals($oldsections[5], $sections[6]);
+        $this->assertEquals($oldsections[6], $sections[4]);
+    }
+
+    public function test_move_section_marker() {
+        global $DB;
+        $this->resetAfterTest(true);
+
+        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
+
+        // Set course marker to the section we are going to move..
+        course_set_marker($course->id, 2);
+        // Verify that the course marker is set correctly.
+        $course = $DB->get_record('course', array('id' => $course->id));
+        $this->assertEquals(2, $course->marker);
+
+        // Test move the marked section down..
+        move_section_to($course, 2, 4);
+
+        // Verify that the coruse marker has been moved along with the section..
+        $course = $DB->get_record('course', array('id' => $course->id));
+        $this->assertEquals(4, $course->marker);
+
+        // Test move the marked section up..
+        move_section_to($course, 4, 3);
+
+        // Verify that the course marker has been moved along with the section..
+        $course = $DB->get_record('course', array('id' => $course->id));
+        $this->assertEquals(3, $course->marker);
+
+        // Test moving a non-marked section above the marked section..
+        move_section_to($course, 4, 2);
+
+        // Verify that the course marker has been moved down to accomodate..
+        $course = $DB->get_record('course', array('id' => $course->id));
+        $this->assertEquals(4, $course->marker);
+
+        // Test moving a non-marked section below the marked section..
+        move_section_to($course, 3, 6);
+
+        // Verify that the course marker has been up to accomodate..
+        $course = $DB->get_record('course', array('id' => $course->id));
+        $this->assertEquals(3, $course->marker);
+    }
+
+    public function test_get_course_display_name_for_list() {
+        global $CFG;
+        $this->resetAfterTest(true);
+
+        $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
+
+        $CFG->courselistshortnames = 0;
+        $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
+
+        $CFG->courselistshortnames = 1;
+        $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
+    }
+
+    public function test_create_course_category() {
+        global $CFG, $DB;
+        $this->resetAfterTest(true);
+
+        // Create the category
+        $data = new stdClass();
+        $data->name = 'aaa';
+        $data->description = 'aaa';
+        $data->idnumber = '';
+
+        $category1 = create_course_category($data);
+
+        // Initially confirm that base data was inserted correctly
+        $this->assertEquals($data->name, $category1->name);
+        $this->assertEquals($data->description, $category1->description);
+        $this->assertEquals($data->idnumber, $category1->idnumber);
+
+        // sortorder should be blank initially
+        $this->assertEmpty($category1->sortorder);
+
+        // Calling fix_course_sortorder() should provide a new sortorder
+        fix_course_sortorder();
+        $category1 = $DB->get_record('course_categories', array('id' => $category1->id));
+
+        $this->assertGreaterThanOrEqual(1, $category1->sortorder);
+
+        // Create two more categories and test the sortorder worked correctly
+        $data->name = 'ccc';
+        $category2 = create_course_category($data);
+        $this->assertEmpty($category2->sortorder);
+
+        $data->name = 'bbb';
+        $category3 = create_course_category($data);
+        $this->assertEmpty($category3->sortorder);
+
+        // Calling fix_course_sortorder() should provide a new sortorder to give category1,
+        // category2, category3. New course categories are ordered by id not name
+        fix_course_sortorder();
+
+        $category1 = $DB->get_record('course_categories', array('id' => $category1->id));
+        $category2 = $DB->get_record('course_categories', array('id' => $category2->id));
+        $category3 = $DB->get_record('course_categories', array('id' => $category3->id));
+
+        $this->assertGreaterThanOrEqual($category1->sortorder, $category2->sortorder);
+        $this->assertGreaterThanOrEqual($category2->sortorder, $category3->sortorder);
+        $this->assertGreaterThanOrEqual($category1->sortorder, $category3->sortorder);
+    }
+
+    public function test_move_module_in_course() {
+        $this->resetAfterTest(true);
+        // Setup fixture
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>5));
+        $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
+
+        $cms = get_fast_modinfo($course)->get_cms();
+        $cm = reset($cms);
+
+        course_create_sections_if_missing($course, 3);
+        $section3 = get_fast_modinfo($course)->get_section_info(3);
+
+        moveto_module($cm, $section3);
+
+        $modinfo = get_fast_modinfo($course);
+        $this->assertTrue(empty($modinfo->sections[0]));
+        $this->assertFalse(empty($modinfo->sections[3]));
+    }
+
+    public function test_module_visibility() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        // Create course and modules.
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
+        $modules = compact('forum', 'assign');
+
+        // Hiding the modules.
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 0);
+            $this->check_module_visibility($mod, 0, 0);
+        }
+
+        // Showing the modules.
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 1);
+            $this->check_module_visibility($mod, 1, 1);
+        }
+    }
+
+    public function test_section_visibility() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        // Create course.
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
+
+        // Testing an empty section.
+        $sectionnumber = 1;
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+
+        // Testing a section with visible modules.
+        $sectionnumber = 2;
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+                array('section' => $sectionnumber));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
+                'course' => $course->id), array('section' => $sectionnumber));
+        $modules = compact('forum', 'assign');
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 1);
+        }
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 1, 1);
+        }
+
+        // Testing a section with hidden modules, which should stay hidden.
+        $sectionnumber = 3;
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+                array('section' => $sectionnumber));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
+                'course' => $course->id), array('section' => $sectionnumber));
+        $modules = compact('forum', 'assign');
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 0);
+            $this->check_module_visibility($mod, 0, 0);
+        }
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 0);
+        }
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 0);
+        }
+    }
+
+    /**
+     * Helper function to assert that a module has correctly been made visible, or hidden.
+     *
+     * @param stdClass $mod module information
+     * @param int $visibility the current state of the module
+     * @param int $visibleold the current state of the visibleold property
+     * @return void
+     */
+    public function check_module_visibility($mod, $visibility, $visibleold) {
+        global $DB;
+        $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
+        $this->assertEquals($visibility, $cm->visible);
+        $this->assertEquals($visibleold, $cm->visibleold);
+
+        // Check the module grade items.
+        $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
+                'iteminstance' => $cm->instance, 'courseid' => $cm->course));
+        if ($grade_items) {
+            foreach ($grade_items as $grade_item) {
+                if ($visibility) {
+                    $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
+                } else {
+                    $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
+                }
+            }
+        }
+
+        // Check the events visibility.
+        if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
+            foreach ($events as $event) {
+                $calevent = new calendar_event($event);
+                $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
+            }
+        }
+    }
+
+}
diff --git a/tests/courserequest_test.php b/tests/courserequest_test.php
new file mode 100644 (file)
index 0000000..d376772
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Course request related unit tests
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Frédéric Massart
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot.'/course/lib.php');
+
+class courserequest_testcase extends advanced_testcase {
+
+    public function test_create_request() {
+        global $DB, $USER;
+        $this->resetAfterTest(true);
+
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $defaultcategory);
+
+        // Create some categories.
+        $cat1 = $this->getDataGenerator()->create_category();
+        $cat2 = $this->getDataGenerator()->create_category();
+        $cat3 = $this->getDataGenerator()->create_category();
+
+        // Basic course request.
+        $data = new stdClass();
+        $data->fullname = 'HÉ™llo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum Â©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+        $cr = course_request::create($data);
+
+        $this->assertEquals($data->fullname, $cr->fullname);
+        $this->assertEquals($data->shortname, $cr->shortname);
+        $this->assertEquals($data->summary_editor['text'], $cr->summary);
+        $this->assertEquals($data->summary_editor['format'], $cr->summaryformat);
+        $this->assertEquals($data->reason, $cr->reason);
+        $this->assertEquals($USER->id, $cr->requester);
+        $this->assertEquals($defaultcategory, $cr->category);
+
+        // Request with category but category selection not allowed.
+        set_config('defaultrequestcategory', $cat2->id);
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $this->assertEquals($cat2->id, $cr->category);
+
+        // Request with category different than default and category selection allowed.
+        set_config('defaultrequestcategory', $cat3->id);
+        set_config('requestcategoryselection', 1);
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $this->assertEquals($cat1->id, $cr->category);
+    }
+
+    public function test_approve_request() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $this->preventResetByRollback();
+
+        $this->setAdminUser();
+        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $defaultcategory);
+
+        // Create some categories.
+        $cat1 = $this->getDataGenerator()->create_category();
+        $cat2 = $this->getDataGenerator()->create_category();
+
+        $data = new stdClass();
+        $data->fullname = 'HÉ™llo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum Â©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+
+        // Test without category.
+        $cr = course_request::create($data);
+        $id = $cr->approve();
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $course = $DB->get_record('course', array('id' => $id));
+        $this->assertEquals($data->fullname, $course->fullname);
+        $this->assertEquals($data->shortname, $course->shortname);
+        $this->assertEquals($data->summary_editor['text'], $course->summary);
+        $this->assertEquals($data->summary_editor['format'], $course->summaryformat);
+        $this->assertEquals(1, $course->requested);
+        $this->assertEquals($defaultcategory, $course->category);
+
+        // Test with category.
+        set_config('requestcategoryselection', 1);
+        set_config('defaultrequestcategory', $cat2->id);
+        $data->shortname .= ' 2nd';
+        $data->category = $cat1->id;
+        $cr = course_request::create($data);
+        $id = $cr->approve();
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $course = $DB->get_record('course', array('id' => $id));
+        $this->assertEquals($data->category, $course->category);
+    }
+
+    public function test_reject_request() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $this->preventResetByRollback();
+        $this->setAdminUser();
+        set_config('enablecourserequests', 1);
+        set_config('requestcategoryselection', 0);
+        set_config('defaultrequestcategory', $DB->get_field_select('course_categories', "MIN(id)", "parent=0"));
+
+        $data = new stdClass();
+        $data->fullname = 'HÉ™llo World!';
+        $data->shortname = 'Hi th€re!';
+        $data->summary_editor['text'] = 'Lorem Ipsum Â©';
+        $data->summary_editor['format'] = FORMAT_HTML;
+        $data->reason = 'Because PHP Unit is cool.';
+
+        $cr = course_request::create($data);
+        $this->assertTrue($DB->record_exists('course_request', array('id' => $cr->id)));
+        $cr->reject('Sorry!');
+        $this->assertDebuggingCalled(); // Caused by sending of message.
+        $this->assertFalse($DB->record_exists('course_request', array('id' => $cr->id)));
+    }
+
+}
diff --git a/tests/externallib_test.php b/tests/externallib_test.php
new file mode 100644 (file)
index 0000000..d6f8fa9
--- /dev/null
@@ -0,0 +1,640 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * External course functions unit tests
+ *
+ * @package    core_course
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->dirroot . '/webservice/tests/helpers.php');
+
+/**
+ * External course functions unit tests
+ *
+ * @package    core_course
+ * @category   external
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_course_external_testcase extends externallib_advanced_testcase {
+
+    /**
+     * Tests set up
+     */
+    protected function setUp() {
+        global $CFG;
+        require_once($CFG->dirroot . '/course/externallib.php');
+    }
+
+    /**
+     * Test create_categories
+     */
+    public function test_create_categories() {
+
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Set the required capabilities by the external function
+        $contextid = context_system::instance()->id;
+        $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
+
+        // Create base categories.
+        $category1 = new stdClass();
+        $category1->name = 'Root Test Category 1';
+        $category2 = new stdClass();
+        $category2->name = 'Root Test Category 2';
+        $category2->idnumber = 'rootcattest2';
+        $category2->desc = 'Description for root test category 1';
+        $category2->theme = 'base';
+        $categories = array(
+            array('name' => $category1->name, 'parent' => 0),
+            array('name' => $category2->name, 'parent' => 0, 'idnumber' => $category2->idnumber,
+                'description' => $category2->desc, 'theme' => $category2->theme)
+        );
+
+        $createdcats = core_course_external::create_categories($categories);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdcats);
+
+        // Initially confirm that base data was inserted correctly.
+        $this->assertEquals($category1->name, $createdcats[0]['name']);
+        $this->assertEquals($category2->name, $createdcats[1]['name']);
+
+        // Save the ids.
+        $category1->id = $createdcats[0]['id'];
+        $category2->id = $createdcats[1]['id'];
+
+        // Create on sub category.
+        $category3 = new stdClass();
+        $category3->name = 'Sub Root Test Category 3';
+        $subcategories = array(
+            array('name' => $category3->name, 'parent' => $category1->id)
+        );
+
+        $createdsubcats = core_course_external::create_categories($subcategories);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdsubcats = external_api::clean_returnvalue(core_course_external::create_categories_returns(), $createdsubcats);
+
+        // Confirm that sub categories were inserted correctly.
+        $this->assertEquals($category3->name, $createdsubcats[0]['name']);
+
+        // Save the ids.
+        $category3->id = $createdsubcats[0]['id'];
+
+        // Calling the ws function should provide a new sortorder to give category1,
+        // category2, category3. New course categories are ordered by id not name.
+        $category1 = $DB->get_record('course_categories', array('id' => $category1->id));
+        $category2 = $DB->get_record('course_categories', array('id' => $category2->id));
+        $category3 = $DB->get_record('course_categories', array('id' => $category3->id));
+
+        $this->assertGreaterThanOrEqual($category1->sortorder, $category3->sortorder);
+        $this->assertGreaterThanOrEqual($category2->sortorder, $category3->sortorder);
+
+        // Call without required capability
+        $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $createdsubcats = core_course_external::create_categories($subcategories);
+
+    }
+
+    /**
+     * Test delete categories
+     */
+    public function test_delete_categories() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Set the required capabilities by the external function
+        $contextid = context_system::instance()->id;
+        $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
+
+        $category1  = self::getDataGenerator()->create_category();
+        $category2  = self::getDataGenerator()->create_category(
+                array('parent' => $category1->id));
+        $category3  = self::getDataGenerator()->create_category();
+        $category4  = self::getDataGenerator()->create_category(
+                array('parent' => $category3->id));
+        $category5  = self::getDataGenerator()->create_category(
+                array('parent' => $category4->id));
+
+        //delete category 1 and 2 + delete category 4, category 5 moved under category 3
+        core_course_external::delete_categories(array(
+            array('id' => $category1->id, 'recursive' => 1),
+            array('id' => $category4->id)
+        ));
+
+        //check $category 1 and 2 are deleted
+        $notdeletedcount = $DB->count_records_select('course_categories',
+            'id IN ( ' . $category1->id . ',' . $category2->id . ',' . $category4->id . ')');
+        $this->assertEquals(0, $notdeletedcount);
+
+        //check that $category5 as $category3 for parent
+        $dbcategory5 = $DB->get_record('course_categories', array('id' => $category5->id));
+        $this->assertEquals($dbcategory5->path, $category3->path . '/' . $category5->id);
+
+         // Call without required capability
+        $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $createdsubcats = core_course_external::delete_categories(
+                array(array('id' => $category3->id)));
+    }
+
+    /**
+     * Test get categories
+     */
+    public function test_get_categories() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $generatedcats = array();
+        $category1data['idnumber'] = 'idnumbercat1';
+        $category1data['name'] = 'Category 1 for PHPunit test';
+        $category1data['description'] = 'Category 1 description';
+        $category1data['descriptionformat'] = FORMAT_MOODLE;
+        $category1  = self::getDataGenerator()->create_category($category1data);
+        $generatedcats[$category1->id] = $category1;
+        $category2  = self::getDataGenerator()->create_category(
+                array('parent' => $category1->id));
+        $generatedcats[$category2->id] = $category2;
+        $category6  = self::getDataGenerator()->create_category(
+                array('parent' => $category1->id, 'visible' => 0));
+        $generatedcats[$category6->id] = $category6;
+        $category3  = self::getDataGenerator()->create_category();
+        $generatedcats[$category3->id] = $category3;
+        $category4  = self::getDataGenerator()->create_category(
+                array('parent' => $category3->id));
+        $generatedcats[$category4->id] = $category4;
+        $category5  = self::getDataGenerator()->create_category(
+                array('parent' => $category4->id));
+        $generatedcats[$category5->id] = $category5;
+
+        // Set the required capabilities by the external function.
+        $context = context_system::instance();
+        $roleid = $this->assignUserCapability('moodle/category:manage', $context->id);
+
+        // Retrieve category1 + sub-categories except not visible ones
+        $categories = core_course_external::get_categories(array(
+            array('key' => 'id', 'value' => $category1->id),
+            array('key' => 'visible', 'value' => 1)), 1);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
+        // Check we retrieve the good total number of categories.
+        $this->assertEquals(2, count($categories));
+
+        // Check the return values
+        foreach ($categories as $category) {
+            $generatedcat = $generatedcats[$category['id']];
+            $this->assertEquals($category['idnumber'], $generatedcat->idnumber);
+            $this->assertEquals($category['name'], $generatedcat->name);
+            $this->assertEquals($category['description'], $generatedcat->description);
+            $this->assertEquals($category['descriptionformat'], FORMAT_HTML);
+        }
+
+        // Check different params.
+        $categories = core_course_external::get_categories(array(
+            array('key' => 'id', 'value' => $category1->id),
+            array('key' => 'idnumber', 'value' => $category1->idnumber),
+            array('key' => 'visible', 'value' => 1)), 0);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
+        $this->assertEquals(1, count($categories));
+
+        // Retrieve categories from parent.
+        $categories = core_course_external::get_categories(array(
+            array('key' => 'parent', 'value' => $category3->id)), 1);
+        $this->assertEquals(2, count($categories));
+
+        // Retrieve all categories.
+        $categories = core_course_external::get_categories();
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $categories = external_api::clean_returnvalue(core_course_external::get_categories_returns(), $categories);
+
+        $this->assertEquals($DB->count_records('course_categories'), count($categories));
+
+        // Call without required capability (it will fail cause of the search on idnumber).
+        $this->unassignUserCapability('moodle/category:manage', $context->id, $roleid);
+        $this->setExpectedException('moodle_exception');
+        $categories = core_course_external::get_categories(array(
+            array('key' => 'id', 'value' => $category1->id),
+            array('key' => 'idnumber', 'value' => $category1->idnumber),
+            array('key' => 'visible', 'value' => 1)), 0);
+    }
+
+    /**
+     * Test update_categories
+     */
+    public function test_update_categories() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Set the required capabilities by the external function
+        $contextid = context_system::instance()->id;
+        $roleid = $this->assignUserCapability('moodle/category:manage', $contextid);
+
+        // Create base categories.
+        $category1data['idnumber'] = 'idnumbercat1';
+        $category1data['name'] = 'Category 1 for PHPunit test';
+        $category1data['description'] = 'Category 1 description';
+        $category1data['descriptionformat'] = FORMAT_MOODLE;
+        $category1  = self::getDataGenerator()->create_category($category1data);
+        $category2  = self::getDataGenerator()->create_category(
+                array('parent' => $category1->id));
+        $category3  = self::getDataGenerator()->create_category();
+        $category4  = self::getDataGenerator()->create_category(
+                array('parent' => $category3->id));
+        $category5  = self::getDataGenerator()->create_category(
+                array('parent' => $category4->id));
+
+        // We update all category1 attribut.
+        // Then we move cat4 and cat5 parent: cat3 => cat1
+        $categories = array(
+            array('id' => $category1->id,
+                'name' => $category1->name . '_updated',
+                'idnumber' => $category1->idnumber . '_updated',
+                'description' => $category1->description . '_updated',
+                'descriptionformat' => FORMAT_HTML,
+                'theme' => $category1->theme),
+            array('id' => $category4->id, 'parent' => $category1->id));
+
+        core_course_external::update_categories($categories);
+
+        // Check the values were updated.
+        $dbcategories = $DB->get_records_select('course_categories',
+                'id IN (' . $category1->id . ',' . $category2->id . ',' . $category2->id
+                . ',' . $category3->id . ',' . $category4->id . ',' . $category5->id .')');
+        $this->assertEquals($category1->name . '_updated',
+                $dbcategories[$category1->id]->name);
+        $this->assertEquals($category1->idnumber . '_updated',
+                $dbcategories[$category1->id]->idnumber);
+        $this->assertEquals($category1->description . '_updated',
+                $dbcategories[$category1->id]->description);
+        $this->assertEquals(FORMAT_HTML, $dbcategories[$category1->id]->descriptionformat);
+
+        // Check that category4 and category5 have been properly moved.
+        $this->assertEquals('/' . $category1->id . '/' . $category4->id,
+                $dbcategories[$category4->id]->path);
+        $this->assertEquals('/' . $category1->id . '/' . $category4->id . '/' . $category5->id,
+                $dbcategories[$category5->id]->path);
+
+        // Call without required capability.
+        $this->unassignUserCapability('moodle/category:manage', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        core_course_external::update_categories($categories);
+    }
+
+    /**
+     * Test create_courses
+     */
+    public function test_create_courses() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Enable course completion.
+        set_config('enablecompletion', 1);
+
+        // Set the required capabilities by the external function
+        $contextid = context_system::instance()->id;
+        $roleid = $this->assignUserCapability('moodle/course:create', $contextid);
+        $this->assignUserCapability('moodle/course:visibility', $contextid, $roleid);
+
+        $category  = self::getDataGenerator()->create_category();
+
+        // Create base categories.
+        $course1['fullname'] = 'Test course 1';
+        $course1['shortname'] = 'Testcourse1';
+        $course1['categoryid'] = $category->id;
+        $course2['fullname'] = 'Test course 2';
+        $course2['shortname'] = 'Testcourse2';
+        $course2['categoryid'] = $category->id;
+        $course2['idnumber'] = 'testcourse2idnumber';
+        $course2['summary'] = 'Description for course 2';
+        $course2['summaryformat'] = FORMAT_MOODLE;
+        $course2['format'] = 'weeks';
+        $course2['showgrades'] = 1;
+        $course2['newsitems'] = 3;
+        $course2['startdate'] = 1420092000; // 01/01/2015
+        $course2['numsections'] = 4;
+        $course2['maxbytes'] = 100000;
+        $course2['showreports'] = 1;
+        $course2['visible'] = 0;
+        $course2['hiddensections'] = 0;
+        $course2['groupmode'] = 0;
+        $course2['groupmodeforce'] = 0;
+        $course2['defaultgroupingid'] = 0;
+        $course2['enablecompletion'] = 1;
+        $course2['completionstartonenrol'] = 1;
+        $course2['completionnotify'] = 1;
+        $course2['lang'] = 'en';
+        $course2['forcetheme'] = 'base';
+        $course3['fullname'] = 'Test course 3';
+        $course3['shortname'] = 'Testcourse3';
+        $course3['categoryid'] = $category->id;
+        $course3['format'] = 'topics';
+        $course3options = array('numsections' => 8,
+            'hiddensections' => 1,
+            'coursedisplay' => 1);
+        $course3['courseformatoptions'] = array();
+        foreach ($course3options as $key => $value) {
+            $course3['courseformatoptions'][] = array('name' => $key, 'value' => $value);
+        }
+        $courses = array($course1, $course2, $course3);
+
+        $createdcourses = core_course_external::create_courses($courses);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $createdcourses = external_api::clean_returnvalue(core_course_external::create_courses_returns(), $createdcourses);
+
+        // Check that right number of courses were created.
+        $this->assertEquals(3, count($createdcourses));
+
+        // Check that the courses were correctly created.
+        foreach ($createdcourses as $createdcourse) {
+            $courseinfo = course_get_format($createdcourse['id'])->get_course();
+
+            if ($createdcourse['shortname'] == $course2['shortname']) {
+                $this->assertEquals($courseinfo->fullname, $course2['fullname']);
+                $this->assertEquals($courseinfo->shortname, $course2['shortname']);
+                $this->assertEquals($courseinfo->category, $course2['categoryid']);
+                $this->assertEquals($courseinfo->idnumber, $course2['idnumber']);
+                $this->assertEquals($courseinfo->summary, $course2['summary']);
+                $this->assertEquals($courseinfo->summaryformat, $course2['summaryformat']);
+                $this->assertEquals($courseinfo->format, $course2['format']);
+                $this->assertEquals($courseinfo->showgrades, $course2['showgrades']);
+                $this->assertEquals($courseinfo->newsitems, $course2['newsitems']);
+                $this->assertEquals($courseinfo->startdate, $course2['startdate']);
+                $this->assertEquals($courseinfo->numsections, $course2['numsections']);
+                $this->assertEquals($courseinfo->maxbytes, $course2['maxbytes']);
+                $this->assertEquals($courseinfo->showreports, $course2['showreports']);
+                $this->assertEquals($courseinfo->visible, $course2['visible']);
+                $this->assertEquals($courseinfo->hiddensections, $course2['hiddensections']);
+                $this->assertEquals($courseinfo->groupmode, $course2['groupmode']);
+                $this->assertEquals($courseinfo->groupmodeforce, $course2['groupmodeforce']);
+                $this->assertEquals($courseinfo->defaultgroupingid, $course2['defaultgroupingid']);
+                $this->assertEquals($courseinfo->completionnotify, $course2['completionnotify']);
+                $this->assertEquals($courseinfo->lang, $course2['lang']);
+
+                if (!empty($CFG->allowcoursethemes)) {
+                    $this->assertEquals($courseinfo->theme, $course2['forcetheme']);
+                }
+
+                // We enabled completion at the beginning of the test.
+                $this->assertEquals($courseinfo->enablecompletion, $course2['enablecompletion']);
+                $this->assertEquals($courseinfo->completionstartonenrol, $course2['completionstartonenrol']);
+
+            } else if ($createdcourse['shortname'] == $course1['shortname']) {
+                $courseconfig = get_config('moodlecourse');
+                $this->assertEquals($courseinfo->fullname, $course1['fullname']);
+                $this->assertEquals($courseinfo->shortname, $course1['shortname']);
+                $this->assertEquals($courseinfo->category, $course1['categoryid']);
+                $this->assertEquals($courseinfo->summaryformat, FORMAT_HTML);
+                $this->assertEquals($courseinfo->format, $courseconfig->format);
+                $this->assertEquals($courseinfo->showgrades, $courseconfig->showgrades);
+                $this->assertEquals($courseinfo->newsitems, $courseconfig->newsitems);
+                $this->assertEquals($courseinfo->maxbytes, $courseconfig->maxbytes);
+                $this->assertEquals($courseinfo->showreports, $courseconfig->showreports);
+                $this->assertEquals($courseinfo->groupmode, $courseconfig->groupmode);
+                $this->assertEquals($courseinfo->groupmodeforce, $courseconfig->groupmodeforce);
+                $this->assertEquals($courseinfo->defaultgroupingid, 0);
+            } else if ($createdcourse['shortname'] == $course3['shortname']) {
+                $this->assertEquals($courseinfo->fullname, $course3['fullname']);
+                $this->assertEquals($courseinfo->shortname, $course3['shortname']);
+                $this->assertEquals($courseinfo->category, $course3['categoryid']);
+                $this->assertEquals($courseinfo->format, $course3['format']);
+                $this->assertEquals($courseinfo->hiddensections, $course3options['hiddensections']);
+                $this->assertEquals($courseinfo->numsections, $course3options['numsections']);
+                $this->assertEquals($courseinfo->coursedisplay, $course3options['coursedisplay']);
+            } else {
+                throw moodle_exception('Unexpected shortname');
+            }
+        }
+
+        // Call without required capability
+        $this->unassignUserCapability('moodle/course:create', $contextid, $roleid);
+        $this->setExpectedException('required_capability_exception');
+        $createdsubcats = core_course_external::create_courses($courses);
+    }
+
+    /**
+     * Test delete_courses
+     */
+    public function test_delete_courses() {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+
+        // Admin can delete a course.
+        $this->setAdminUser();
+        // Validate_context() will fail as the email is not set by $this->setAdminUser().
+        $USER->email = 'emailtopass@contextvalidation.me';
+
+        $course1  = self::getDataGenerator()->create_course();
+        $course2  = self::getDataGenerator()->create_course();
+        $course3  = self::getDataGenerator()->create_course();
+
+        // Delete courses.
+        core_course_external::delete_courses(array($course1->id, $course2->id));
+
+        // Check $course 1 and 2 are deleted.
+        $notdeletedcount = $DB->count_records_select('course',
+            'id IN ( ' . $course1->id . ',' . $course2->id . ')');
+        $this->assertEquals(0, $notdeletedcount);
+
+         // Fail when the user is not allow to access the course (enrolled) or is not admin.
+        $this->setGuestUser();
+        $this->setExpectedException('require_login_exception');
+        $createdsubcats = core_course_external::delete_courses(array($course3->id));
+    }
+
+    /**
+     * Test get_courses
+     */
+    public function test_get_courses () {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        $generatedcourses = array();
+        $coursedata['idnumber'] = 'idnumbercourse1';
+        $coursedata['fullname'] = 'Course 1 for PHPunit test';
+        $coursedata['summary'] = 'Course 1 description';
+        $coursedata['summaryformat'] = FORMAT_MOODLE;
+        $course1  = self::getDataGenerator()->create_course($coursedata);
+        $generatedcourses[$course1->id] = $course1;
+        $course2  = self::getDataGenerator()->create_course();
+        $generatedcourses[$course2->id] = $course2;
+        $course3  = self::getDataGenerator()->create_course(array('format' => 'topics'));
+        $generatedcourses[$course3->id] = $course3;
+
+        // Set the required capabilities by the external function.
+        $context = context_system::instance();
+        $roleid = $this->assignUserCapability('moodle/course:view', $context->id);
+        $this->assignUserCapability('moodle/course:update',
+                context_course::instance($course1->id)->id, $roleid);
+        $this->assignUserCapability('moodle/course:update',
+                context_course::instance($course2->id)->id, $roleid);
+        $this->assignUserCapability('moodle/course:update',
+                context_course::instance($course3->id)->id, $roleid);
+
+        $courses = core_course_external::get_courses(array('ids' =>
+            array($course1->id, $course2->id)));
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses);
+
+        // Check we retrieve the good total number of categories.
+        $this->assertEquals(2, count($courses));
+
+        foreach ($courses as $course) {
+            $dbcourse = $generatedcourses[$course['id']];
+            $this->assertEquals($course['idnumber'], $dbcourse->idnumber);
+            $this->assertEquals($course['fullname'], $dbcourse->fullname);
+            $this->assertEquals($course['summary'], $dbcourse->summary);
+            $this->assertEquals($course['summaryformat'], FORMAT_HTML);
+            $this->assertEquals($course['shortname'], $dbcourse->shortname);
+            $this->assertEquals($course['categoryid'], $dbcourse->category);
+            $this->assertEquals($course['format'], $dbcourse->format);
+            $this->assertEquals($course['showgrades'], $dbcourse->showgrades);
+            $this->assertEquals($course['newsitems'], $dbcourse->newsitems);
+            $this->assertEquals($course['startdate'], $dbcourse->startdate);
+            $this->assertEquals($course['numsections'], $dbcourse->numsections);
+            $this->assertEquals($course['maxbytes'], $dbcourse->maxbytes);
+            $this->assertEquals($course['showreports'], $dbcourse->showreports);
+            $this->assertEquals($course['visible'], $dbcourse->visible);
+            $this->assertEquals($course['hiddensections'], $dbcourse->hiddensections);
+            $this->assertEquals($course['groupmode'], $dbcourse->groupmode);
+            $this->assertEquals($course['groupmodeforce'], $dbcourse->groupmodeforce);
+            $this->assertEquals($course['defaultgroupingid'], $dbcourse->defaultgroupingid);
+            $this->assertEquals($course['completionnotify'], $dbcourse->completionnotify);
+            $this->assertEquals($course['lang'], $dbcourse->lang);
+            $this->assertEquals($course['forcetheme'], $dbcourse->theme);
+            $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol);
+            $this->assertEquals($course['enablecompletion'], $dbcourse->enablecompletion);
+            $this->assertEquals($course['completionstartonenrol'], $dbcourse->completionstartonenrol);
+            if ($dbcourse->format === 'topics') {
+                $this->assertEquals($course['courseformatoptions'], array(
+                    array('name' => 'numsections', 'value' => $dbcourse->numsections),
+                    array('name' => 'hiddensections', 'value' => $dbcourse->hiddensections),
+                    array('name' => 'coursedisplay', 'value' => $dbcourse->coursedisplay),
+                ));
+            }
+        }
+
+        // Get all courses in the DB
+        $courses = core_course_external::get_courses(array());
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_courses_returns(), $courses);
+
+        $this->assertEquals($DB->count_records('course'), count($courses));
+    }
+
+    /**
+     * Test get_course_contents
+     */
+    public function test_get_course_contents() {
+        $this->resetAfterTest(true);
+
+        $course  = self::getDataGenerator()->create_course();
+        $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
+        $forumcm = get_coursemodule_from_id('forum', $forum->cmid);
+        $forumcontext = context_module::instance($forum->cmid);
+        $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id));
+        $datacontext = context_module::instance($data->cmid);
+        $datacm = get_coursemodule_from_instance('page', $data->id);
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
+        $pagecontext = context_module::instance($page->cmid);
+        $pagecm = get_coursemodule_from_instance('page', $page->id);
+
+        // Set the required capabilities by the external function.
+        $context = context_course::instance($course->id);
+        $roleid = $this->assignUserCapability('moodle/course:view', $context->id);
+        $this->assignUserCapability('moodle/course:update', $context->id, $roleid);
+
+        $courses = core_course_external::get_course_contents($course->id, array());
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $courses = external_api::clean_returnvalue(core_course_external::get_course_contents_returns(), $courses);
+
+        // Check that the course has the 3 created modules
+        $this->assertEquals(3, count($courses[0]['modules']));
+    }
+
+    /**
+     * Test duplicate_course
+     */
+    public function test_duplicate_course() {
+        $this->resetAfterTest(true);
+
+        // Create one course with three modules.
+        $course  = self::getDataGenerator()->create_course();
+        $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
+        $forumcm = get_coursemodule_from_id('forum', $forum->cmid);
+        $forumcontext = context_module::instance($forum->cmid);
+        $data = $this->getDataGenerator()->create_module('data', array('assessed'=>1, 'scale'=>100, 'course'=>$course->id));
+        $datacontext = context_module::instance($data->cmid);
+        $datacm = get_coursemodule_from_instance('page', $data->id);
+        $page = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
+        $pagecontext = context_module::instance($page->cmid);
+        $pagecm = get_coursemodule_from_instance('page', $page->id);
+
+        // Set the required capabilities by the external function.
+        $coursecontext = context_course::instance($course->id);
+        $categorycontext = context_coursecat::instance($course->category);
+        $roleid = $this->assignUserCapability('moodle/course:create', $categorycontext->id);
+        $this->assignUserCapability('moodle/course:view', $categorycontext->id, $roleid);
+        $this->assignUserCapability('moodle/restore:restorecourse', $categorycontext->id, $roleid);
+        $this->assignUserCapability('moodle/backup:backupcourse', $coursecontext->id, $roleid);
+        $this->assignUserCapability('moodle/backup:configure', $coursecontext->id, $roleid);
+        // Optional capabilities to copy user data.
+        $this->assignUserCapability('moodle/backup:userinfo', $coursecontext->id, $roleid);
+        $this->assignUserCapability('moodle/restore:userinfo', $categorycontext->id, $roleid);
+
+        $newcourse['fullname'] = 'Course duplicate';
+        $newcourse['shortname'] = 'courseduplicate';
+        $newcourse['categoryid'] = $course->category;
+        $newcourse['visible'] = true;
+        $newcourse['options'][] = array('name' => 'users', 'value' => true);
+
+        $duplicate = core_course_external::duplicate_course($course->id, $newcourse['fullname'],
+                $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']);
+
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $duplicate = external_api::clean_returnvalue(core_course_external::duplicate_course_returns(), $duplicate);
+
+        // Check that the course has been duplicated.
+        $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
+    }
+}
diff --git a/togglecompletion.php b/togglecompletion.php
new file mode 100644 (file)
index 0000000..0be2108
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Toggles the manual completion flag for a particular activity or course completion
+ * and the current user.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package course
+ */
+
+require_once('../config.php');
+require_once($CFG->libdir.'/completionlib.php');
+
+// Parameters
+$cmid = optional_param('id', 0, PARAM_INT);
+$courseid = optional_param('course', 0, PARAM_INT);
+$confirm = optional_param('confirm', 0, PARAM_BOOL);
+
+if (!$cmid && !$courseid) {
+    print_error('invalidarguments');
+}
+
+// Process self completion
+if ($courseid) {
+    $PAGE->set_url(new moodle_url('/course/togglecompletion.php', array('course'=>$courseid)));
+
+    // Check user is logged in
+    $course = $DB->get_record('course', array('id' => $courseid), '*', MUST_EXIST);
+    $context = context_course::instance($course->id);
+    require_login($course);
+
+    $completion = new completion_info($course);
+    if (!$completion->is_enabled()) {
+        throw new moodle_exception('completionnotenabled', 'completion');
+    } elseif (!$completion->is_tracked_user($USER->id)) {
+        throw new moodle_exception('nottracked', 'completion');
+    }
+
+    // Check if we are marking a user complete via the completion report
+    $user = optional_param('user', 0, PARAM_INT);
+    $rolec = optional_param('rolec', 0, PARAM_INT);
+
+    if ($user && $rolec) {
+        require_sesskey();
+
+        completion_criteria::factory(array('id'=>$rolec, 'criteriatype'=>COMPLETION_CRITERIA_TYPE_ROLE)); //TODO: this is dumb, because it does not fetch the data?!?!
+        $criteria = completion_criteria_role::fetch(array('id'=>$rolec));
+
+        if ($criteria and user_has_role_assignment($USER->id, $criteria->role, $context->id)) {
+            $criteria_completions = $completion->get_completions($user, COMPLETION_CRITERIA_TYPE_ROLE);
+
+            foreach ($criteria_completions as $criteria_completion) {
+                if ($criteria_completion->criteriaid == $rolec) {
+                    $criteria->complete($criteria_completion);
+                    break;
+                }
+            }
+        }
+
+        // Return to previous page
+        if (!empty($_SERVER['HTTP_REFERER'])) {
+            redirect($_SERVER['HTTP_REFERER']);
+        } else {
+            redirect('view.php?id='.$course->id);
+        }
+
+    } else {
+
+        // Confirm with user
+        if ($confirm and confirm_sesskey()) {
+            $completion = $completion->get_completion($USER->id, COMPLETION_CRITERIA_TYPE_SELF);
+
+            if (!$completion) {
+                print_error('noselfcompletioncriteria', 'completion');
+            }
+
+            // Check if the user has already marked themselves as complete
+            if ($completion->is_complete()) {
+                print_error('useralreadymarkedcomplete', 'completion');
+            }
+
+            $completion->mark_complete();
+
+            redirect($CFG->wwwroot.'/course/view.php?id='.$courseid);
+            return;
+        }
+
+        $strconfirm = get_string('confirmselfcompletion', 'completion');
+        $PAGE->set_title($strconfirm);
+        $PAGE->set_heading($course->fullname);
+        $PAGE->navbar->add($strconfirm);
+        echo $OUTPUT->header();
+        $buttoncontinue = new single_button(new moodle_url('/course/togglecompletion.php', array('course'=>$courseid, 'confirm'=>1, 'sesskey'=>sesskey())), get_string('yes'), 'post');
+        $buttoncancel   = new single_button(new moodle_url('/course/view.php', array('id'=>$courseid)), get_string('no'), 'get');
+        echo $OUTPUT->confirm($strconfirm, $buttoncontinue, $buttoncancel);
+        echo $OUTPUT->footer();
+        exit;
+    }
+}
+
+
+$targetstate = required_param('completionstate', PARAM_INT);
+$fromajax    = optional_param('fromajax', 0, PARAM_INT);
+
+$PAGE->set_url('/course/togglecompletion.php', array('id'=>$cmid, 'completionstate'=>$targetstate));
+
+switch($targetstate) {
+    case COMPLETION_COMPLETE:
+    case COMPLETION_INCOMPLETE:
+        break;
+    default:
+        print_error('unsupportedstate');
+}
+
+// Get course-modules entry
+$cm = get_coursemodule_from_id(null, $cmid, null, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+
+// Check user is logged in
+require_login($course, false, $cm);
+
+if (isguestuser() or !confirm_sesskey()) {
+    print_error('error');
+}
+
+// Now change state
+$completion = new completion_info($course);
+if (!$completion->is_enabled()) {
+    throw new moodle_exception('completionnotenabled', 'completion');
+} elseif (!$completion->is_tracked_user($USER->id)) {
+    throw new moodle_exception('nottracked', 'completion');
+}
+
+// Check completion state is manual
+if($cm->completion != COMPLETION_TRACKING_MANUAL) {
+    error_or_ajax('cannotmanualctrack', $fromajax);
+}
+
+$completion->update_state($cm, $targetstate);
+
+// And redirect back to course
+if ($fromajax) {
+    print 'OK';
+} else {
+    // In case of use in other areas of code we allow a 'backto' parameter,
+    // otherwise go back to course page
+    $backto = optional_param('backto', 'view.php?id='.$course->id, PARAM_URL);
+    redirect($backto);
+}
+
+// utility functions
+
+function error_or_ajax($message, $fromajax) {
+    if ($fromajax) {
+        print get_string($message, 'error');
+        exit;
+    } else {
+        print_error($message);
+    }
+}
+
diff --git a/user.php b/user.php
new file mode 100644 (file)
index 0000000..73839e1
--- /dev/null
+++ b/user.php
@@ -0,0 +1,139 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Display user activity reports for a course
+ *
+ * @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");
+
+$id      = required_param('id',PARAM_INT);       // course id
+$user    = required_param('user',PARAM_INT);     // user id
+$mode    = optional_param('mode', "todaylogs", PARAM_ALPHA);
+
+$url = new moodle_url('/course/user.php', array('id'=>$id,'user'=>$user, 'mode'=>$mode));
+
+$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
+$user = $DB->get_record("user", array("id"=>$user, 'deleted'=>0), '*', MUST_EXIST);
+
+if ($mode === 'outline' or $mode === 'complete') {
+    $url = new moodle_url('/report/outline/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>$mode));
+    redirect($url);
+}
+if ($mode === 'todaylogs' or $mode === 'alllogs') {
+    $logmode = ($mode === 'todaylogs') ? 'today' : 'all';
+    $url = new moodle_url('/report/log/user.php', array('id'=>$user->id, 'course'=>$course->id, 'mode'=>$logmode));
+    redirect($url);
+}
+if ($mode === 'stats') {
+    $url = new moodle_url('/report/stats/user.php', array('id'=>$user->id, 'course'=>$course->id));
+    redirect($url);
+}
+if ($mode === 'coursecompletions' or $mode === 'coursecompletion') {
+    $url = new moodle_url('/report/completion/user.php', array('id'=>$user->id, 'course'=>$course->id));
+    redirect($url);
+}
+
+$coursecontext   = context_course::instance($course->id);
+$personalcontext = context_user::instance($user->id);
+
+$PAGE->set_url('/course/user.php', array('id'=>$id, 'user'=>$user->id, 'mode'=>$mode));
+
+require_login();
+$PAGE->set_pagelayout('admin');
+if (has_capability('moodle/user:viewuseractivitiesreport', $personalcontext) and !is_enrolled($coursecontext)) {
+    // do not require parents to be enrolled in courses ;-)
+    $PAGE->set_course($course);
+} else {
+    require_login($course);
+}
+
+if ($user->deleted) {
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading(get_string('userdeleted'));
+    echo $OUTPUT->footer();
+    die;
+}
+
+// prepare list of allowed modes
+$myreports  = ($course->showreports and $USER->id == $user->id);
+$anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
+
+$modes = array();
+
+if (has_capability('moodle/grade:viewall', $coursecontext)) {
+    //ok - can view all course grades
+    $modes[] = 'grade';
+
+} else if ($course->showgrades and $user->id == $USER->id and has_capability('moodle/grade:view', $coursecontext)) {
+    //ok - can view own grades
+    $modes[] = 'grade';
+
+} else if ($course->showgrades and has_capability('moodle/grade:viewall', $personalcontext)) {
+    // ok - can view grades of this user - parent most probably
+    $modes[] = 'grade';
+
+} else if ($course->showgrades and $anyreport) {
+    // ok - can view grades of this user - parent most probably
+    $modes[] = 'grade';
+}
+
+if (empty($modes)) {
+    require_capability('moodle/user:viewuseractivitiesreport', $personalcontext);
+}
+
+if (!in_array($mode, $modes)) {
+    // forbidden or non-existent mode
+    $mode = reset($modes);
+}
+
+add_to_log($course->id, "course", "user report", "user.php?id=$course->id&amp;user=$user->id&amp;mode=$mode", "$user->id");
+
+$stractivityreport = get_string("activityreport");
+
+$PAGE->navigation->extend_for_user($user);
+$PAGE->navigation->set_userid_for_parent_checks($user->id); // see MDL-25805 for reasons and for full commit reference for reversal when fixed.
+$PAGE->set_title("$course->shortname: $stractivityreport ($mode)");
+$PAGE->set_heading($course->fullname);
+echo $OUTPUT->header();
+
+switch ($mode) {
+    case "grade":
+        if (empty($CFG->grade_profilereport) or !file_exists($CFG->dirroot.'/grade/report/'.$CFG->grade_profilereport.'/lib.php')) {
+            $CFG->grade_profilereport = 'user';
+        }
+        require_once $CFG->libdir.'/gradelib.php';
+        require_once $CFG->dirroot.'/grade/lib.php';
+        require_once $CFG->dirroot.'/grade/report/'.$CFG->grade_profilereport.'/lib.php';
+
+        $functionname = 'grade_report_'.$CFG->grade_profilereport.'_profilereport';
+        if (function_exists($functionname)) {
+            $functionname($course, $user);
+        }
+        break;
+
+        break;
+    default:
+        // can not be reached ;-)
+}
+
+
+echo $OUTPUT->footer();
diff --git a/view.php b/view.php
new file mode 100644 (file)
index 0000000..6f40c36
--- /dev/null
+++ b/view.php
@@ -0,0 +1,288 @@
+<?php
+
+//  Display the course home page.
+
+    require_once('../config.php');
+    require_once('lib.php');
+    require_once($CFG->dirroot.'/mod/forum/lib.php');
+    require_once($CFG->libdir.'/conditionlib.php');
+    require_once($CFG->libdir.'/completionlib.php');
+
+    $id          = optional_param('id', 0, PARAM_INT);
+    $name        = optional_param('name', '', PARAM_RAW);
+    $edit        = optional_param('edit', -1, PARAM_BOOL);
+    $hide        = optional_param('hide', 0, PARAM_INT);
+    $show        = optional_param('show', 0, PARAM_INT);
+    $idnumber    = optional_param('idnumber', '', PARAM_RAW);
+    $sectionid   = optional_param('sectionid', 0, PARAM_INT);
+    $section     = optional_param('section', 0, PARAM_INT);
+    $move        = optional_param('move', 0, PARAM_INT);
+    $marker      = optional_param('marker',-1 , PARAM_INT);
+    $switchrole  = optional_param('switchrole',-1, PARAM_INT);
+    $modchooser  = optional_param('modchooser', -1, PARAM_BOOL);
+    $return      = optional_param('return', 0, PARAM_LOCALURL);
+
+    $params = array();
+    if (!empty($name)) {
+        $params = array('shortname' => $name);
+    } else if (!empty($idnumber)) {
+        $params = array('idnumber' => $idnumber);
+    } else if (!empty($id)) {
+        $params = array('id' => $id);
+    }else {
+        print_error('unspecifycourseid', 'error');
+    }
+
+    $course = $DB->get_record('course', $params, '*', MUST_EXIST);
+
+    $urlparams = array('id' => $course->id);
+
+    // Sectionid should get priority over section number
+    if ($sectionid) {
+        $section = $DB->get_field('course_sections', 'section', array('id' => $sectionid, 'course' => $course->id), MUST_EXIST);
+    }
+    if ($section) {
+        $urlparams['section'] = $section;
+    }
+
+    $PAGE->set_url('/course/view.php', $urlparams); // Defined here to avoid notices on errors etc
+
+    // Prevent caching of this page to stop confusion when changing page after making AJAX changes
+    $PAGE->set_cacheable(false);
+
+    preload_course_contexts($course->id);
+    $context = context_course::instance($course->id, MUST_EXIST);
+
+    // Remove any switched roles before checking login
+    if ($switchrole == 0 && confirm_sesskey()) {
+        role_switch($switchrole, $context);
+    }
+
+    require_login($course);
+
+    // Switchrole - sanity check in cost-order...
+    $reset_user_allowed_editing = false;
+    if ($switchrole > 0 && confirm_sesskey() &&
+        has_capability('moodle/role:switchroles', $context)) {
+        // is this role assignable in this context?
+        // inquiring minds want to know...
+        $aroles = get_switchable_roles($context);
+        if (is_array($aroles) && isset($aroles[$switchrole])) {
+            role_switch($switchrole, $context);
+            // Double check that this role is allowed here
+            require_login($course);
+        }
+        // reset course page state - this prevents some weird problems ;-)
+        $USER->activitycopy = false;
+        $USER->activitycopycourse = NULL;
+        unset($USER->activitycopyname);
+        unset($SESSION->modform);
+        $USER->editing = 0;
+        $reset_user_allowed_editing = true;
+    }
+
+    //If course is hosted on an external server, redirect to corresponding
+    //url with appropriate authentication attached as parameter
+    if (file_exists($CFG->dirroot .'/course/externservercourse.php')) {
+        include $CFG->dirroot .'/course/externservercourse.php';
+        if (function_exists('extern_server_course')) {
+            if ($extern_url = extern_server_course($course)) {
+                redirect($extern_url);
+            }
+        }
+    }
+
+
+    require_once($CFG->dirroot.'/calendar/lib.php');    /// This is after login because it needs $USER
+
+    $logparam = 'id='. $course->id;
+    $loglabel = 'view';
+    $infoid = $course->id;
+    if ($section and $section > 0) {
+        $loglabel = 'view section';
+
+        // Get section details and check it exists.
+        $modinfo = get_fast_modinfo($course);
+        $coursesections = $modinfo->get_section_info($section, MUST_EXIST);
+
+        // Check user is allowed to see it.
+        if (!$coursesections->uservisible) {
+            // Note: We actually already know they don't have this capability
+            // or uservisible would have been true; this is just to get the
+            // correct error message shown.
+            require_capability('moodle/course:viewhiddensections', $context);
+        }
+        $infoid = $coursesections->id;
+        $logparam .= '&sectionid='. $infoid;
+    }
+    add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
+
+    // Fix course format if it is no longer installed
+    $course->format = course_get_format($course)->get_format();
+
+    $PAGE->set_pagelayout('course');
+    $PAGE->set_pagetype('course-view-' . $course->format);
+    $PAGE->set_other_editing_capability('moodle/course:manageactivities');
+
+    // Preload course format renderer before output starts.
+    // This is a little hacky but necessary since
+    // format.php is not included until after output starts
+    if (file_exists($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php')) {
+        require_once($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php');
+        if (class_exists('format_'.$course->format.'_renderer')) {
+            // call get_renderer only if renderer is defined in format plugin
+            // otherwise an exception would be thrown
+            $PAGE->get_renderer('format_'. $course->format);
+        }
+    }
+
+    if ($reset_user_allowed_editing) {
+        // ugly hack
+        unset($PAGE->_user_allowed_editing);
+    }
+
+    if (!isset($USER->editing)) {
+        $USER->editing = 0;
+    }
+    if ($PAGE->user_allowed_editing()) {
+        if (($edit == 1) and confirm_sesskey()) {
+            $USER->editing = 1;
+            // Redirect to site root if Editing is toggled on frontpage
+            if ($course->id == SITEID) {
+                redirect($CFG->wwwroot .'/?redirect=0');
+            } else if (!empty($return)) {
+                redirect($CFG->wwwroot . $return);
+            } else {
+                $url = new moodle_url($PAGE->url, array('notifyeditingon' => 1));
+                redirect($url);
+            }
+        } else if (($edit == 0) and confirm_sesskey()) {
+            $USER->editing = 0;
+            if(!empty($USER->activitycopy) && $USER->activitycopycourse == $course->id) {
+                $USER->activitycopy       = false;
+                $USER->activitycopycourse = NULL;
+            }
+            // Redirect to site root if Editing is toggled on frontpage
+            if ($course->id == SITEID) {
+                redirect($CFG->wwwroot .'/?redirect=0');
+            } else if (!empty($return)) {
+                redirect($CFG->wwwroot . $return);
+            } else {
+                redirect($PAGE->url);
+            }
+        }
+        if (($modchooser == 1) && confirm_sesskey()) {
+            set_user_preference('usemodchooser', $modchooser);
+        } else if (($modchooser == 0) && confirm_sesskey()) {
+            set_user_preference('usemodchooser', $modchooser);
+        }
+
+        if (has_capability('moodle/course:sectionvisibility', $context)) {
+            if ($hide && confirm_sesskey()) {
+                set_section_visible($course->id, $hide, '0');
+                redirect($PAGE->url);
+            }
+
+            if ($show && confirm_sesskey()) {
+                set_section_visible($course->id, $show, '1');
+                redirect($PAGE->url);
+            }
+        }
+
+        if (has_capability('moodle/course:update', $context)) {
+            if (!empty($section)) {
+                if (!empty($move) and has_capability('moodle/course:movesections', $context) and confirm_sesskey()) {
+                    $destsection = $section + $move;
+                    if (move_section_to($course, $section, $destsection)) {
+                        if ($course->id == SITEID) {
+                            redirect($CFG->wwwroot . '/?redirect=0');
+                        } else {
+                            redirect(course_get_url($course));
+                        }
+                    } else {
+                        echo $OUTPUT->notification('An error occurred while moving a section');
+                    }
+                }
+            }
+        }
+    } else {
+        $USER->editing = 0;
+    }
+
+    $SESSION->fromdiscussion = $PAGE->url->out(false);
+
+
+    if ($course->id == SITEID) {
+        // This course is not a real course.
+        redirect($CFG->wwwroot .'/');
+    }
+
+    $completion = new completion_info($course);
+    if ($completion->is_enabled() && ajaxenabled()) {
+        $PAGE->requires->string_for_js('completion-title-manual-y', 'completion');
+        $PAGE->requires->string_for_js('completion-title-manual-n', 'completion');
+        $PAGE->requires->string_for_js('completion-alt-manual-y', 'completion');
+        $PAGE->requires->string_for_js('completion-alt-manual-n', 'completion');
+
+        $PAGE->requires->js_init_call('M.core_completion.init');
+    }
+
+    // We are currently keeping the button here from 1.x to help new teachers figure out
+    // what to do, even though the link also appears in the course admin block.  It also
+    // means you can back out of a situation where you removed the admin block. :)
+    if ($PAGE->user_allowed_editing()) {
+        $buttons = $OUTPUT->edit_button($PAGE->url);
+        $PAGE->set_button($buttons);
+    }
+
+    $PAGE->set_title(get_string('course') . ': ' . $course->fullname);
+    $PAGE->set_heading($course->fullname);
+    echo $OUTPUT->header();
+
+    if ($completion->is_enabled() && ajaxenabled()) {
+        // This value tracks whether there has been a dynamic change to the page.
+        // It is used so that if a user does this - (a) set some tickmarks, (b)
+        // go to another page, (c) clicks Back button - the page will
+        // automatically reload. Otherwise it would start with the wrong tick
+        // values.
+        echo html_writer::start_tag('form', array('action'=>'.', 'method'=>'get'));
+        echo html_writer::start_tag('div');
+        echo html_writer::empty_tag('input', array('type'=>'hidden', 'id'=>'completion_dynamic_change', 'name'=>'completion_dynamic_change', 'value'=>'0'));
+        echo html_writer::end_tag('div');
+        echo html_writer::end_tag('form');
+    }
+
+    // Course wrapper start.
+    echo html_writer::start_tag('div', array('class'=>'course-content'));
+
+    // make sure that section 0 exists (this function will create one if it is missing)
+    course_create_sections_if_missing($course, 0);
+
+    // get information about course modules and existing module types
+    // format.php in course formats may rely on presence of these variables
+    $modinfo = get_fast_modinfo($course);
+    $modnames = get_module_types_names();
+    $modnamesplural = get_module_types_names(true);
+    $modnamesused = $modinfo->get_used_module_names();
+    $mods = $modinfo->get_cms();
+    $sections = $modinfo->get_section_info_all();
+
+    // CAUTION, hacky fundamental variable defintion to follow!
+    // Note that because of the way course fromats are constructed though
+    // inclusion we pass parameters around this way..
+    $displaysection = $section;
+
+    // Include the actual course format.
+    require($CFG->dirroot .'/course/format/'. $course->format .'/format.php');
+    // Content wrapper end.
+
+    echo html_writer::end_tag('div');
+
+    // Include course AJAX
+    if (include_course_ajax($course, $modnamesused)) {
+        // Add the module chooser
+        $renderer = $PAGE->get_renderer('core', 'course');
+        echo $renderer->course_modchooser(get_module_metadata($course, $modnames, $displaysection), $course);
+    }
+
+    echo $OUTPUT->footer();
diff --git a/yui/coursebase/coursebase.js b/yui/coursebase/coursebase.js
new file mode 100644 (file)
index 0000000..b02c362
--- /dev/null
@@ -0,0 +1,224 @@
+YUI.add('moodle-course-coursebase', function(Y) {
+
+    /**
+     * The coursebase class
+     */
+    var COURSEBASENAME = 'course-coursebase';
+
+    var COURSEBASE = function() {
+        COURSEBASE.superclass.constructor.apply(this, arguments);
+    }
+
+    Y.extend(COURSEBASE, Y.Base, {
+        // Registered Modules
+        registermodules : [],
+
+        /**
+         * Initialize the coursebase module
+         */
+        initializer : function(config) {
+            // We don't actually perform any work here
+        },
+
+        /**
+         * Register a new Javascript Module
+         *
+         * @param object The instantiated module to call functions on
+         */
+        register_module : function(object) {
+            this.registermodules.push(object);
+        },
+
+        /**
+         * Invoke the specified function in all registered modules with the given arguments
+         *
+         * @param functionname The name of the function to call
+         * @param args The argument supplied to the function
+         */
+        invoke_function : function(functionname, args) {
+            for (module in this.registermodules) {
+                if (functionname in this.registermodules[module]) {
+                    this.registermodules[module][functionname](args);
+                }
+            }
+        }
+    },
+    {
+        NAME : COURSEBASENAME,
+        ATTRS : {}
+    }
+    );
+
+    // Ensure that M.course exists and that coursebase is initialised correctly
+    M.course = M.course || {};
+    M.course.coursebase = M.course.coursebase || new COURSEBASE();
+
+    // Abstract functions that needs to be defined per format (course/format/somename/format.js)
+    M.course.format = M.course.format || {}
+
+   /**
+    * Swap section (should be defined in format.js if requred)
+    *
+    * @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 = M.course.format.swap_sections || function(Y, node1, node2) {
+        return null;
+    }
+
+   /**
+    * Process sections after ajax response (should be defined in format.js)
+    * If some response is expected, we pass it over to format, as it knows better
+    * hot to process it.
+    *
+    * @param {YUI} Y YUI3 instance
+    * @param {NodeList} list of sections
+    * @param {array} response ajax response
+    * @param {string} sectionfrom first affected section
+    * @param {string} sectionto last affected section
+    * @return void
+    */
+    M.course.format.process_sections = M.course.format.process_sections || function(Y, sectionlist, response, sectionfrom, sectionto) {
+        return null;
+    }
+
+   /**
+    * Get sections config for this format, for examples see function definition
+    * in the formats.
+    *
+    * @return {object} section list configuration
+    */
+    M.course.format.get_config = M.course.format.get_config || function() {
+        return {
+            container_node : null, // compulsory
+            container_class : null, // compulsory
+            section_wrapper_node : null, // optional
+            section_wrapper_class : null, // optional
+            section_node : null,  // compulsory
+            section_class : null  // compulsory
+        }
+    }
+
+   /**
+    * Get section list for this format (usually items inside container_node.container_class selector)
+    *
+    * @param {YUI} Y YUI3 instance
+    * @return {string} section selector
+    */
+    M.course.format.get_section_selector = M.course.format.get_section_selector || function(Y) {
+        var config = M.course.format.get_config();
+        if (config.section_node && config.section_class) {
+            return config.section_node + '.' + config.section_class;
+        }
+        console.log('section_node and section_class are not defined in M.course.format.get_config');
+        return null;
+    }
+
+   /**
+    * Get section wraper for this format (only used in case when each
+    * container_node.container_class node is wrapped in some other element).
+    *
+    * @param {YUI} Y YUI3 instance
+    * @return {string} section wrapper selector or M.course.format.get_section_selector
+    * if section_wrapper_node and section_wrapper_class are not defined in the format config.
+    */
+    M.course.format.get_section_wrapper = M.course.format.get_section_wrapper || function(Y) {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_node && config.section_wrapper_class) {
+            return config.section_wrapper_node + '.' + config.section_wrapper_class;
+        }
+        return M.course.format.get_section_selector(Y);
+    }
+
+   /**
+    * Get the tag of container node
+    *
+    * @return {string} tag of container node.
+    */
+    M.course.format.get_containernode = M.course.format.get_containernode || function() {
+        var config = M.course.format.get_config();
+        if (config.container_node) {
+            return config.container_node;
+        } else {
+            console.log('container_node is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the class of container node
+    *
+    * @return {string} class of the container node.
+    */
+    M.course.format.get_containerclass = M.course.format.get_containerclass || function() {
+        var config = M.course.format.get_config();
+        if (config.container_class) {
+            return config.container_class;
+        } else {
+            console.log('container_class is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the tag of draggable node (section wrapper if exists, otherwise section)
+    *
+    * @return {string} tag of the draggable node.
+    */
+    M.course.format.get_sectionwrappernode = M.course.format.get_sectionwrappernode || function() {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_node) {
+            return config.section_wrapper_node;
+        } else {
+            return config.section_node;
+        }
+    }
+
+   /**
+    * Get the class of draggable node (section wrapper if exists, otherwise section)
+    *
+    * @return {string} class of the draggable node.
+    */
+    M.course.format.get_sectionwrapperclass = M.course.format.get_sectionwrapperclass || function() {
+        var config = M.course.format.get_config();
+        if (config.section_wrapper_class) {
+            return config.section_wrapper_class;
+        } else {
+            return config.section_class;
+        }
+    }
+
+   /**
+    * Get the tag of section node
+    *
+    * @return {string} tag of section node.
+    */
+    M.course.format.get_sectionnode = M.course.format.get_sectionnode || function() {
+        var config = M.course.format.get_config();
+        if (config.section_node) {
+            return config.section_node;
+        } else {
+            console.log('section_node is not defined in M.course.format.get_config');
+        }
+    }
+
+   /**
+    * Get the class of section node
+    *
+    * @return {string} class of the section node.
+    */
+    M.course.format.get_sectionclass = M.course.format.get_sectionclass || function() {
+        var config = M.course.format.get_config();
+        if (config.section_class) {
+            return config.section_class;
+        } else {
+            console.log('section_class is not defined in M.course.format.get_config');
+        }
+
+    }
+
+},
+'@VERSION@', {
+    requires : ['base', 'node']
+}
+);
diff --git a/yui/dragdrop/dragdrop.js b/yui/dragdrop/dragdrop.js
new file mode 100644 (file)
index 0000000..6872ece
--- /dev/null
@@ -0,0 +1,428 @@
+YUI.add('moodle-course-dragdrop', function(Y) {
+
+    var CSS = {
+        ACTIVITY : 'activity',
+        COMMANDSPAN : 'span.commands',
+        CONTENT : 'content',
+        COURSECONTENT : 'course-content',
+        EDITINGMOVE : 'editing_move',
+        ICONCLASS : 'iconsmall',
+        JUMPMENU : 'jumpmenu',
+        LEFT : 'left',
+        LIGHTBOX : 'lightbox',
+        MOVEDOWN : 'movedown',
+        MOVEUP : 'moveup',
+        PAGECONTENT : 'page-content',
+        RIGHT : 'right',
+        SECTION : 'section',
+        SECTIONADDMENUS : 'section_add_menus',
+        SECTIONHANDLE : 'section-handle',
+        SUMMARY : 'summary'
+    };
+
+    var DRAGSECTION = function() {
+        DRAGSECTION.superclass.constructor.apply(this, arguments);
+    };
+    Y.extend(DRAGSECTION, M.core.dragdrop, {
+        sectionlistselector : null,
+
+        initializer : function(params) {
+            // Set group for parent class
+            this.groups = ['section'];
+            this.samenodeclass = M.course.format.get_sectionwrapperclass();
+            this.parentnodeclass = M.course.format.get_containerclass();
+
+            // Check if we are in single section mode
+            if (Y.Node.one('.'+CSS.JUMPMENU)) {
+                return false;
+            }
+            // Initialise sections dragging
+            this.sectionlistselector = M.course.format.get_section_wrapper(Y);
+            if (this.sectionlistselector) {
+                this.sectionlistselector = '.'+CSS.COURSECONTENT+' '+this.sectionlistselector;
+                this.setup_for_section(this.sectionlistselector);
+
+                // Make each li element in the lists of sections draggable
+                var nodeselector = this.sectionlistselector.slice(CSS.COURSECONTENT.length+2);
+                var del = new Y.DD.Delegate({
+                    container: '.'+CSS.COURSECONTENT,
+                    nodes: nodeselector,
+                    target: true,
+                    handles: ['.'+CSS.LEFT],
+                    dragConfig: {groups: this.groups}
+                });
+                del.dd.plug(Y.Plugin.DDProxy, {
+                    // Don't move the node at the end of the drag
+                    moveOnEnd: false
+                });
+                del.dd.plug(Y.Plugin.DDConstrained, {
+                    // Keep it inside the .course-content
+                    constrain: '#'+CSS.PAGECONTENT,
+                    stickY: true
+                });
+                del.dd.plug(Y.Plugin.DDWinScroll);
+            }
+        },
+
+         /**
+         * Apply dragdrop features to the specified selector or node that refers to section(s)
+         *
+         * @param baseselector The CSS selector or node to limit scope to
+         * @return void
+         */
+        setup_for_section : function(baseselector) {
+            Y.Node.all(baseselector).each(function(sectionnode) {
+                // Determine the section ID
+                var sectionid = this.get_section_id(sectionnode);
+
+                // We skip the top section as it is not draggable
+                if (sectionid > 0) {
+                    // Remove move icons
+                    var movedown = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEDOWN);
+                    var moveup = sectionnode.one('.'+CSS.RIGHT+' a.'+CSS.MOVEUP);
+
+                    // Add dragger icon
+                    var title = M.util.get_string('movesection', 'moodle', sectionid);
+                    var cssleft = sectionnode.one('.'+CSS.LEFT);
+
+                    if ((movedown || moveup) && cssleft) {
+                        cssleft.setStyle('cursor', 'move');
+                        cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
+
+                        if (moveup) {
+                            moveup.remove();
+                        }
+                        if (movedown) {
+                            movedown.remove();
+                        }
+                    }
+                }
+            }, this);
+        },
+
+        get_section_id : function(node) {
+            return Number(node.get('id').replace(/section-/i, ''));
+        },
+
+        /*
+         * Drag-dropping related functions
+         */
+        drag_start : function(e) {
+            // Get our drag object
+            var drag = e.target;
+            // Creat a dummy structure of the outer elemnents for clean styles application
+            var containernode = Y.Node.create('<'+M.course.format.get_containernode()+'></'+M.course.format.get_containernode()+'>');
+            containernode.addClass(M.course.format.get_containerclass());
+            var sectionnode = Y.Node.create('<'+ M.course.format.get_sectionwrappernode()+'></'+ M.course.format.get_sectionwrappernode()+'>');
+            sectionnode.addClass( M.course.format.get_sectionwrapperclass());
+            sectionnode.setStyle('margin', 0);
+            sectionnode.setContent(drag.get('node').get('innerHTML'));
+            containernode.appendChild(sectionnode);
+            drag.get('dragNode').setContent(containernode);
+            drag.get('dragNode').addClass(CSS.COURSECONTENT);
+        },
+
+        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');
+            // Prepare some variables
+            var dragnodeid = Number(this.get_section_id(dragnode));
+            var dropnodeid = Number(this.get_section_id(dropnode));
+
+            var loopstart = dragnodeid;
+            var loopend = dropnodeid;
+
+            if (this.goingup) {
+                loopstart = dropnodeid;
+                loopend = dragnodeid;
+            }
+
+            // Get the list of nodes
+            drag.get('dragNode').removeClass(CSS.COURSECONTENT);
+            var sectionlist = Y.Node.all(this.sectionlistselector);
+
+            // Add lightbox if it not there
+            var lightbox = M.util.add_lightbox(Y, dragnode);
+
+            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'] = 'section';
+            params.field = 'move';
+            params.id = dragnodeid;
+            params.value = dropnodeid;
+
+            // Do AJAX request
+            var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+            Y.io(uri, {
+                method: 'POST',
+                data: params,
+                on: {
+                    start : function(tid) {
+                        lightbox.show();
+                    },
+                    success: function(tid, response) {
+                        // Update section titles, we can't simply swap them as
+                        // they might have custom title
+                        try {
+                            var responsetext = Y.JSON.parse(response.responseText);
+                            if (responsetext.error) {
+                                new M.core.ajaxException(responsetext);
+                            }
+                            M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
+                        } catch (e) {}
+
+                        // Classic bubble sort algorithm is applied to the section
+                        // nodes between original drag node location and the new one.
+                        do {
+                            var swapped = false;
+                            for (var i = loopstart; i <= loopend; i++) {
+                                if (this.get_section_id(sectionlist.item(i-1)) > this.get_section_id(sectionlist.item(i))) {
+                                    // Swap section id
+                                    var sectionid = sectionlist.item(i-1).get('id');
+                                    sectionlist.item(i-1).set('id', sectionlist.item(i).get('id'));
+                                    sectionlist.item(i).set('id', sectionid);
+                                    // See what format needs to swap
+                                    M.course.format.swap_sections(Y, i-1, i);
+                                    // Update flag
+                                    swapped = true;
+                                }
+                            }
+                            loopend = loopend - 1;
+                        } while (swapped);
+
+                        // Finally, hide the lightbox
+                        window.setTimeout(function(e) {
+                            lightbox.hide();
+                        }, 250);
+                    },
+                    failure: function(tid, response) {
+                        this.ajax_failure(response);
+                        lightbox.hide();
+                    }
+                },
+                context:this
+            });
+        }
+
+    }, {
+        NAME : 'course-dragdrop-section',
+        ATTRS : {
+            courseid : {
+                value : null
+            },
+            ajaxurl : {
+                'value' : 0
+            },
+            config : {
+                'value' : 0
+            }
+        }
+    });
+
+    var DRAGRESOURCE = function() {
+        DRAGRESOURCE.superclass.constructor.apply(this, arguments);
+    };
+    Y.extend(DRAGRESOURCE, M.core.dragdrop, {
+        initializer : function(params) {
+            // Set group for parent class
+            this.groups = ['resource'];
+            this.samenodeclass = CSS.ACTIVITY;
+            this.parentnodeclass = CSS.SECTION;
+            this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS);
+
+            // Go through all sections
+            var sectionlistselector = M.course.format.get_section_selector(Y);
+            if (sectionlistselector) {
+                sectionlistselector = '.'+CSS.COURSECONTENT+' '+sectionlistselector;
+                this.setup_for_section(sectionlistselector);
+
+                // Initialise drag & drop for all resources/activities
+                var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length+2)+' li.'+CSS.ACTIVITY;
+                var del = new Y.DD.Delegate({
+                    container: '.'+CSS.COURSECONTENT,
+                    nodes: nodeselector,
+                    target: true,
+                    handles: ['.' + CSS.EDITINGMOVE],
+                    dragConfig: {groups: this.groups}
+                });
+                del.dd.plug(Y.Plugin.DDProxy, {
+                    // Don't move the node at the end of the drag
+                    moveOnEnd: false,
+                    cloneNode: true
+                });
+                del.dd.plug(Y.Plugin.DDConstrained, {
+                    // Keep it inside the .course-content
+                    constrain: '#'+CSS.PAGECONTENT
+                });
+                del.dd.plug(Y.Plugin.DDWinScroll);
+
+                M.course.coursebase.register_module(this);
+                M.course.dragres = this;
+            }
+        },
+
+         /**
+         * Apply dragdrop features to the specified selector or node that refers to section(s)
+         *
+         * @param baseselector The CSS selector or node to limit scope to
+         * @return void
+         */
+        setup_for_section : function(baseselector) {
+            Y.Node.all(baseselector).each(function(sectionnode) {
+                var resources = sectionnode.one('.'+CSS.CONTENT+' ul.'+CSS.SECTION);
+                // See if resources ul exists, if not create one
+                if (!resources) {
+                    var resources = Y.Node.create('<ul></ul>');
+                    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 (file)
index 0000000..0597c4b
--- /dev/null
@@ -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 (file)
index 0000000..10fdded
--- /dev/null
@@ -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("<a href='#' />");
+            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') + '&section=' + 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 (file)
index 0000000..4e6ad15
--- /dev/null
@@ -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 <li> 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 <li> 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 <li> 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('<img />')
+                .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('<a />')
+                .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('<input />')
+                .setAttrs({
+                    'name'  : 'title',
+                    'value' : titletext,
+                    'autocomplete' : 'off'
+                })
+                .addClass('titleeditor');
+            var editform = Y.Node.create('<form />')
+                .addClass('activityinstance')
+                .setAttribute('action', '#');
+            var editinstructions = Y.Node.create('<span />')
+                .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']
+}
+);