Merge branch 'MDL-38395' of git://github.com/timhunt/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 19 Mar 2013 22:06:06 +0000 (23:06 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 19 Mar 2013 22:06:06 +0000 (23:06 +0100)
92 files changed:
admin/roles/lib.php
admin/settings/plugins.php
admin/settings/users.php
admin/tool/unsuproles/settings.php
admin/webservice/lib.php
cohort/locallib.php
comment/lib.php
composer.json
course/completion_form.php
course/dndupload.js
course/dnduploadlib.php
course/edit_form.php
course/editsection_form.php
course/lib.php
course/loginas.php
course/modedit.php
course/modlib.php [new file with mode: 0644]
course/recent.php
course/tests/courselib_test.php
enrol/ajax.php
enrol/authorize/enrol_form.php
enrol/manual/ajax.php
enrol/manual/locallib.php
lang/en/admin.php
lang/en/cache.php
lang/en/moodle.php
lib/accesslib.php
lib/adminlib.php
lib/db/caches.php
lib/editor/tinymce/lib.php
lib/form/yui/shortforms/shortforms.js
lib/formslib.php
lib/outputlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/tests/admintree_test.php [new file with mode: 0644]
lib/tests/behat/behat_forms.php
lib/upgrade.txt
lib/upgradelib.php
local/readme.txt
login/change_password_form.php
login/forgot_password_form.php
login/signup_form.php
mdeploy.php
mdeploytest.php
mod/assign/feedback/file/batchuploadfilesform.php
mod/assign/feedback/file/uploadzipform.php
mod/assign/feedback/offline/uploadgradesform.php
mod/assign/lib.php
mod/feedback/edit_form.php
mod/folder/db/install.xml
mod/folder/db/upgrade.php
mod/folder/lang/en/folder.php
mod/folder/mod_form.php
mod/folder/module.js
mod/folder/renderer.php
mod/folder/settings.php
mod/folder/version.php
mod/forum/lib.php
mod/forum/mod_form.php
mod/forum/post_form.php
mod/glossary/mod_form.php
mod/glossary/sql.php
mod/label/lang/en/label.php
mod/label/lib.php
mod/page/lang/en/page.php
mod/quiz/report/attemptsreport_options.php
mod/quiz/report/overview/report.php
mod/quiz/report/responses/report.php
mod/workshop/locallib.php
plagiarism/lib.php
question/format/blackboard_six/format.php
question/format/blackboard_six/tests/blackboardformatpool_test.php
question/format/blackboard_six/tests/blackboardsixformatqti_test.php
question/type/calculated/datasetitems_form.php
question/type/calculated/questiontype.php
question/type/edit_question_form.php
question/type/multianswer/renderer.php
question/type/shortanswer/question.php
question/type/shortanswer/tests/question_test.php
repository/equella/lib.php
repository/lib.php
repository/manage_instances.php
repository/repository_ajax.php
theme/base/style/core.css
theme/mymobile/renderers.php
theme/upgrade.txt
theme/yui_combo.php
user/selector/lib.php
user/selector/module.js
user/selector/search.php
version.php

index 45dccf6..7473f86 100644 (file)
@@ -1089,8 +1089,6 @@ class override_permissions_table_advanced extends capability_table_with_risks {
  * Base class to avoid duplicating code.
  */
 abstract class role_assign_user_selector_base extends user_selector_base {
-    const MAX_USERS_PER_PAGE = 100;
-
     protected $roleid;
     protected $context;
 
@@ -1159,7 +1157,7 @@ class potential_assignees_below_course extends role_assign_user_selector_base {
         // Check to see if there are too many to show sensibly.
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
@@ -1187,9 +1185,6 @@ class potential_assignees_below_course extends role_assign_user_selector_base {
  * @copyright 2012 Petr Skoda {@link http://skodak.org}
  */
 class role_check_users_selector extends user_selector_base {
-    const MAX_ENROLLED_PER_PAGE = 100;
-    const MAX_POTENTIAL_PER_PAGE = 100;
-
     /** @var bool limit listing of users to enrolled only */
     var $onlyenrolled;
 
@@ -1270,7 +1265,7 @@ class role_check_users_selector extends user_selector_base {
 
         if ($sql1) {
             $enrolleduserscount = $DB->count_records_sql($countfields . $sql1, $params);
-            if (!$this->is_validating() and $enrolleduserscount > $this::MAX_ENROLLED_PER_PAGE) {
+            if (!$this->is_validating() and $enrolleduserscount > $this->maxusersperpage) {
                 $result[$groupname1] = array();
                 $toomany = $this->too_many_results($search, $enrolleduserscount);
                 $result[implode(' - ', array_keys($toomany))] = array();
@@ -1287,7 +1282,7 @@ class role_check_users_selector extends user_selector_base {
         }
         if ($sql2) {
             $otheruserscount = $DB->count_records_sql($countfields . $sql2, $params);
-            if (!$this->is_validating() and $otheruserscount > $this::MAX_POTENTIAL_PER_PAGE) {
+            if (!$this->is_validating() and $otheruserscount > $this->maxusersperpage) {
                 $result[$groupname2] = array();
                 $toomany = $this->too_many_results($search, $otheruserscount);
                 $result[implode(' - ', array_keys($toomany))] = array();
@@ -1340,7 +1335,7 @@ class potential_assignees_course_and_above extends role_assign_user_selector_bas
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > role_assign_user_selector_base::MAX_USERS_PER_PAGE) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
@@ -1765,7 +1760,7 @@ class admins_potential_selector extends user_selector_base {
         // Check to see if there are too many to show sensibly.
         if (!$this->is_validating()) {
             $potentialcount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialcount > 100) {
+            if ($potentialcount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialcount);
             }
         }
index f2d6f58..61d2477 100644 (file)
@@ -196,9 +196,9 @@ if ($hassiteconfig) {
         50, PARAM_INT, 3));
 
     $ADMIN->add('portfoliosettings', $temp);
-    $ADMIN->add('portfoliosettings', new admin_externalpage('portfolionew', new lang_string('addnewportfolio', 'portfolio'), $url, 'moodle/site:config', true), '', $url);
-    $ADMIN->add('portfoliosettings', new admin_externalpage('portfoliodelete', new lang_string('deleteportfolio', 'portfolio'), $url, 'moodle/site:config', true), '', $url);
-    $ADMIN->add('portfoliosettings', new admin_externalpage('portfoliocontroller', new lang_string('manageportfolios', 'portfolio'), $url, 'moodle/site:config', true), '', $url);
+    $ADMIN->add('portfoliosettings', new admin_externalpage('portfolionew', new lang_string('addnewportfolio', 'portfolio'), $url, 'moodle/site:config', true));
+    $ADMIN->add('portfoliosettings', new admin_externalpage('portfoliodelete', new lang_string('deleteportfolio', 'portfolio'), $url, 'moodle/site:config', true));
+    $ADMIN->add('portfoliosettings', new admin_externalpage('portfoliocontroller', new lang_string('manageportfolios', 'portfolio'), $url, 'moodle/site:config', true));
 
     foreach (portfolio_instances(false, false) as $portfolio) {
         require_once($CFG->dirroot . '/portfolio/' . $portfolio->get('plugin') . '/lib.php');
@@ -234,20 +234,15 @@ if ($hassiteconfig) {
     $temp->add(new admin_setting_configcheckbox('legacyfilesinnewcourses', new lang_string('legacyfilesinnewcourses', 'admin'), new lang_string('legacyfilesinnewcourses_help', 'admin'), 0));
     $ADMIN->add('repositorysettings', $temp);
     $ADMIN->add('repositorysettings', new admin_externalpage('repositorynew',
-        new lang_string('addplugin', 'repository'), $url, 'moodle/site:config', true),
-        '', $url);
+        new lang_string('addplugin', 'repository'), $url, 'moodle/site:config', true));
     $ADMIN->add('repositorysettings', new admin_externalpage('repositorydelete',
-        new lang_string('deleterepository', 'repository'), $url, 'moodle/site:config', true),
-        '', $url);
+        new lang_string('deleterepository', 'repository'), $url, 'moodle/site:config', true));
     $ADMIN->add('repositorysettings', new admin_externalpage('repositorycontroller',
-        new lang_string('manage', 'repository'), $url, 'moodle/site:config', true),
-        '', $url);
+        new lang_string('manage', 'repository'), $url, 'moodle/site:config', true));
     $ADMIN->add('repositorysettings', new admin_externalpage('repositoryinstancenew',
-        new lang_string('createrepository', 'repository'), $url, 'moodle/site:config', true),
-        '', $url);
+        new lang_string('createrepository', 'repository'), $url, 'moodle/site:config', true));
     $ADMIN->add('repositorysettings', new admin_externalpage('repositoryinstanceedit',
-        new lang_string('editrepositoryinstance', 'repository'), $url, 'moodle/site:config', true),
-        '', $url);
+        new lang_string('editrepositoryinstance', 'repository'), $url, 'moodle/site:config', true));
     foreach ($allplugins['repository'] as $repositorytype) {
         $repositorytype->load_settings($ADMIN, 'repositorysettings', $hassiteconfig);
     }
index 0783599..0365ab5 100644 (file)
@@ -150,6 +150,7 @@ if ($hassiteconfig
                     'department'  => new lang_string('department'),
                     'institution' => new lang_string('institution'),
                 )));
+        $temp->add(new admin_setting_configtext('maxusersperpage', new lang_string('maxusersperpage','admin'), new lang_string('configmaxusersperpage','admin'), 100, PARAM_INT));
         $temp->add(new admin_setting_configcheckbox('enablegravatar', new lang_string('enablegravatar', 'admin'), new lang_string('enablegravatar_help', 'admin'), 0));
         $temp->add(new admin_setting_configtext('gravatardefaulturl', new lang_string('gravatardefaulturl', 'admin'), new lang_string('gravatardefaulturl_help', 'admin'), 'mm'));
     }
index 5d30c44..e803291 100644 (file)
@@ -26,5 +26,5 @@
 defined('MOODLE_INTERNAL') || die;
 
 if ($hassiteconfig) {
-    $ADMIN->add('roles', new admin_externalpage('toolunsuproles', get_string('pluginname', 'tool_unsuproles'), "$CFG->wwwroot/$CFG->admin/tool/unsuproles/index.php"), array('moodle/site:config', 'moodle/role:assign'));
+    $ADMIN->add('roles', new admin_externalpage('toolunsuproles', get_string('pluginname', 'tool_unsuproles'), "$CFG->wwwroot/$CFG->admin/tool/unsuproles/index.php", array('moodle/site:config', 'moodle/role:assign')));
 }
index 161c03a..b135d0f 100644 (file)
@@ -29,8 +29,6 @@ require_once($CFG->dirroot . '/user/selector/lib.php');
  * either all the other Moodle users.
  */
 class service_user_selector extends user_selector_base {
-    const MAX_USERS_PER_PAGE = 100;
-
     protected $serviceid;
     protected $displayallowedusers; //set to true if the selector displays the
                                     //allowed users on this service
@@ -85,7 +83,7 @@ class service_user_selector extends user_selector_base {
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > service_user_selector::MAX_USERS_PER_PAGE) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
index 3cb1d3b..23fa8a6 100644 (file)
@@ -62,7 +62,7 @@ class cohort_candidate_selector extends user_selector_base {
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > 100) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
index 9ca0a41..7b28158 100644 (file)
@@ -528,8 +528,9 @@ class comment {
             $c->timecreated = $u->ctimecreated;
             $c->strftimeformat = get_string('strftimerecent', 'langconfig');
             $url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
-            $c->profileurl = $url->out(false);
+            $c->profileurl = $url->out(true);
             $c->fullname = fullname($u);
+            $c->time = userdate($c->timecreated, $c->strftimeformat);
             $c->content = format_text($c->content, $c->format, $formatoptions);
             $c->avatar = $OUTPUT->user_picture($u, array('size'=>18));
 
@@ -808,7 +809,7 @@ class comment {
         $replacements[] = $cmt->avatar;
         $replacements[] = html_writer::link($cmt->profileurl, $cmt->fullname);
         $replacements[] = $cmt->content;
-        $replacements[] = userdate($cmt->timecreated, $cmt->strftimeformat);
+        $replacements[] = $cmt->time;
 
         // use html template to format a single comment.
         return str_replace($patterns, $replacements, $this->template);
index e5d73be..cb017d3 100644 (file)
@@ -8,6 +8,6 @@
     "require-dev": {
         "phpunit/phpunit": "3.7.*",
         "phpunit/dbUnit": "1.2.*",
-        "moodlehq/behat-extension": "1.0.3"
+        "moodlehq/behat-extension": "1.0.*"
     }
 }
index 1f68101..4344aa3 100644 (file)
@@ -51,7 +51,7 @@ class course_completion_form extends moodleform {
 
         // Check if there is existing criteria completions
         if ($completion->is_course_locked()) {
-            $mform->addElement('header', '', get_string('completionsettingslocked', 'completion'));
+            $mform->addElement('header', 'completionsettingslocked', get_string('completionsettingslocked', 'completion'));
             $mform->addElement('static', '', '', get_string('err_settingslocked', 'completion'));
             $mform->addElement('submit', 'settingsunlock', get_string('unlockcompletiondelete', 'completion'));
         }
index 35ded97..b1dbc63 100644 (file)
@@ -239,7 +239,7 @@ M.course_dndupload = {
      * 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 {
+     * @return object|false - 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
@@ -284,6 +284,7 @@ M.course_dndupload = {
                         realtype: dttypes[j],
                         addmessage: types[i].addmessage,
                         namemessage: types[i].namemessage,
+                        handlermessage: types[i].handlermessage,
                         type: types[i].identifier,
                         handlers: types[i].handlers
                     };
@@ -417,7 +418,7 @@ M.course_dndupload = {
         var modsel = section.one(this.modslistselector);
         if (!modsel) {
             // Create the above 'ul' if it doesn't exist
-            var modsel = document.createElement('ul');
+            modsel = document.createElement('ul');
             modsel.className = 'section img-text';
             var contentel = section.get('children').pop();
             var brel = contentel.get('children').pop();
@@ -489,6 +490,7 @@ M.course_dndupload = {
      */
     hide_preview_element: function() {
         this.Y.all('li.dndupload-preview').addClass('dndupload-hidden');
+        this.Y.all('.dndupload-over').removeClass('dndupload-over');
     },
 
     /**
@@ -500,7 +502,11 @@ M.course_dndupload = {
     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);
+        section.addClass('dndupload-over');
+
+        // Horrible work-around to allow the 'Add X here' text to be a drop target in Firefox.
+        var node = preview.one('span').getDOMNode();
+        node.firstChild.nodeValue = type.addmessage;
     },
 
     /**
@@ -616,44 +622,16 @@ M.course_dndupload = {
 
         var Y = this.Y;
         var self = this;
-        var panel = new Y.Panel({
+        var panel = new M.core.dialogue({
             bodyContent: content,
-            width: 350,
-            zIndex: 5,
-            centered: true,
+            width: '350px',
             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
-            }]
+            align: {
+                node: null,
+                points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC]
+            }
         });
         // When the panel is hidden - destroy it and then check for other pending uploads
         panel.after("visibleChange", function(e) {
@@ -662,6 +640,39 @@ M.course_dndupload = {
                 self.check_upload_queue();
             }
         });
+
+        // Add the submit/cancel buttons to the bottom of the dialog.
+        panel.addButton({
+            label: 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
+        });
+        panel.addButton({
+            label: M.util.get_string('cancel', 'moodle'),
+            action: function(e) {
+                e.preventDefault();
+                panel.hide();
+            },
+            section: Y.WidgetStdMod.FOOTER
+        });
     },
 
     /**
@@ -822,6 +833,7 @@ M.course_dndupload = {
         var nameid = 'dndupload_handler_name'+uploadid;
         var content = '';
         if (type.handlers.length > 1) {
+            content += '<p>'+type.handlermessage+'</p>';
             content += '<div id="dndupload_handlers'+uploadid+'">';
             var sel = type.handlers[0].module;
             for (var i=0; i<type.handlers.length; i++) {
@@ -840,56 +852,18 @@ M.course_dndupload = {
 
         var Y = this.Y;
         var self = this;
-        var panel = new Y.Panel({
+        var panel = new M.core.dialogue({
             bodyContent: content,
-            width: 350,
-            zIndex: 5,
-            centered: true,
+            width: '350px',
             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
-                    var module = false;
-                    var noname = 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')) {
-                                var idx = input.get('value');
-                                module = type.handlers[idx].module;
-                                noname = type.handlers[idx].noname;
-                            }
-                        });
-                        if (!module) {
-                            return;
-                        }
-                    } else {
-                        module = type.handlers[0].module;
-                        noname = type.handlers[0].noname;
-                    }
-                    if (name == '' && !noname) {
-                        return;
-                    }
-                    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
-            }]
+            align: {
+                node: null,
+                points: [Y.WidgetPositionAlign.CC, Y.WidgetPositionAlign.CC]
+            }
         });
+
         // When the panel is hidden - destroy it and then check for other pending uploads
         panel.after("visibleChange", function(e) {
             if (!panel.get('visible')) {
@@ -897,19 +871,86 @@ M.course_dndupload = {
                 self.check_upload_queue();
             }
         });
-        // Focus on the 'name' box
-        Y.one('#'+nameid).focus();
+
+        var namefield = Y.one('#'+nameid);
+        var submit = function(e) {
+            e.preventDefault();
+            var name = Y.Lang.trim(namefield.get('value'));
+            var module = false;
+            var noname = 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')) {
+                        var idx = input.get('value');
+                        module = type.handlers[idx].module;
+                        noname = type.handlers[idx].noname;
+                    }
+                });
+                if (!module) {
+                    return;
+                }
+            } else {
+                module = type.handlers[0].module;
+                noname = type.handlers[0].noname;
+            }
+            if (name == '' && !noname) {
+                return;
+            }
+            if (noname) {
+                name = '';
+            }
+            panel.hide();
+            // Do the upload
+            self.upload_item(name, type.type, contents, section, sectionnumber, module);
+        };
+
+        // Add the submit/cancel buttons to the bottom of the dialog.
+        panel.addButton({
+            label: M.util.get_string('upload', 'moodle'),
+            action: submit,
+            section: Y.WidgetStdMod.FOOTER,
+            name: 'submit'
+        });
+        panel.addButton({
+            label: M.util.get_string('cancel', 'moodle'),
+            action: function(e) {
+                e.preventDefault();
+                panel.hide();
+            },
+            section: Y.WidgetStdMod.FOOTER
+        });
+        var submitbutton = panel.getButton('submit').button;
+        namefield.on('key', submit, 'enter'); // Submit the form if 'enter' pressed
+        namefield.after('keyup', function() {
+            if (Y.Lang.trim(namefield.get('value')) == '') {
+                submitbutton.disable();
+            } else {
+                submitbutton.enable();
+            }
+        });
+
+        // Enable / disable the 'name' box, depending on the handler selected.
         for (i=0; i<type.handlers.length; i++) {
             if (type.handlers[i].noname) {
                 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) {
-                    Y.one('#'+nameid).set('disabled', 'disabled');
+                    namefield.set('disabled', 'disabled');
+                    submitbutton.enable();
                 });
             } else {
                 Y.one('#dndupload_handler'+uploadid+type.handlers[i].module).on('click', function (e) {
-                    Y.one('#'+nameid).removeAttribute('disabled');
+                    namefield.removeAttribute('disabled');
+                    namefield.focus();
+                    if (Y.Lang.trim(namefield.get('value')) == '') {
+                        submitbutton.disable();
+                    }
                 });
             }
         }
+
+        // Focus on the 'name' box
+        Y.one('#'+nameid).focus();
     },
 
     /**
index b540001..b7e8994 100644 (file)
@@ -67,7 +67,7 @@ function dndupload_add_to_course($course, $modnames) {
             array('upload', 'moodle'),
             array('cancel', 'moodle')
         ),
-        'requires' => array('node', 'event', 'panel', 'json', 'anim')
+        'requires' => array('node', 'event', 'json', 'anim')
     );
     $vars = array(
         array('courseid' => $course->id,
@@ -112,12 +112,12 @@ class dndupload_handler {
         // 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);
+        $this->register_type('url', array('url', 'text/uri-list', 'text/x-moz-url'), get_string('addlinkhere', 'moodle'),
+                        get_string('nameforlink', 'moodle'), get_string('whatforlink', 'moodle'), 10);
+        $this->register_type('text/html', array('text/html'), get_string('addpagehere', 'moodle'),
+                        get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 20);
+        $this->register_type('text', array('text', 'text/plain'), get_string('addpagehere', 'moodle'),
+                        get_string('nameforpage', 'moodle'), get_string('whatforpage', 'moodle'), 30);
 
         // Loop through all modules to find handlers.
         $mods = get_plugin_list_with_function('mod', 'dndupload_register');
@@ -135,7 +135,7 @@ class dndupload_handler {
             }
             if (isset($resp['files'])) {
                 foreach ($resp['files'] as $file) {
-                    $this->add_file_handler($file['extension'], $modname, $file['message']);
+                    $this->register_file_handler($file['extension'], $modname, $file['message']);
                 }
             }
             if (isset($resp['addtypes'])) {
@@ -145,19 +145,38 @@ class dndupload_handler {
                     } else {
                         $priority = 100;
                     }
-                    $this->add_type($type['identifier'], $type['datatransfertypes'],
-                                    $type['addmessage'], $type['namemessage'], $priority);
+                    if (!isset($type['handlermessage'])) {
+                        $type['handlermessage'] = '';
+                    }
+                    $this->register_type($type['identifier'], $type['datatransfertypes'],
+                                    $type['addmessage'], $type['namemessage'], $type['handlermessage'], $priority);
                 }
             }
             if (isset($resp['types'])) {
                 foreach ($resp['types'] as $type) {
                     $noname = !empty($type['noname']);
-                    $this->add_type_handler($type['identifier'], $modname, $type['message'], $noname);
+                    $this->register_type_handler($type['identifier'], $modname, $type['message'], $noname);
                 }
             }
         }
     }
 
+    /**
+     * No external code should be directly adding new types - they should be added via a 'addtypes' array, returned
+     * by MODNAME_dndupload_register.
+     *
+     * @deprecated deprecated since Moodle 2.5
+     * @param string $identifier
+     * @param array $datatransfertypes
+     * @param string $addmessage
+     * @param string $namemessage
+     * @param int $priority
+     */
+    public function add_type($identifier, $datatransfertypes, $addmessage, $namemessage, $priority=100) {
+        debugging('add_type() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
+        $this->register_type($identifier, $datatransfertypes, $addmessage, $namemessage, '', $priority);
+    }
+
     /**
      * Used to add a new mime type that can be drag and dropped onto a
      * course displayed in a browser window
@@ -169,10 +188,11 @@ class dndupload_handler {
      *                           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 string $handlermessage The message to pop up when asking which module should handle this type
      * @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) {
+    protected function register_type($identifier, $datatransfertypes, $addmessage, $namemessage, $handlermessage, $priority=100) {
         if ($this->is_known_type($identifier)) {
             throw new coding_exception("Type $identifier is already registered");
         }
@@ -182,6 +202,7 @@ class dndupload_handler {
         $add->datatransfertypes = $datatransfertypes;
         $add->addmessage = $addmessage;
         $add->namemessage = $namemessage;
+        $add->handlermessage = $handlermessage;
         $add->priority = $priority;
         $add->handlers = array();
 
@@ -189,9 +210,10 @@ class dndupload_handler {
     }
 
     /**
-     * Used to declare that a particular module will handle a particular type
-     * of dropped data
+     * No external code should be directly adding new type handlers - they should be added via a 'addtypes' array, returned
+     * by MODNAME_dndupload_register.
      *
+     * @deprecated deprecated since Moodle 2.5
      * @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
@@ -200,6 +222,22 @@ class dndupload_handler {
      * @throws coding_exception
      */
     public function add_type_handler($type, $module, $message, $noname) {
+        debugging('add_type_handler() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
+        $this->register_type_handler($type, $module, $message, $noname);
+    }
+
+    /**
+     * 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 register_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
+     * @param bool $noname If true, the 'name' dialog should be disabled in the pop-up.
+     * @throws coding_exception
+     */
+    protected function register_type_handler($type, $module, $message, $noname) {
         if (!$this->is_known_type($type)) {
             throw new coding_exception("Trying to add handler for unknown type $type");
         }
@@ -213,6 +251,21 @@ class dndupload_handler {
         $this->types[$type]->handlers[] = $add;
     }
 
+    /**
+     * No external code should be directly adding new file handlers - they should be added via a 'files' array, returned
+     * by MODNAME_dndupload_register.
+     *
+     * @deprecated deprecated since Moodle 2.5
+     * @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) {
+        debugging('add_file_handler() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
+        $this->register_file_handler($extension, $module, $message);
+    }
+
     /**
      * Used to declare that a particular module will handle a particular type
      * of dropped file
@@ -222,7 +275,7 @@ class dndupload_handler {
      * @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) {
+    protected function register_file_handler($extension, $module, $message) {
         $extension = strtolower($extension);
 
         $add = new stdClass;
index 8f7e875..2f9b3f4 100644 (file)
@@ -196,7 +196,7 @@ class course_edit_form extends moodleform {
         enrol_course_edit_form($mform, $course, $context);
 
 //--------------------------------------------------------------------------------
-        $mform->addElement('header','', get_string('groups', 'group'));
+        $mform->addElement('header','groups', get_string('groups', 'group'));
 
         $choices = array();
         $choices[NOGROUPS] = get_string('groupsnone', 'group');
@@ -219,7 +219,7 @@ class course_edit_form extends moodleform {
         $mform->addElement('select', 'defaultgroupingid', get_string('defaultgrouping', 'group'), $options);
 
 //--------------------------------------------------------------------------------
-        $mform->addElement('header','', get_string('availability'));
+        $mform->addElement('header','availability', get_string('availability'));
 
         $choices = array();
         $choices['0'] = get_string('courseavailablenot');
@@ -237,7 +237,7 @@ class course_edit_form extends moodleform {
         }
 
 //--------------------------------------------------------------------------------
-        $mform->addElement('header','', get_string('language'));
+        $mform->addElement('header','language', get_string('language'));
 
         $languages=array();
         $languages[''] = get_string('forceno');
@@ -247,7 +247,7 @@ class course_edit_form extends moodleform {
 
 //--------------------------------------------------------------------------------
         if (completion_info::is_enabled_for_site()) {
-            $mform->addElement('header','', get_string('progress','completion'));
+            $mform->addElement('header','progress', 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);
@@ -314,7 +314,7 @@ class course_edit_form extends moodleform {
         $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',
+            $newel = $mform->createElement('header', 'courseformatoptions', get_string('courseformatoptions', 'moodle',
                     $courseformat->get_format_name()));
             $mform->insertElementBefore($newel, 'addcourseformatoptionshere');
 
index 202138c..5e7ec95 100644 (file)
@@ -56,7 +56,7 @@ class editsection_form extends moodleform {
         $course = $this->_customdata['course'];
 
         if (!empty($CFG->enableavailability)) {
-            $mform->addElement('header', '', get_string('availabilityconditions', 'condition'));
+            $mform->addElement('header', 'availabilityconditions', 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
index ba5d976..347d17c 100644 (file)
@@ -3892,3 +3892,83 @@ function get_sorted_course_formats($enabledonly = false) {
 function course_get_url($courseorid, $section = null, $options = array()) {
     return course_get_format($courseorid)->get_view_url($section, $options);
 }
+
+/**
+ * Create a module.
+ *
+ * It includes:
+ *      - capability checks and other checks
+ *      - create the module from the module info
+ *
+ * @param object $module
+ * @return object the created module info
+ */
+function create_module($moduleinfo) {
+    global $DB, $CFG;
+
+    require_once($CFG->dirroot . '/course/modlib.php');
+
+    // Check manadatory attributs.
+    $mandatoryfields = array('modulename', 'course', 'section', 'visible');
+    if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
+        $mandatoryfields[] = 'introeditor';
+    }
+    foreach($mandatoryfields as $mandatoryfield) {
+        if (!isset($moduleinfo->{$mandatoryfield})) {
+            throw new moodle_exception('createmodulemissingattribut', '', '', $mandatoryfield);
+        }
+    }
+
+    // Some additional checks (capability / existing instances).
+    $course = $DB->get_record('course', array('id'=>$moduleinfo->course), '*', MUST_EXIST);
+    list($module, $context, $cw) = can_add_moduleinfo($course, $moduleinfo->modulename, $moduleinfo->section);
+
+    // Load module library.
+    include_modulelib($module->name);
+
+    // Add the module.
+    $moduleinfo->module = $module->id;
+    $moduleinfo = add_moduleinfo($moduleinfo, $course, null);
+
+    return $moduleinfo;
+}
+
+/**
+ * Update a module.
+ *
+ * It includes:
+ *      - capability and other checks
+ *      - update the module
+ *
+ * @param object $module
+ * @return object the updated module info
+ */
+function update_module($moduleinfo) {
+    global $DB, $CFG;
+
+    require_once($CFG->dirroot . '/course/modlib.php');
+
+    // Check the course module exists.
+    $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
+
+    // Check the course exists.
+    $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
+
+    // Some checks (capaibility / existing instances).
+    list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
+
+    // Load module library.
+    include_modulelib($module->name);
+
+    // Retrieve few information needed by update_moduleinfo.
+    $moduleinfo->modulename = $cm->modname;
+    if (!isset($moduleinfo->scale)) {
+        $moduleinfo->scale = 0;
+    }
+    $moduleinfo->type = 'mod';
+
+    // Update the module.
+    list($cm, $moduleinfo) = update_moduleinfo($cm, $moduleinfo, $course, null);
+
+    return $moduleinfo;
+}
index b4e5ea7..0c1ec26 100644 (file)
@@ -4,13 +4,22 @@
 require_once('../config.php');
 require_once('lib.php');
 
-$id = optional_param('id', SITEID, PARAM_INT);   // course id
+$id       = optional_param('id', SITEID, PARAM_INT);   // course id
+$redirect = optional_param('redirect', 0, PARAM_BOOL);
+
+$url = new moodle_url('/course/loginas.php', array('id'=>$id));
+$PAGE->set_url($url);
 
 /// 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();
 
+    // We can not set wanted URL here because the session is closed.
+    redirect(new moodle_url($url, array('redirect'=>1)));
+}
+
+if ($redirect) {
     if ($id and $id != SITEID) {
         $SESSION->wantsurl = "$CFG->wwwroot/course/view.php?id=".$id;
     } else {
@@ -25,12 +34,6 @@ if (session_is_loggedinas()) {
 
 $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);
 
index c08108a..8cfe0a0 100644 (file)
@@ -30,6 +30,7 @@ require_once($CFG->libdir.'/gradelib.php');
 require_once($CFG->libdir.'/completionlib.php');
 require_once($CFG->libdir.'/conditionlib.php');
 require_once($CFG->libdir.'/plagiarismlib.php');
+require_once($CFG->dirroot . '/course/modlib.php');
 
 $add    = optional_param('add', '', PARAM_ALPHA);     // module name
 $update = optional_param('update', 0, PARAM_INT);
@@ -53,18 +54,9 @@ if (!empty($add)) {
     $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');
-    }
+    list($module, $context, $cw) = can_add_moduleinfo($course, $add, $section);
 
     $cm = null;
 
@@ -128,16 +120,16 @@ if (!empty($add)) {
     $url->param('update', $update);
     $PAGE->set_url($url);
 
+    // Check the course module exists.
     $cm = get_coursemodule_from_id('', $update, 0, false, MUST_EXIST);
+
+    // Check the course exists.
     $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 
+    // require_login
     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);
+    list($cm, $context, $module, $data, $cw) = can_update_moduleinfo($cm);
 
     $data->coursemodule       = $cm->id;
     $data->section            = $cw->section;  // The section number itself - relative!!! (section column in course_sections)
@@ -250,12 +242,7 @@ if (file_exists($modmoodleform)) {
     print_error('noformdesc');
 }
 
-$modlib = "$CFG->dirroot/mod/$module->name/lib.php";
-if (file_exists($modlib)) {
-    include_once($modlib);
-} else {
-    print_error('modulemissingcode', '', '', $modlib);
-}
+include_modulelib($module->name);
 
 $mformclassname = 'mod_'.$module->name.'_mod_form';
 $mform = new $mformclassname($data, $cw->section, $cm, $course);
@@ -268,369 +255,21 @@ if ($mform->is_cancelled()) {
         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);
-
+        list($cm, $fromform) = update_moduleinfo($cm, $fromform, $course, $mform);
     } 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);
+        $fromform = add_moduleinfo($fromform, $course, $mform);
     } 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)) {
+        if (empty($fromform->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));
+            redirect($fromform->gradingman->get_management_url($returnurl));
         }
     } else {
         redirect(course_get_url($course, $cw->section, array('sr' => $sectionreturn)));
diff --git a/course/modlib.php b/course/modlib.php
new file mode 100644 (file)
index 0000000..166c638
--- /dev/null
@@ -0,0 +1,519 @@
+<?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 functions specific to course/modedit.php and course API functions.
+ * The course API function calling them are course/lib.php:create_module() and update_module().
+ * This file has been created has an alternative solution to a full refactor of course/modedit.php
+ * in order to create the course API functions.
+ *
+ * @copyright 2013 Jerome Mouneyrac
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package core_course
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+require_once($CFG->dirroot.'/course/lib.php');
+
+/**
+ * Add course module.
+ *
+ * The function does not check user capabilities.
+ * The function creates course module, module instance, add the module to the correct section.
+ * It also trigger common action that need to be done after adding/updating a module.
+ *
+ * @param object $moduleinfo the moudle data
+ * @param object $course the course of the module
+ * @param object $mform this is required by an existing hack to deal with files during MODULENAME_add_instance()
+ * @return object the updated module info
+ */
+function add_moduleinfo($moduleinfo, $course, $mform = null) {
+    global $DB, $CFG;
+
+    $moduleinfo->course = $course->id;
+    $moduleinfo = set_moduleinfo_defaults($moduleinfo);
+
+    if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
+        $moduleinfo->groupmode = 0; // Do not set groupmode.
+    }
+
+    if (!course_allowed_module($course, $moduleinfo->modulename)) {
+        print_error('moduledisable', '', '', $moduleinfo->modulename);
+    }
+
+    // First add course_module record because we need the context.
+    $newcm = new stdClass();
+    $newcm->course           = $course->id;
+    $newcm->module           = $moduleinfo->module;
+    $newcm->instance         = 0; // Not known yet, will be updated later (this is similar to restore code).
+    $newcm->visible          = $moduleinfo->visible;
+    $newcm->groupmode        = $moduleinfo->groupmode;
+    $newcm->groupingid       = $moduleinfo->groupingid;
+    $newcm->groupmembersonly = $moduleinfo->groupmembersonly;
+    $completion = new completion_info($course);
+    if ($completion->is_enabled()) {
+        $newcm->completion                = $moduleinfo->completion;
+        $newcm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
+        $newcm->completionview            = $moduleinfo->completionview;
+        $newcm->completionexpected        = $moduleinfo->completionexpected;
+    }
+    if(!empty($CFG->enableavailability)) {
+        $newcm->availablefrom             = $moduleinfo->availablefrom;
+        $newcm->availableuntil            = $moduleinfo->availableuntil;
+        $newcm->showavailability          = $moduleinfo->showavailability;
+    }
+    if (isset($moduleinfo->showdescription)) {
+        $newcm->showdescription = $moduleinfo->showdescription;
+    } else {
+        $newcm->showdescription = 0;
+    }
+
+    if (!$moduleinfo->coursemodule = add_course_module($newcm)) {
+        print_error('cannotaddcoursemodule');
+    }
+
+    if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
+        $introeditor = $moduleinfo->introeditor;
+        unset($moduleinfo->introeditor);
+        $moduleinfo->intro       = $introeditor['text'];
+        $moduleinfo->introformat = $introeditor['format'];
+    }
+
+    $addinstancefunction    = $moduleinfo->modulename."_add_instance";
+    $returnfromfunc = $addinstancefunction($moduleinfo, $mform);
+    if (!$returnfromfunc or !is_number($returnfromfunc)) {
+        // Undo everything we can.
+        $modcontext = context_module::instance($moduleinfo->coursemodule);
+        delete_context(CONTEXT_MODULE, $moduleinfo->coursemodule);
+        $DB->delete_records('course_modules', array('id'=>$moduleinfo->coursemodule));
+
+        if (!is_number($returnfromfunc)) {
+            print_error('invalidfunction', '', course_get_url($course, $cw->section));
+        } else {
+            print_error('cannotaddnewmodule', '', course_get_url($course, $cw->section), $moduleinfo->modulename);
+        }
+    }
+
+    $moduleinfo->instance = $returnfromfunc;
+
+    $DB->set_field('course_modules', 'instance', $returnfromfunc, array('id'=>$moduleinfo->coursemodule));
+
+    // Update embedded links and save files.
+    $modcontext = context_module::instance($moduleinfo->coursemodule);
+    if (!empty($introeditor)) {
+        $moduleinfo->intro = file_save_draft_area_files($introeditor['itemid'], $modcontext->id,
+                                                      'mod_'.$moduleinfo->modulename, 'intro', 0,
+                                                      array('subdirs'=>true), $introeditor['text']);
+        $DB->set_field($moduleinfo->modulename, 'intro', $moduleinfo->intro, array('id'=>$moduleinfo->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, $moduleinfo->coursemodule, $moduleinfo->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($moduleinfo->coursemodule, $moduleinfo->visible);
+    $DB->set_field('course_modules', 'visibleold', 1, array('id' => $moduleinfo->coursemodule));
+
+    if (isset($moduleinfo->cmidnumber)) { // Label.
+        // Set cm idnumber - uniqueness is already verified by form validation.
+        set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->cmidnumber);
+    }
+
+    // Set up conditions.
+    if ($CFG->enableavailability) {
+        condition_info::update_cm_from_form((object)array('id'=>$moduleinfo->coursemodule), $moduleinfo, false);
+    }
+
+    $eventname = 'mod_created';
+
+    add_to_log($course->id, "course", "add mod",
+               "../mod/$moduleinfo->modulename/view.php?id=$moduleinfo->coursemodule",
+               "$moduleinfo->modulename $moduleinfo->instance");
+    add_to_log($course->id, $moduleinfo->modulename, "add",
+               "view.php?id=$moduleinfo->coursemodule",
+               "$moduleinfo->instance", $moduleinfo->coursemodule);
+
+    $moduleinfo = edit_module_post_actions($moduleinfo, $course, 'mod_created');
+
+    return $moduleinfo;
+}
+
+
+/**
+ * Common create/update module module actions that need to be processed as soon as a module is created/updaded.
+ * For example: trigger event, create grade parent category, add outcomes, rebuild caches, regrade, save plagiarism settings...
+ *
+ * @param object $moduleinfo the module info
+ * @param object $course the course of the module
+ * @param string $eventname the event name to trigger
+ *
+ * return object moduleinfo update with grading management info
+ */
+function edit_module_post_actions($moduleinfo, $course, $eventname) {
+    global $USER, $CFG;
+
+    $modcontext = context_module::instance($moduleinfo->coursemodule);
+
+    // Trigger mod_created/mod_updated event with information about this module.
+    $eventdata = new stdClass();
+    $eventdata->modulename = $moduleinfo->modulename;
+    $eventdata->name       = $moduleinfo->name;
+    $eventdata->cmid       = $moduleinfo->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'=>$moduleinfo->modulename,
+                 'iteminstance'=>$moduleinfo->instance, 'itemnumber'=>0, 'courseid'=>$course->id))) {
+        if ($grade_item->idnumber != $moduleinfo->cmidnumber) {
+            $grade_item->idnumber = $moduleinfo->cmidnumber;
+            $grade_item->update();
+        }
+    }
+
+    $items = grade_item::fetch_all(array('itemtype'=>'mod', 'itemmodule'=>$moduleinfo->modulename,
+                                         'iteminstance'=>$moduleinfo->instance, 'courseid'=>$course->id));
+
+    // Create parent category if requested and move to correct parent category.
+    if ($items and isset($moduleinfo->gradecat)) {
+        if ($moduleinfo->gradecat == -1) {
+            $grade_category = new grade_category();
+            $grade_category->courseid = $course->id;
+            $grade_category->fullname = $moduleinfo->name;
+            $grade_category->insert();
+            if ($grade_item) {
+                $parent = $grade_item->get_parent_category();
+                $grade_category->set_parent($parent->id);
+            }
+            $moduleinfo->gradecat = $grade_category->id;
+        }
+        foreach ($items as $itemid=>$unused) {
+            $items[$itemid]->set_parent($moduleinfo->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($moduleinfo, $elname) and $moduleinfo->$elname) {
+                // So we have a request for new outcome grade item?
+                if ($items) {
+                    $outcomeexists = false;
+                    foreach($items as $item) {
+                        if ($item->outcomeid == $outcome->id) {
+                            $outcomeexists = true;
+                            break;
+                        }
+                    }
+                    if ($outcomeexists) {
+                        continue;
+                    }
+                }
+
+                $max_itemnumber++;
+
+                $outcome_item = new grade_item();
+                $outcome_item->courseid     = $course->id;
+                $outcome_item->itemtype     = 'mod';
+                $outcome_item->itemmodule   = $moduleinfo->modulename;
+                $outcome_item->iteminstance = $moduleinfo->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($moduleinfo->gradecat)) {
+                    $outcome_item->set_parent($moduleinfo->gradecat);
+                }
+            }
+        }
+    }
+
+    if (plugin_supports('mod', $moduleinfo->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_'.$moduleinfo->modulename);
+        $showgradingmanagement = false;
+        foreach ($gradingman->get_available_areas() as $areaname => $aretitle) {
+            $formfield = 'advancedgradingmethod_'.$areaname;
+            if (isset($moduleinfo->{$formfield})) {
+                $gradingman->set_area($areaname);
+                $methodchanged = $gradingman->set_active_method($moduleinfo->{$formfield});
+                if (empty($moduleinfo->{$formfield})) {
+                    // Going back to the simple direct grading is not a reason to open the management screen.
+                    $methodchanged = false;
+                }
+                $showgradingmanagement = $showgradingmanagement || $methodchanged;
+            }
+        }
+        // Update grading management information.
+        $moduleinfo->gradingman = $gradingman;
+        $moduleinfo->showgradingmanagement = $showgradingmanagement;
+    }
+
+    rebuild_course_cache($course->id);
+    grade_regrade_final_grades($course->id);
+    require_once($CFG->libdir.'/plagiarismlib.php');
+    plagiarism_save_form_elements($moduleinfo);
+
+    return $moduleinfo;
+}
+
+
+/**
+ * Set module info default values for the unset module attributs.
+ *
+ * @param object $moduleinfo the current known data of the module
+ * @return object the completed module info
+ */
+function set_moduleinfo_defaults($moduleinfo) {
+    global $DB;
+
+    if (empty($moduleinfo->coursemodule)) {
+        // Add.
+        $cm = null;
+        $moduleinfo->instance     = '';
+        $moduleinfo->coursemodule = '';
+    } else {
+        // Update.
+        $cm = get_coursemodule_from_id('', $moduleinfo->coursemodule, 0, false, MUST_EXIST);
+        $moduleinfo->instance     = $cm->instance;
+        $moduleinfo->coursemodule = $cm->id;
+    }
+    // For safety.
+    $moduleinfo->modulename = clean_param($moduleinfo->modulename, PARAM_PLUGIN);
+
+    if (!isset($moduleinfo->groupingid)) {
+        $moduleinfo->groupingid = 0;
+    }
+
+    if (!isset($moduleinfo->groupmembersonly)) {
+        $moduleinfo->groupmembersonly = 0;
+    }
+
+    if (!isset($moduleinfo->name)) { // Label.
+        $moduleinfo->name = $moduleinfo->modulename;
+    }
+
+    if (!isset($moduleinfo->completion)) {
+        $moduleinfo->completion = COMPLETION_DISABLED;
+    }
+    if (!isset($moduleinfo->completionview)) {
+        $moduleinfo->completionview = COMPLETION_VIEW_NOT_REQUIRED;
+    }
+
+    // Convert the 'use grade' checkbox into a grade-item number: 0 if checked, null if not.
+    if (isset($moduleinfo->completionusegrade) && $moduleinfo->completionusegrade) {
+        $moduleinfo->completiongradeitemnumber = 0;
+    } else {
+        $moduleinfo->completiongradeitemnumber = null;
+    }
+
+    return $moduleinfo;
+}
+
+/**
+ * Check that the user can add a module. Also returns some information like the module, context and course section info.
+ * The fucntion create the course section if it doesn't exist.
+ *
+ * @param object $course the course of the module
+ * @param object $modulename the module name
+ * @param object $section the section of the module
+ * @return array list containing module, context, course section.
+ */
+function can_add_moduleinfo($course, $modulename, $section) {
+    global $DB;
+
+    $module = $DB->get_record('modules', array('name'=>$modulename), '*', MUST_EXIST);
+
+    $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');
+    }
+
+    return array($module, $context, $cw);
+}
+
+/**
+ * Check if user is allowed to update module info and returns related item/data to the module.
+ *
+ * @param object $cm course module
+ * @return array - list of course module, context, module, moduleinfo, and course section.
+ */
+function can_update_moduleinfo($cm) {
+    global $DB;
+
+    // Check the $USER has the right capability.
+    $context = context_module::instance($cm->id);
+    require_capability('moodle/course:manageactivities', $context);
+
+    // Check module exists.
+    $module = $DB->get_record('modules', array('id'=>$cm->module), '*', MUST_EXIST);
+
+    // Check the moduleinfo exists.
+    $data = $DB->get_record($module->name, array('id'=>$cm->instance), '*', MUST_EXIST);
+
+    // Check the course section exists.
+    $cw = $DB->get_record('course_sections', array('id'=>$cm->section), '*', MUST_EXIST);
+
+    return array($cm, $context, $module, $data, $cw);
+}
+
+
+/**
+ * Update the module info.
+ * This function doesn't check the user capabilities. It updates the course module and the module instance.
+ * Then execute common action to create/update module process (trigger event, rebuild cache, save plagiarism settings...).
+ *
+ * @param object $cm course module
+ * @param object $moduleinfo module info
+ * @param object $course course of the module
+ * @param object $mform - the mform is required by some specific module in the function MODULE_update_instance(). This is due to a hack in this function.
+ * @return array list of course module and module info.
+ */
+function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
+    global $DB, $CFG;
+
+    $moduleinfo->course = $course->id;
+    $moduleinfo = set_moduleinfo_defaults($moduleinfo);
+
+    if (!empty($course->groupmodeforce) or !isset($moduleinfo->groupmode)) {
+        $moduleinfo->groupmode = $cm->groupmode; // Keep original.
+    }
+
+    // Update course module first.
+    $cm->groupmode = $moduleinfo->groupmode;
+    if (isset($moduleinfo->groupingid)) {
+        $cm->groupingid = $moduleinfo->groupingid;
+    }
+    if (isset($moduleinfo->groupmembersonly)) {
+        $cm->groupmembersonly = $moduleinfo->groupmembersonly;
+    }
+
+    $completion = new completion_info($course);
+    if ($completion->is_enabled()) {
+        // Update completion settings.
+        $cm->completion                = $moduleinfo->completion;
+        $cm->completiongradeitemnumber = $moduleinfo->completiongradeitemnumber;
+        $cm->completionview            = $moduleinfo->completionview;
+        $cm->completionexpected        = $moduleinfo->completionexpected;
+    }
+    if (!empty($CFG->enableavailability)) {
+        $cm->availablefrom             = $moduleinfo->availablefrom;
+        $cm->availableuntil            = $moduleinfo->availableuntil;
+        $cm->showavailability          = $moduleinfo->showavailability;
+        condition_info::update_cm_from_form($cm,$moduleinfo,true);
+    }
+    if (isset($moduleinfo->showdescription)) {
+        $cm->showdescription = $moduleinfo->showdescription;
+    } else {
+        $cm->showdescription = 0;
+    }
+
+    $DB->update_record('course_modules', $cm);
+
+    $modcontext = context_module::instance($moduleinfo->coursemodule);
+
+    // Update embedded links and save files.
+    if (plugin_supports('mod', $moduleinfo->modulename, FEATURE_MOD_INTRO, true)) {
+        $moduleinfo->intro = file_save_draft_area_files($moduleinfo->introeditor['itemid'], $modcontext->id,
+                                                      'mod_'.$moduleinfo->modulename, 'intro', 0,
+                                                      array('subdirs'=>true), $moduleinfo->introeditor['text']);
+        $moduleinfo->introformat = $moduleinfo->introeditor['format'];
+        unset($moduleinfo->introeditor);
+    }
+    $updateinstancefunction = $moduleinfo->modulename."_update_instance";
+    if (!$updateinstancefunction($moduleinfo, $mform)) {
+        print_error('cannotupdatemod', '', course_get_url($course, $cw->section), $moduleinfo->modulename);
+    }
+
+    // Make sure visibility is set correctly (in particular in calendar).
+    if (has_capability('moodle/course:activityvisibility', $modcontext)) {
+        set_coursemodule_visible($moduleinfo->coursemodule, $moduleinfo->visible);
+    }
+
+    if (isset($moduleinfo->cmidnumber)) { // Label.
+        // Set cm idnumber - uniqueness is already verified by form validation.
+        set_coursemodule_idnumber($moduleinfo->coursemodule, $moduleinfo->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($moduleinfo->completionunlocked)) {
+        $completion->reset_all_state($cm);
+    }
+
+    add_to_log($course->id, "course", "update mod",
+               "../mod/$moduleinfo->modulename/view.php?id=$moduleinfo->coursemodule",
+               "$moduleinfo->modulename $moduleinfo->instance");
+    add_to_log($course->id, $moduleinfo->modulename, "update",
+               "view.php?id=$moduleinfo->coursemodule",
+               "$moduleinfo->instance", $moduleinfo->coursemodule);
+
+    $moduleinfo = edit_module_post_actions($moduleinfo, $course, 'mod_updated');
+
+    return array($cm, $moduleinfo);
+}
+
+/**
+ * Include once the module lib file.
+ *
+ * @param string $modulename module name of the lib to include
+ */
+function include_modulelib($modulename) {
+    global $CFG;
+    $modlib = "$CFG->dirroot/mod/$modulename/lib.php";
+    if (file_exists($modlib)) {
+        include_once($modlib);
+    } else {
+        throw new moodle_exception('modulemissingcode', '', '', $modlib);
+    }
+}
+
index df0f0d5..8640419 100644 (file)
@@ -274,21 +274,37 @@ 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 ((!array_key_exists('timestamp', $a)) && (!array_key_exists('timestamp', $b))) {
+        return 0;
+    }
+    // We treat instances without timestamp as if they have a timestamp of 0.
+    if ((!array_key_exists('timestamp', $a)) && (array_key_exists('timestamp', $b))) {
+        return 1;
+    }
+    if ((array_key_exists('timestamp', $a)) && (!array_key_exists('timestamp', $b))) {
+        return -1;
     }
-    if ($a->timestamp == $b->timestamp)
+    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))) {
+    if ((!array_key_exists('timestamp', $a)) && (!array_key_exists('timestamp', $b))) {
       return 0;
     }
-    if ($a->timestamp == $b->timestamp)
+    // We treat instances without timestamp as if they have a timestamp of 0.
+    if ((!array_key_exists('timestamp', $a)) && (array_key_exists('timestamp', $b))) {
+        return -1;
+    }
+    if ((array_key_exists('timestamp', $a)) && (!array_key_exists('timestamp', $b))) {
+        return 1;
+    }
+    if ($a->timestamp == $b->timestamp) {
         return 0;
+    }
     return ($a->timestamp < $b->timestamp) ? -1 : 1;
 }
 
index 0e6275e..6fc792a 100644 (file)
@@ -30,6 +30,543 @@ require_once($CFG->dirroot.'/course/lib.php');
 
 class courselib_testcase extends advanced_testcase {
 
+    /**
+     * Set forum specific test values for calling create_module().
+     *
+     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
+     */
+    private function forum_create_set_values(&$moduleinfo) {
+        // Completion specific to forum - optional.
+        $moduleinfo->completionposts = 3;
+        $moduleinfo->completiondiscussions = 1;
+        $moduleinfo->completionreplies = 2;
+
+        // Specific values to the Forum module.
+        $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
+        $moduleinfo->type = 'single';
+        $moduleinfo->trackingtype = FORUM_TRACKING_ON;
+        $moduleinfo->maxbytes = 10240;
+        $moduleinfo->maxattachments = 2;
+
+        // Post threshold for blocking - specific to forum.
+        $moduleinfo->blockperiod = 60*60*24;
+        $moduleinfo->blockafter = 10;
+        $moduleinfo->warnafter = 5;
+    }
+
+    /**
+     * Execute test asserts on the saved DB data by create_module($forum).
+     *
+     * @param object $moduleinfo - the specific forum values that were used to create a forum.
+     * @param object $dbmodinstance - the DB values of the created forum.
+     */
+    private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
+        // Compare values specific to forums.
+        $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
+        $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
+        $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
+        $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
+        $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
+        $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
+        $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
+        $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
+        $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
+        $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
+        $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
+        $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
+        $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
+        $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
+        $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
+        $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
+        $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
+    }
+
+    /**
+     * Set assign module specific test values for calling create_module().
+     *
+     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
+     */
+    private function assign_create_set_values(&$moduleinfo) {
+        // Specific values to the Assign module.
+        $moduleinfo->alwaysshowdescription = true;
+        $moduleinfo->submissiondrafts = true;
+        $moduleinfo->requiresubmissionstatement = true;
+        $moduleinfo->sendnotifications = true;
+        $moduleinfo->sendlatenotifications = true;
+        $moduleinfo->duedate = time() + (7 * 24 * 3600);
+        $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
+        $moduleinfo->allowsubmissionsfromdate = time();
+        $moduleinfo->teamsubmission = true;
+        $moduleinfo->requireallteammemberssubmit = true;
+        $moduleinfo->teamsubmissiongroupingid = true;
+        $moduleinfo->blindmarking = true;
+        $moduleinfo->assignsubmission_onlinetext_enabled = true;
+        $moduleinfo->assignsubmission_file_enabled = true;
+        $moduleinfo->assignsubmission_file_maxfiles = 1;
+        $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
+        $moduleinfo->assignsubmission_comments_enabled = true;
+        $moduleinfo->assignfeedback_comments_enabled = true;
+        $moduleinfo->assignfeedback_offline_enabled = true;
+        $moduleinfo->assignfeedback_file_enabled = true;
+
+        // Advanced grading.
+        $gradingmethods = grading_manager::available_methods();
+        $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
+    }
+
+    /**
+     * Execute test asserts on the saved DB data by create_module($assign).
+     *
+     * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
+     * @param object $dbmodinstance - the DB values of the created assign module.
+     */
+    private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
+        global $DB;
+
+        $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
+        $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
+        $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
+        $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
+        $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
+        $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
+        $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
+        $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
+        $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
+        $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
+        $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
+        // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
+
+        // Advanced grading.
+        $contextmodule = context_module::instance($dbmodinstance->id);
+        $advancedgradingmethod = $DB->get_record('grading_areas',
+            array('contextid' => $contextmodule->id,
+                'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
+        $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
+    }
+
+    /**
+     * Run some asserts test for a specific module for the function create_module().
+     *
+     * The function has been created (and is called) for $this->test_create_module().
+     * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
+     * So if you want, you can overwrite the default values/asserts in the respective functions.
+     * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
+     */
+    private function create_specific_module_test($modulename) {
+        global $DB, $CFG;
+
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+
+        // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
+        require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
+
+        // Enable avaibility.
+        // If not enabled all conditional fields will be ignored.
+        set_config('enableavailability', 1);
+
+        // Enable course completion.
+        // If not enabled all completion settings will be ignored.
+        set_config('enablecompletion', COMPLETION_ENABLED);
+
+        // Enable forum RSS feeds.
+        set_config('enablerssfeeds', 1);
+        set_config('forum_enablerssfeeds', 1);
+
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
+           array('createsections'=>true));
+
+        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
+
+        // Create assign module instance for test.
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $instance = $generator->create_instance($params);
+        $assigncm = get_coursemodule_from_instance('assign', $instance->id);
+
+        // Module test values.
+        $moduleinfo = new stdClass();
+
+        // Always mandatory generic values to any module.
+        $moduleinfo->modulename = $modulename;
+        $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
+        $moduleinfo->course = $course->id;
+        $moduleinfo->groupingid = $grouping->id;
+        $moduleinfo->groupmembersonly = 0;
+        $moduleinfo->visible = true;
+
+        // Sometimes optional generic values for some modules.
+        $moduleinfo->name = 'My test module';
+        $moduleinfo->showdescription = 1; // standard boolean
+        require_once($CFG->libdir . '/gradelib.php');
+        $gradecats = grade_get_categories_menu($moduleinfo->course, false);
+        $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
+        $moduleinfo->gradecat = $gradecatid;
+        $moduleinfo->groupmode = VISIBLEGROUPS;
+        $moduleinfo->cmidnumber = 'idnumber_XXX';
+
+        // Completion common to all module.
+        $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
+        $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
+        $moduleinfo->completiongradeitemnumber = 1;
+        $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
+
+        // Conditional activity.
+        $moduleinfo->availablefrom = time();
+        $moduleinfo->availableuntil = time() + (7 * 24 * 3600);
+        $moduleinfo->showavailability = CONDITION_STUDENTVIEW_SHOW;
+        $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
+        $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
+        $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => OP_CONTAINS, 'conditionfieldvalue' => '@'));
+        $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
+
+        // Grading and Advanced grading.
+        require_once($CFG->dirroot . '/rating/lib.php');
+        $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
+        $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
+        $moduleinfo->assesstimestart = time();
+        $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
+
+        // RSS.
+        $moduleinfo->rsstype = 2;
+        $moduleinfo->rssarticles = 10;
+
+        // Optional intro editor (depends of module).
+        $draftid_editor = 0;
+        file_prepare_draft_area($draftid_editor, null, null, null, null);
+        $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
+
+        // Following is the advanced grading method area called 'submissions' for the 'assign' module.
+        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
+            $moduleinfo->grade = 100;
+        }
+
+        // Plagiarism form values.
+        // No plagiarism plugin installed by default. Use this space to make your own test.
+
+        // Values specific to the module.
+        $modulesetvalues = $modulename.'_create_set_values';
+        $this->$modulesetvalues($moduleinfo);
+
+        // Create the module.
+        $result = create_module($moduleinfo);
+
+        // Retrieve the module info.
+        $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
+        $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
+        // We passed the course section number to create_courses but $dbcm contain the section id.
+        // We need to retrieve the db course section number.
+        $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
+        // Retrieve the grade item.
+        $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
+            'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
+
+        // Compare the values common to all module instances.
+        $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
+        $this->assertEquals($moduleinfo->section, $section->section);
+        $this->assertEquals($moduleinfo->course, $dbcm->course);
+        $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
+        $this->assertEquals($moduleinfo->groupmembersonly, $dbcm->groupmembersonly);
+        $this->assertEquals($moduleinfo->visible, $dbcm->visible);
+        $this->assertEquals($moduleinfo->completion, $dbcm->completion);
+        $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
+        $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
+        $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
+        $this->assertEquals($moduleinfo->availablefrom, $dbcm->availablefrom);
+        $this->assertEquals($moduleinfo->availableuntil, $dbcm->availableuntil);
+        $this->assertEquals($moduleinfo->showavailability, $dbcm->showavailability);
+        $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
+        $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
+        $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
+        $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
+
+        // Optional grade testing.
+        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
+            $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
+        }
+
+        // Some optional (but quite common) to some module.
+        $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
+        $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
+        $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
+
+        // Common values when conditional activity is enabled.
+        foreach ($moduleinfo->conditionfieldgroup as $fieldgroup) {
+            $isfieldgroupsaved = $DB->count_records('course_modules_avail_fields', array('coursemoduleid' => $dbcm->id,
+                'userfield' => $fieldgroup['conditionfield'], 'operator' => $fieldgroup['conditionfieldoperator'],
+                'value' => $fieldgroup['conditionfieldvalue']));
+            $this->assertEquals(1, $isfieldgroupsaved);
+        }
+        foreach ($moduleinfo->conditiongradegroup as $gradegroup) {
+            $isgradegroupsaved = $DB->count_records('course_modules_availability', array('coursemoduleid' => $dbcm->id,
+                'grademin' => $gradegroup['conditiongrademin'], 'grademax' => $gradegroup['conditiongrademax'],
+                'gradeitemid' => $gradegroup['conditiongradeitemid']));
+            $this->assertEquals(1, $isgradegroupsaved);
+        }
+        foreach ($moduleinfo->conditioncompletiongroup as $completiongroup) {
+            $iscompletiongroupsaved = $DB->count_records('course_modules_availability', array('coursemoduleid' => $dbcm->id,
+                'sourcecmid' => $completiongroup['conditionsourcecmid'], 'requiredcompletion' => $completiongroup['conditionrequiredcompletion']));
+            $this->assertEquals(1, $iscompletiongroupsaved);
+        }
+
+        // Test specific to the module.
+        $modulerunasserts = $modulename.'_create_run_asserts';
+        $this->$modulerunasserts($moduleinfo, $dbmodinstance);
+    }
+
+    /**
+     * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
+     */
+    public function test_create_module() {
+        // Add the module name you want to test here.
+        // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
+        $modules = array('forum', 'assign');
+        // Run all tests.
+        foreach ($modules as $modulename) {
+            $this->create_specific_module_test($modulename);
+        }
+    }
+
+    /**
+     * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
+     */
+    public function test_update_module() {
+        // Add the module name you want to test here.
+        // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
+        $modules = array('forum');
+        // Run all tests.
+        foreach ($modules as $modulename) {
+            $this->update_specific_module_test($modulename);
+        }
+    }
+
+    /**
+     * Set forum specific test values for calling update_module().
+     *
+     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
+     */
+    private function forum_update_set_values(&$moduleinfo) {
+        // Completion specific to forum - optional.
+        $moduleinfo->completionposts = 3;
+        $moduleinfo->completiondiscussions = 1;
+        $moduleinfo->completionreplies = 2;
+
+        // Specific values to the Forum module.
+        $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
+        $moduleinfo->type = 'single';
+        $moduleinfo->trackingtype = FORUM_TRACKING_ON;
+        $moduleinfo->maxbytes = 10240;
+        $moduleinfo->maxattachments = 2;
+
+        // Post threshold for blocking - specific to forum.
+        $moduleinfo->blockperiod = 60*60*24;
+        $moduleinfo->blockafter = 10;
+        $moduleinfo->warnafter = 5;
+    }
+
+    /**
+     * Execute test asserts on the saved DB data by update_module($forum).
+     *
+     * @param object $moduleinfo - the specific forum values that were used to update a forum.
+     * @param object $dbmodinstance - the DB values of the updated forum.
+     */
+    private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
+        // Compare values specific to forums.
+        $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
+        $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
+        $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
+        $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
+        $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
+        $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
+        $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
+        $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
+        $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
+        $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
+        $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
+        $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
+        $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
+        $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
+        $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
+        $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
+        $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
+    }
+
+
+
+    /**
+     * Test a specific type of module.
+     *
+     * @param string $modulename - the module name to test
+     */
+    private function update_specific_module_test($modulename) {
+        global $DB, $CFG;
+
+        $this->resetAfterTest(true);
+
+        $this->setAdminUser();
+
+        // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
+        require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
+
+        // Enable avaibility.
+        // If not enabled all conditional fields will be ignored.
+        set_config('enableavailability', 1);
+
+        // Enable course completion.
+        // If not enabled all completion settings will be ignored.
+        set_config('enablecompletion', COMPLETION_ENABLED);
+
+        // Enable forum RSS feeds.
+        set_config('enablerssfeeds', 1);
+        set_config('forum_enablerssfeeds', 1);
+
+        $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
+           array('createsections'=>true));
+
+        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
+
+        // Create assign module instance for testing gradeitem.
+        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+        $params['course'] = $course->id;
+        $instance = $generator->create_instance($params);
+        $assigncm = get_coursemodule_from_instance('assign', $instance->id);
+
+        // Create the test forum to update.
+        $initvalues = new stdClass();
+        $initvalues->introformat = FORMAT_HTML;
+        $initvalues->course = $course->id;
+        $forum = self::getDataGenerator()->create_module('forum', $initvalues);
+
+        // Retrieve course module.
+        $cm = get_coursemodule_from_instance('forum', $forum->id);
+
+        // Module test values.
+        $moduleinfo = new stdClass();
+
+        // Always mandatory generic values to any module.
+        $moduleinfo->coursemodule = $cm->id;
+        $moduleinfo->modulename = $modulename;
+        $moduleinfo->course = $course->id;
+        $moduleinfo->groupingid = $grouping->id;
+        $moduleinfo->groupmembersonly = 0;
+        $moduleinfo->visible = true;
+
+        // Sometimes optional generic values for some modules.
+        $moduleinfo->name = 'My test module';
+        $moduleinfo->showdescription = 1; // standard boolean
+        require_once($CFG->libdir . '/gradelib.php');
+        $gradecats = grade_get_categories_menu($moduleinfo->course, false);
+        $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
+        $moduleinfo->gradecat = $gradecatid;
+        $moduleinfo->groupmode = VISIBLEGROUPS;
+        $moduleinfo->cmidnumber = 'idnumber_XXX';
+
+        // Completion common to all module.
+        $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
+        $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
+        $moduleinfo->completiongradeitemnumber = 1;
+        $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
+
+        // Conditional activity.
+        $moduleinfo->availablefrom = time();
+        $moduleinfo->availableuntil = time() + (7 * 24 * 3600);
+        $moduleinfo->showavailability = CONDITION_STUDENTVIEW_SHOW;
+        $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
+        $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
+        $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => OP_CONTAINS, 'conditionfieldvalue' => '@'));
+        $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
+
+        // Grading and Advanced grading.
+        require_once($CFG->dirroot . '/rating/lib.php');
+        $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
+        $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
+        $moduleinfo->assesstimestart = time();
+        $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
+
+        // RSS.
+        $moduleinfo->rsstype = 2;
+        $moduleinfo->rssarticles = 10;
+
+        // Optional intro editor (depends of module).
+        $draftid_editor = 0;
+        file_prepare_draft_area($draftid_editor, null, null, null, null);
+        $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
+
+        // Following is the advanced grading method area called 'submissions' for the 'assign' module.
+        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
+            $moduleinfo->grade = 100;
+        }
+        // Plagiarism form values.
+        // No plagiarism plugin installed by default. Use this space to make your own test.
+
+        // Values specific to the module.
+        $modulesetvalues = $modulename.'_update_set_values';
+        $this->$modulesetvalues($moduleinfo);
+
+        // Create the module.
+        $result = update_module($moduleinfo);
+
+        // Retrieve the module info.
+        $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
+        $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
+        // Retrieve the grade item.
+        $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
+            'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
+
+        // Compare the values common to all module instances.
+        $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
+        $this->assertEquals($moduleinfo->course, $dbcm->course);
+        $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
+        $this->assertEquals($moduleinfo->groupmembersonly, $dbcm->groupmembersonly);
+        $this->assertEquals($moduleinfo->visible, $dbcm->visible);
+        $this->assertEquals($moduleinfo->completion, $dbcm->completion);
+        $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
+        $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
+        $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
+        $this->assertEquals($moduleinfo->availablefrom, $dbcm->availablefrom);
+        $this->assertEquals($moduleinfo->availableuntil, $dbcm->availableuntil);
+        $this->assertEquals($moduleinfo->showavailability, $dbcm->showavailability);
+        $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
+        $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
+        $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
+        $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
+
+        // Optional grade testing.
+        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
+            $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
+        }
+
+        // Some optional (but quite common) to some module.
+        $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
+        $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
+        $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
+
+        // Common values when conditional activity is enabled.
+        foreach ($moduleinfo->conditionfieldgroup as $fieldgroup) {
+            $isfieldgroupsaved = $DB->count_records('course_modules_avail_fields', array('coursemoduleid' => $dbcm->id,
+                'userfield' => $fieldgroup['conditionfield'], 'operator' => $fieldgroup['conditionfieldoperator'],
+                'value' => $fieldgroup['conditionfieldvalue']));
+            $this->assertEquals(1, $isfieldgroupsaved);
+        }
+        foreach ($moduleinfo->conditiongradegroup as $gradegroup) {
+            $isgradegroupsaved = $DB->count_records('course_modules_availability', array('coursemoduleid' => $dbcm->id,
+                'grademin' => $gradegroup['conditiongrademin'], 'grademax' => $gradegroup['conditiongrademax'],
+                'gradeitemid' => $gradegroup['conditiongradeitemid']));
+            $this->assertEquals(1, $isgradegroupsaved);
+        }
+        foreach ($moduleinfo->conditioncompletiongroup as $completiongroup) {
+            $iscompletiongroupsaved = $DB->count_records('course_modules_availability', array('coursemoduleid' => $dbcm->id,
+                'sourcecmid' => $completiongroup['conditionsourcecmid'], 'requiredcompletion' => $completiongroup['conditionrequiredcompletion']));
+            $this->assertEquals(1, $iscompletiongroupsaved);
+        }
+
+        // Test specific to the module.
+        $modulerunasserts = $modulename.'_update_run_asserts';
+        $this->$modulerunasserts($moduleinfo, $dbmodinstance);
+   }
+
+
     public function test_create_course() {
         global $DB;
         $this->resetAfterTest(true);
index 0b0ee31..f49c1dc 100644 (file)
@@ -96,9 +96,16 @@ switch ($action) {
         $page = optional_param('page', 0, PARAM_INT);
         $outcome->response = $manager->search_other_users($search, $searchanywhere, $page);
         $extrafields = get_extra_user_fields($context);
+        $useroptions = array();
+        // User is not enrolled, either link to site profile or do not link at all.
+        if (has_capability('moodle/user:viewdetails', context_system::instance())) {
+            $useroptions['courseid'] = SITEID;
+        } else {
+            $useroptions['link'] = false;
+        }
         foreach ($outcome->response['users'] as &$user) {
             $user->userId = $user->id;
-            $user->picture = $OUTPUT->user_picture($user);
+            $user->picture = $OUTPUT->user_picture($user, $useroptions);
             $user->fullname = fullname($user);
             $fieldvalues = array();
             foreach ($extrafields as $field) {
index e30ea09..04911b9 100644 (file)
@@ -127,7 +127,7 @@ class enrol_authorize_form extends moodleform
             }
 
             if ($plugin->get_config('an_avs')) {
-                $mform->addElement('header', '', '&nbsp;&nbsp;' . get_string('address'), '');
+                $mform->addElement('header', 'addressheader', '&nbsp;&nbsp;' . get_string('address'), '');
 
                 $mform->addElement('text', 'ccaddress', get_string('address'), 'size="30"');
                 $mform->setType('ccaddress', PARAM_ALPHANUM);
index ea4acd8..f51adca 100644 (file)
@@ -71,8 +71,15 @@ switch ($action) {
         $perpage = optional_param('perpage', 25, PARAM_INT);  //  This value is hard-coded to 25 in quickenrolment.js
         $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page, $perpage, $addedenrollment);
         $extrafields = get_extra_user_fields($context);
+        $useroptions = array();
+        // User is not enrolled yet, either link to site profile or do not link at all.
+        if (has_capability('moodle/user:viewdetails', context_system::instance())) {
+            $useroptions['courseid'] = SITEID;
+        } else {
+            $useroptions['link'] = false;
+        }
         foreach ($outcome->response['users'] as &$user) {
-            $user->picture = $OUTPUT->user_picture($user);
+            $user->picture = $OUTPUT->user_picture($user, $useroptions);
             $user->fullname = fullname($user);
             $fieldvalues = array();
             foreach ($extrafields as $field) {
index 16ad4a2..80631b2 100644 (file)
@@ -63,7 +63,7 @@ class enrol_manual_potential_participant extends user_selector_base {
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > 100) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
@@ -127,7 +127,7 @@ class enrol_manual_current_participant extends user_selector_base {
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > 100) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
index 3943340..9bfba65 100644 (file)
@@ -687,6 +687,8 @@ from a STABLE branch of the Moodle code. See Moodle Docs for more details.';
 $string['maxbytes'] = 'Maximum uploaded file size';
 $string['maxconsecutiveidentchars'] = 'Consecutive identical characters';
 $string['maxeditingtime'] = 'Maximum time to edit posts';
+$string['maxusersperpage'] = ' Maximum users per page';
+$string['configmaxusersperpage'] = 'Maximum number of users displayed within user selector in course, group, cohort, webservice etc.';
 $string['mbstringrecommended'] = 'Installing the optional MBSTRING library is highly recommended in order to improve site performance, particularly if your site is supporting non-Latin languages.';
 $string['mediapluginavi'] = 'Enable .avi filter';
 $string['mediapluginflv'] = 'Enable .flv filter';
index 43db38e..dfd28f6 100644 (file)
@@ -43,6 +43,7 @@ $string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
 $string['cachedef_locking'] = 'Locking';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_string'] = 'Language string cache';
+$string['cachedef_yuimodules'] = 'YUI Module definitions';
 $string['cachelock_file_default'] = 'Default file locking';
 $string['cachestores'] = 'Cache stores';
 $string['component'] = 'Component';
index 46237a9..fe13216 100644 (file)
@@ -1097,7 +1097,7 @@ $string['mymoodledashboard'] = 'My Moodle dashboard';
 $string['myprofile'] = 'My profile';
 $string['name'] = 'Name';
 $string['nameforlink'] = 'What do you want to call this link?';
-$string['nameforpage'] = 'What do you want to call this text?';
+$string['nameforpage'] = 'Name';
 $string['navigation'] = 'Navigation';
 $string['needed'] = 'Needed';
 $string['never'] = 'Never';
@@ -1806,6 +1806,8 @@ $string['welcometocoursetext'] = 'Welcome to {$a->coursename}!
 If you have not done so already, you should edit your profile page so that we can learn more about you:
 
   {$a->profileurl}';
+$string['whatforlink'] = 'What do you want to do with the link?';
+$string['whatforpage'] = 'What do you want to do with the text?';
 $string['whattocallzip'] = 'What do you want to call the zip file?';
 $string['whattodo'] = 'What to do';
 $string['windowclosing'] = 'This window should close automatically. If not, please close it now.';
index 5a59ea6..4b7432d 100644 (file)
@@ -4368,23 +4368,27 @@ function role_get_description(stdClass $role) {
 
 /**
  * Get all the localised role names for a context.
- * @param context $context the context
+ *
+ * @param context $context the context, null means system context
  * @param array of role objects with a ->localname field containing the context-specific role name.
+ * @param int $rolenamedisplay
+ * @param bool $returnmenu true means id=>localname, false means id=>rolerecord
+ * @return array Array of context-specific role names, or role objects with a ->localname field added.
  */
-function role_get_names(context $context) {
-    return role_fix_names(get_all_roles(), $context);
+function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
+    return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu);
 }
 
 /**
  * Prepare list of roles for display, apply aliases and format text
  *
  * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only
- * @param context|bool $context a context
+ * @param context $context the context, null means system context
  * @param int $rolenamedisplay
  * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord
  * @return array Array of context-specific role names, or role objects with a ->localname field added.
  */
-function role_fix_names($roleoptions, $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
+function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
     global $DB;
 
     if (empty($roleoptions)) {
index 1782445..fbd7df3 100644 (file)
@@ -802,11 +802,16 @@ interface parentable_part_of_admin_tree extends part_of_admin_tree {
  * $this->name. If it doesn't, add should be called on child objects that are
  * also parentable_part_of_admin_tree's.
  *
+ * $something should be appended as the last child in the $destinationname. If the
+ * $beforesibling is specified, $something should be prepended to it. If the given
+ * sibling is not found, $something should be appended to the end of $destinationname
+ * and a developer debugging message should be displayed.
+ *
  * @param string $destinationname The internal name of the new parent for $something.
  * @param part_of_admin_tree $something The object to be added.
  * @return bool True on success, false on failure.
  */
-    public function add($destinationname, $something);
+    public function add($destinationname, $something, $beforesibling = null);
 
 }
 
@@ -944,11 +949,18 @@ class admin_category implements parentable_part_of_admin_tree {
     /**
      * Adds a part_of_admin_tree to a child or grandchild (or great-grandchild, and so forth) of this object.
      *
+     * By default the new part of the tree is appended as the last child of the parent. You
+     * can specify a sibling node that the new part should be prepended to. If the given
+     * sibling is not found, the part is appended to the end (as it would be by default) and
+     * a developer debugging message is displayed.
+     *
+     * @throws coding_exception if the $beforesibling is empty string or is not string at all.
      * @param string $destinationame The internal name of the immediate parent that we want for $something.
      * @param mixed $something A part_of_admin_tree or setting instance to be added.
+     * @param string $beforesibling The name of the parent's child the $something should be prepended to.
      * @return bool True if successfully added, false if $something can not be added.
      */
-    public function add($parentname, $something) {
+    public function add($parentname, $something, $beforesibling = null) {
         $parent = $this->locate($parentname);
         if (is_null($parent)) {
             debugging('parent does not exist!');
@@ -965,7 +977,32 @@ class admin_category implements parentable_part_of_admin_tree {
                 // It is intentional to check for the debug level before performing the check.
                 debugging('Duplicate admin page name: ' . $something->name, DEBUG_DEVELOPER);
             }
-            $parent->children[] = $something;
+            if (is_null($beforesibling)) {
+                // Append $something as the parent's last child.
+                $parent->children[] = $something;
+            } else {
+                if (!is_string($beforesibling) or trim($beforesibling) === '') {
+                    throw new coding_exception('Unexpected value of the beforesibling parameter');
+                }
+                // Try to find the position of the sibling.
+                $siblingposition = null;
+                foreach ($parent->children as $childposition => $child) {
+                    if ($child->name === $beforesibling) {
+                        $siblingposition = $childposition;
+                        break;
+                    }
+                }
+                if (is_null($siblingposition)) {
+                    debugging('Sibling '.$beforesibling.' not found', DEBUG_DEVELOPER);
+                    $parent->children[] = $something;
+                } else {
+                    $parent->children = array_merge(
+                        array_slice($parent->children, 0, $siblingposition),
+                        array($something),
+                        array_slice($parent->children, $siblingposition)
+                    );
+                }
+            }
             if (is_array($this->category_cache) and ($something instanceof admin_category)) {
                 if (isset($this->category_cache[$something->name])) {
                     debugging('Duplicate admin category name: '.$something->name);
index fa71321..cd138e6 100644 (file)
@@ -111,5 +111,12 @@ $definitions = array(
         'simplekeys' => true,
         'simpledata' => true,
         'persistent' => true,
-    )
+    ),
+
+    // YUI Module cache.
+    // This stores the YUI module metadata for Shifted YUI modules in Moodle.
+    'yuimodules' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+        'persistent' => true,
+    ),
 );
index 277322a..7260516 100644 (file)
@@ -153,7 +153,7 @@ class tinymce_texteditor extends texteditor {
             'apply_source_formatting' => true,
             'remove_script_host' => false,
             'entity_encoding' => "raw",
-            'plugins' => 'safari,table,style,layer,advhr,advlink,emotions,inlinepopups,' .
+            'plugins' => 'lists,table,style,layer,advhr,advlink,emotions,inlinepopups,' .
                 'searchreplace,paste,directionality,fullscreen,nonbreaking,contextmenu,' .
                 'insertdatetime,save,iespell,preview,print,noneditable,visualchars,' .
                 'xhtmlxtras,template,pagebreak',
index a14746d..dbbb454 100644 (file)
@@ -23,8 +23,7 @@ YUI.add('moodle-form-shortforms', function(Y) {
         },
         CSS = {
             COLLAPSED : 'collapsed',
-            FHEADER : 'fheader',
-            JSPROCESSED : 'jsprocessed'
+            FHEADER : 'fheader'
         },
         ATTRS = {};
 
@@ -67,7 +66,6 @@ YUI.add('moodle-form-shortforms', function(Y) {
             Y.one('#'+this.get('formid')).delegate('click', this.switch_state, SELECTORS.FIELDSETCOLLAPSIBLE+' .'+CSS.FHEADER);
         },
         process_fieldset : function(fieldset) {
-            fieldset.addClass(CSS.JSPROCESSED);
             // Get legend element.
             var legendelement = fieldset.one(SELECTORS.LEGENDFTOGGLER);
 
index c84c9b8..8d4eb46 100644 (file)
@@ -1036,11 +1036,6 @@ abstract class moodleform {
                 }else {
                     $realelementname = $elementname."[$i]";
                 }
-                // This logic to calculate the element id is the same as in
-                // HTML_QuickForm_element::_generateId(). There was no way to
-                // avoid this duplication.
-                $realelementid = 'id_' . str_replace(array('qf_', '[', ']'), array('', '_', ''), $realelementname);
-                $realelementid = clean_param($realelementid, PARAM_ALPHANUMEXT);
                 foreach ($elementoptions as  $option => $params){
 
                     switch ($option){
@@ -1075,8 +1070,8 @@ abstract class moodleform {
                             }
                             break;
 
-                        case 'expanded' :
-                            $mform->setExpanded($realelementid, $params);
+                        case 'expanded':
+                            $mform->setExpanded($realelementname, $params);
                             break;
 
                         case 'advanced' :
@@ -1374,23 +1369,36 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
      * Use this method to indicate that the fieldset should be shown as expanded.
      * The method is applicable to header elements only.
      *
-     * @param string $headerid header element id
+     * @param string $headername header element name
      * @param boolean $expanded default true sets the element to expanded. False makes the element collapsed.
+     * @return void
      */
-    function setExpanded($headerid, $expanded=true){
-        if ($this->getElementType('mform_isexpanded_' . $headerid)===false) {
-            // see if we the form has been submitted already
+    function setExpanded($headername, $expanded=true){
+        if (empty($headername)) {
+            return;
+        }
+        $element = $this->getElement($headername);
+        if ($element->getType() != 'header') {
+            debugging('Cannot use setExpanded on non-header elements', DEBUG_DEVELOPER);
+            return;
+        }
+        if (!$headerid = $element->getAttribute('id')) {
+            $element->_generateId();
+            $headerid = $element->getAttribute('id');
+        }
+        if ($this->getElementType('mform_isexpanded_' . $headerid) === false) {
+            // See if we the form has been submitted already.
             $formexpanded = optional_param('mform_isexpanded_' . $headerid, -1, PARAM_INT);
             if (!$expanded && $formexpanded != -1) {
-                // override expanded state with the form variable
+                // Override expanded state with the form variable.
                 $expanded = $formexpanded;
             }
-            // create the form element for storing expanded state
+            // Create the form element for storing expanded state.
             $this->addElement('hidden', 'mform_isexpanded_' . $headerid);
             $this->setType('mform_isexpanded_' . $headerid, PARAM_INT);
-            $this->setConstant('mform_isexpanded_' . $headerid, (int)$expanded);
+            $this->setConstant('mform_isexpanded_' . $headerid, (int) $expanded);
         }
-        $this->_collapsibleElements[$headerid] = !$expanded;
+        $this->_collapsibleElements[$headername] = !$expanded;
     }
 
     /**
@@ -1531,53 +1539,40 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
             $headercounter = 0;
             foreach (array_keys($this->_elements) as $elementIndex){
                 $element =& $this->_elements[$elementIndex];
-                if ($element->getType()=='header') {
+                if ($element->getType() == 'header') {
                     $headercounter++;
                 }
             }
             if ($headercounter > $this->_non_collapsible_headers) {
                 // So, we have more than $_non_collapsible_headers headers
                 // add all headers to collapsible elements array (if they have not been added yet).
-                unset($lastHeader);
-                $lastHeader = null;
-                $anyRequiredOrError = false;
+                $anyrequiredorerror = false;
                 $headercounter = 0;
+                $headername = null;
                 foreach (array_keys($this->_elements) as $elementIndex){
                     $element =& $this->_elements[$elementIndex];
-                    if ($element->getType()=='header') {
-                        if (!is_null($lastHeader)) {
-                            $lastHeader->_generateId();
-                            // Check if we had any required elements or
-                            // we are at the top header that should be expanded by default.
-                            if ($anyRequiredOrError || $headercounter === 1) {
-                                $this->setExpanded($lastHeader->getAttribute('id'));
-                            } else if (!isset($this->_collapsibleElements[$lastHeader->getAttribute('id')])) {
-                                // Define element as collapsed by default.
-                                $this->setExpanded($lastHeader->getAttribute('id'), false);
-                            }
-                        }
+
+                    if ($element->getType() == 'header') {
                         $headercounter++;
-                        $lastHeader =& $element;
-                        $anyRequiredOrError = false;
-                    } elseif (in_array($element->getName(), $this->_required) || isset($this->_errors[$element->getName()])) {
-                        $anyRequiredOrError = true;
+                        $element->_generateId();
+                        $headername = $element->getName();
+                        $anyrequiredorerror = false;
+                    } else if (in_array($element->getName(), $this->_required) || isset($this->_errors[$element->getName()])) {
+                        $anyrequiredorerror = true;
                     }
-                }
-                // Process very last header.
-                if (!is_null($lastHeader)){
-                    $lastHeader->_generateId();
-                    // Check if we had any required elements or
-                    // we are at the top header that should be expanded by default.
-                    if ($anyRequiredOrError || $headercounter === 1) {
-                        $this->setExpanded($lastHeader->getName());
-                    } elseif (!isset($this->_collapsibleElements[$lastHeader->getAttribute('id')])) {
+
+                    if ($headercounter === 1 || $anyrequiredorerror) {
+                        // If we're working on the first header, or if any error or required field
+                        // is present in this header, we need to expand it.
+                        $this->setExpanded($headername, true);
+                    } else if (!isset($this->_collapsibleElements[$headername])) {
                         // Define element as collapsed by default.
-                        $this->setExpanded($lastHeader->getName(), false);
+                        $this->setExpanded($headername, false);
                     }
                 }
             }
             // Pass the array to renderer object.
-            $renderer->setCollapsibleElements($this->_collapsibleElements, $this->getAttribute('id'));
+            $renderer->setCollapsibleElements($this->_collapsibleElements);
         }
         parent::accept($renderer);
     }
@@ -2343,7 +2338,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
        "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"fcontainer clearfix\">\n\t\t";
 
     /** @var string Template used when opening a fieldset */
-    var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id}>";
+    var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id} {aria-live}>";
 
     /** @var string Template used when closing a fieldset */
     var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
@@ -2587,10 +2582,12 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
         }
 
         // Define collapsible classes for fieldsets.
+        $arialive = '';
         $fieldsetclasses = array('clearfix');
-        if (isset($this->_collapsibleElements[$header->getAttribute('id')])) {
+        if (isset($this->_collapsibleElements[$header->getName()])) {
             $fieldsetclasses[] = 'collapsible';
-            if ($this->_collapsibleElements[$header->getAttribute('id')]) {
+            $arialive = 'aria-live="polite"';
+            if ($this->_collapsibleElements[$header->getName()]) {
                 $fieldsetclasses[] = 'collapsed';
             }
         }
@@ -2601,6 +2598,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
 
         $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
         $openFieldsetTemplate = str_replace('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate);
+        $openFieldsetTemplate = str_replace('{aria-live}', $arialive, $openFieldsetTemplate);
 
         $this->_html .= $openFieldsetTemplate . $header_html;
         $this->_fieldsetsOpen++;
index 416b944..d65775b 100644 (file)
@@ -315,6 +315,12 @@ class theme_config {
      */
     public $hidefromselector = false;
 
+    /**
+     * @var array list of YUI CSS modules to be included on each page. This may be used
+     * to remove cssreset and use cssnormalise module instead.
+     */
+    public $yuicssmodules = array('cssreset', 'cssfonts', 'cssgrids', 'cssbase');
+
     /**
      * @var renderer_factory Instance of the renderer_factory implementation
      * we are using. Implementation detail.
@@ -407,7 +413,8 @@ class theme_config {
 
         $configurable = array('parents', 'sheets', 'parents_exclude_sheets', 'plugins_exclude_sheets', 'javascripts', 'javascripts_footer',
                               'parents_exclude_javascripts', 'layouts', 'enable_dock', 'enablecourseajax', 'supportscssoptimisation',
-                              'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'hidefromselector', 'doctype');
+                              'rendererfactory', 'csspostprocess', 'editor_sheets', 'rarrow', 'larrow', 'hidefromselector', 'doctype',
+                              'yuicssmodules');
 
         foreach ($config as $key=>$value) {
             if (in_array($key, $configurable)) {
index 70af409..019f019 100644 (file)
@@ -601,7 +601,7 @@ class core_renderer extends renderer_base {
             } else if (is_role_switched($course->id)) { // Has switched roles
                 $rolename = '';
                 if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
-                    $rolename = ': '.format_string($role->name);
+                    $rolename = ': '.role_get_name($role, $context);
                 }
                 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename;
                 if ($withlinks) {
index 7aa0c60..1e256bb 100644 (file)
@@ -155,7 +155,7 @@ class page_requirements_manager {
         $sep = empty($CFG->yuislasharguments) ? '?' : '/';
 
         $this->yui3loader = new stdClass();
-        $this->YUI_config = new stdClass();
+        $this->YUI_config = new YUI_config();
 
         // Set up some loader options.
         if (!empty($CFG->useexternalyui) and strpos($CFG->httpswwwroot, 'https:') !== 0) {
@@ -181,54 +181,37 @@ class page_requirements_manager {
         $this->YUI_config->base         = $this->yui3loader->base;
         $this->YUI_config->comboBase    = $this->yui3loader->comboBase;
         $this->YUI_config->combine      = $this->yui3loader->combine;
-        $this->YUI_config->insertBefore = 'firstthemesheet';
-        $this->YUI_config->modules      = array();
-        $this->YUI_config->groups       = array(
-            // Loader for our YUI modules stored in /yui/ subdirectories of our plugins and subsystems.
-            'moodle' => array(
-                'name' => 'moodle',
-                'base' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep.'moodle/'.$jsrev.'/',
-                'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep,
-                'combine' => $this->yui3loader->combine,
-                'ext' => false,
-                'root' => 'moodle/'.$jsrev.'/', // Add the rev to the root path so that we can control caching.
-                'patterns' => array(
-                    'moodle-' => array(
-                        'group' => 'moodle',
-                        'configFn' => '@MOODLECONFIGFN@'
-                    )
-                )
-            ),
-            // Gallery modules are not supported much, sorry.
-            'local' => array(
-                'name' => 'gallery',
-                'base' => $CFG->httpswwwroot . '/lib/yui/gallery/',
-                'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep,
-                'combine' => $this->yui3loader->combine,
-                'ext' => false,
-                'root' => 'gallery/',
-                'patterns' => array(
-                    'gallery-' => array(
-                        'group' => 'gallery',
-                        'configFn' => '@GALLERYCONFIGFN@',
-                    )
-                )
-            ),
+
+        $configname = $this->YUI_config->set_config_function("if(/-skin|reset|fonts|grids|base/.test(me.name)){me.type='css';me.path=me.path.replace(/\.js/,'.css');me.path=me.path.replace(/\/yui2-skin/,'/assets/skins/sam/yui2-skin');}");
+        $this->YUI_config->add_group('yui2', array(
             // Loader configuration for our 2in3, for now ignores $CFG->useexternalyui.
-            'yui2' => array(
-                'base' => $CFG->httpswwwroot . '/lib/yuilib/2in3/' . $CFG->yui2version . '/build/',
-                'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep,
-                'combine' => $this->yui3loader->combine,
-                'ext' => false,
-                'root' => '2in3/' . $CFG->yui2version .'/build/',
-                'patterns' => array(
-                    'yui2-' => array(
-                        'group' => 'yui2',
-                        'configFn' => '@2IN3CONFIGFN@'
-                    )
+            'base' => $CFG->httpswwwroot . '/lib/yuilib/2in3/' . $CFG->yui2version . '/build/',
+            'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep,
+            'combine' => $this->yui3loader->combine,
+            'ext' => false,
+            'root' => '2in3/' . $CFG->yui2version .'/build/',
+            'patterns' => array(
+                'yui2-' => array(
+                    'group' => 'yui2',
+                    'configFn' => $configname,
                 )
             )
-        );
+        ));
+        $configname = $this->YUI_config->set_config_function("var p = me.path, b = me.name.replace(/^moodle-/,'').split('-', 3), n = b.pop();if (!b.length) {Y.log('Attempt to load invalid module name: ' + me.name, 'error'); return;} if (/(skin|core)/.test(n)) {n = b.pop();me.type = 'css';};me.path = b.join('-')+'/'+n+'/'+n+'-min.'+me.type;");
+        $this->YUI_config->add_group('moodle', array(
+            'name' => 'moodle',
+            'base' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep.'moodle/'.$jsrev.'/',
+            'combine' => $this->yui3loader->combine,
+            'comboBase' => $CFG->httpswwwroot . '/theme/yui_combo.php'.$sep,
+            'ext' => false,
+            'root' => 'moodle/'.$jsrev.'/', // Add the rev to the root path so that we can control caching.
+            'patterns' => array(
+                'moodle-' => array(
+                    'group' => 'moodle',
+                    'configFn' => $configname,
+                )
+            )
+        ));
 
         // Set some more loader options applying to groups too.
         if (debugging('', DEBUG_DEVELOPER)) {
@@ -247,6 +230,9 @@ class page_requirements_manager {
             $this->YUI_config->debug = false;
         }
 
+        // Add the moodle group's module data.
+        $this->YUI_config->add_moodle_metadata();
+
         // Every page should include definition of following modules.
         $this->js_module($this->find_module('core_filepicker'));
         $this->js_module($this->find_module('core_dock'));
@@ -402,7 +388,7 @@ class page_requirements_manager {
                 case 'core_filepicker':
                     $module = array('name'     => 'core_filepicker',
                                     'fullpath' => '/repository/filepicker.js',
-                                    'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io-base', 'io-upload-iframe', 'io-form', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort', 'resize-plugin', 'dd-plugin', 'escape', 'moodle-core_filepicker'),
+                                    'requires' => array('base', 'node', 'node-event-simulate', 'json', 'async-queue', 'io-base', 'io-upload-iframe', 'io-form', 'yui2-treeview', 'panel', 'cookie', 'datatable', 'datatable-sort', 'resize-plugin', 'dd-plugin', 'escape'),
                                     'strings'  => array(array('lastmodified', 'moodle'), array('name', 'moodle'), array('type', 'repository'), array('size', 'repository'),
                                                         array('invalidjson', 'repository'), array('error', 'moodle'), array('info', 'moodle'),
                                                         array('nofilesattached', 'repository'), array('filepicker', 'repository'), array('logout', 'repository'),
@@ -535,7 +521,7 @@ class page_requirements_manager {
         if ($this->headdone) {
             $this->extramodules[$module['name']] = $module;
         } else {
-            $this->YUI_config->modules[$module['name']] = $module;
+            $this->YUI_config->add_module_config($module['name'], $module);
         }
         if (debugging('', DEBUG_DEVELOPER)) {
             if (!array_key_exists($module['name'], $this->debug_moduleloadstacktraces)) {
@@ -963,27 +949,31 @@ class page_requirements_manager {
      * Major benefit of this compared to standard js/csss loader is much improved
      * caching, better browser cache utilisation, much fewer http requests.
      *
+     * @param moodle_page $page
      * @return string
      */
-    protected function get_yui3lib_headcode() {
+    protected function get_yui3lib_headcode($page) {
         global $CFG;
 
         $code = '';
 
         if ($this->yui3loader->combine) {
-            $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase
-                     .$CFG->yui3version.'/build/cssreset/reset-min.css&amp;'
-                     .$CFG->yui3version.'/build/cssfonts/fonts-min.css&amp;'
-                     .$CFG->yui3version.'/build/cssgrids/grids-min.css&amp;'
-                     .$CFG->yui3version.'/build/cssbase/base-min.css" />';
+            if (!empty($page->theme->yuicssmodules)) {
+                $modules = array();
+                foreach ($page->theme->yuicssmodules as $module) {
+                    $modules[] = "$CFG->yui3version/build/$module/$module-min.css";
+                }
+                $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->comboBase.implode('&amp;', $modules).'" />';
+            }
             $code .= '<script type="text/javascript" src="'.$this->yui3loader->comboBase
                      .$CFG->yui3version.'/build/simpleyui/simpleyui-min.js&amp;'
                      .$CFG->yui3version.'/build/loader/loader-min.js"></script>';
         } else {
-            $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssreset/reset-min.css" />';
-            $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssfonts/fonts-min.css" />';
-            $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssgrids/grids-min.css" />';
-            $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.'cssbase/base-min.css" />';
+            if (!empty($page->theme->yuicssmodules)) {
+                foreach ($page->theme->yuicssmodules as $module) {
+                    $code .= '<link rel="stylesheet" type="text/css" href="'.$this->yui3loader->base.$module.'/'.$module.'-min.css" />';
+                }
+            }
             $code .= '<script type="text/javascript" src="'.$this->yui3loader->base.'simpleyui/simpleyui-min.js"></script>';
             $code .= '<script type="text/javascript" src="'.$this->yui3loader->base.'loader/loader-min.js"></script>';
         }
@@ -1059,28 +1049,27 @@ class page_requirements_manager {
         // create circular references in memory which prevents garbage collection.
         $this->init_requirements_data($page, $renderer);
 
-        // YUI3 JS and CSS is always loaded first - it is cached in browser.
-        $output = $this->get_yui3lib_headcode();
-
-        // Now theme CSS + custom CSS in this specific order.
-        $output .= $this->get_css_code();
+        $output = '';
 
         // Set up global YUI3 loader object - this should contain all code needed by plugins.
         // Note: in JavaScript just use "YUI().use('overlay', function(Y) { .... });",
         //       this needs to be done before including any other script.
-        $js = "var M = {}; M.yui = {};
-var moodleConfigFn = function(me) {var p = me.path, b = me.name.replace(/^moodle-/,'').split('-', 3), n = b.pop();if (/(skin|core)/.test(n)) {n = b.pop();me.type = 'css';};me.path = b.join('-')+'/'+n+'/'+n+'-min.'+me.type;};
-var galleryConfigFn = function(me) {var p = me.path,v=M.yui.galleryversion,f;if(/-(skin|core)/.test(me.name)) {me.type = 'css';p = p.replace(/-(skin|core)/, '').replace(/\.js/, '.css').split('/'), f = p.pop().replace(/(\-(min|debug))/, '');if (/-skin/.test(me.name)) {p.splice(p.length,0,v,'assets','skins','sam', f);} else {p.splice(p.length,0,v,'assets', f);};} else {p = p.split('/'), f = p.pop();p.splice(p.length,0,v, f);};me.path = p.join('/');};
-var yui2in3ConfigFn = function(me) {if(/-skin|reset|fonts|grids|base/.test(me.name)){me.type='css';me.path=me.path.replace(/\.js/,'.css');me.path=me.path.replace(/\/yui2-skin/,'/assets/skins/sam/yui2-skin');}};\n";
+        $js = "var M = {}; M.yui = {};\n";
+        $js .= $this->YUI_config->get_config_functions();
         $js .= js_writer::set_variable('YUI_config', $this->YUI_config, false) . "\n";
         $js .= "M.yui.loader = {modules: {}};\n"; // Backwards compatibility only, not used any more.
         $js .= js_writer::set_variable('M.cfg', $this->M_cfg, false);
-        $js = str_replace('"@GALLERYCONFIGFN@"', 'galleryConfigFn', $js);
-        $js = str_replace('"@MOODLECONFIGFN@"', 'moodleConfigFn', $js);
-        $js = str_replace('"@2IN3CONFIGFN@"', 'yui2in3ConfigFn', $js);
 
+        $js = $this->YUI_config->update_header_js($js);
         $output .= html_writer::script($js);
 
+        // YUI3 JS and CSS need to be loaded in the header but after the YUI_config has been created.
+        // They should be cached well by the browser.
+        $output .= $this->get_yui3lib_headcode($page);
+
+        // Now theme CSS + custom CSS in this specific order.
+        $output .= $this->get_css_code();
+
         // Link our main JS file, all core stuff should be there.
         $output .= html_writer::script('', $this->js_fix_url('/lib/javascript-static.js'));
 
@@ -1205,6 +1194,253 @@ var yui2in3ConfigFn = function(me) {if(/-skin|reset|fonts|grids|base/.test(me.na
     }
 }
 
+/**
+ * This class represents the YUI configuration.
+ *
+ * @copyright 2013 Andrew Nicols
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.5
+ * @package core
+ * @category output
+ */
+class YUI_config {
+    /**
+     * These settings must be public so that when the object is converted to json they are exposed.
+     * Note: Some of these are camelCase because YUI uses camelCase variable names.
+     *
+     * The settings are described and documented in the YUI API at:
+     * - http://yuilibrary.com/yui/docs/api/classes/config.html
+     * - http://yuilibrary.com/yui/docs/api/classes/Loader.html
+     */
+    public $debug = false;
+    public $base;
+    public $comboBase;
+    public $combine;
+    public $filter = null;
+    public $insertBefore = 'firstthemesheet';
+    public $groups = array();
+    public $modules = array();
+
+    /**
+     * @var array List of functions used by the YUI Loader group pattern recognition.
+     */
+    protected $jsconfigfunctions = array();
+
+    /**
+     * Create a new group within the YUI_config system.
+     *
+     * @param String $name The name of the group. This must be unique and
+     * not previously used.
+     * @param Array $config The configuration for this group.
+     * @return void
+     */
+    public function add_group($name, $config) {
+        if (isset($this->groups[$name])) {
+            throw new coding_exception("A YUI configuration group for '{$name}' already exists. To make changes to this group use YUI_config->update_group().");
+        }
+        $this->groups[$name] = $config;
+    }
+
+    /**
+     * Update an existing group configuration
+     *
+     * Note, any existing configuration for that group will be wiped out.
+     * This includes module configuration.
+     *
+     * @param String $name The name of the group. This must be unique and
+     * not previously used.
+     * @param Array $config The configuration for this group.
+     * @return void
+     */
+    public function update_group($name, $config) {
+        if (!isset($this->groups[$name])) {
+            throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.');
+        }
+        $this->groups[$name] = $config;
+    }
+
+    /**
+     * Set the value of a configuration function used by the YUI Loader's pattern testing.
+     *
+     * Only the body of the function should be passed, and not the whole function wrapper.
+     *
+     * The JS function your write will be passed a single argument 'name' containing the
+     * name of the module being loaded.
+     *
+     * @param $function String the body of the JavaScript function. This should be used i
+     * @return String the name of the function to use in the group pattern configuration.
+     */
+    public function set_config_function($function) {
+        $configname = 'yui' . (count($this->jsconfigfunctions) + 1) . 'ConfigFn';
+        if (isset($this->jsconfigfunctions[$configname])) {
+            throw new coding_exception("A YUI config function with this name already exists. Config function names must be unique.");
+        }
+        $this->jsconfigfunctions[$configname] = $function;
+        return '@' . $configname . '@';
+    }
+
+    /**
+     * Retrieve the list of JavaScript functions for YUI_config groups.
+     *
+     * @return String The complete set of config functions
+     */
+    public function get_config_functions() {
+        $configfunctions = '';
+        foreach ($this->jsconfigfunctions as $functionname => $function) {
+            $configfunctions .= "var {$functionname} = function(me) {";
+            $configfunctions .= $function;
+            $configfunctions .= "};\n";
+        }
+        return $configfunctions;
+    }
+
+    /**
+     * Update the header JavaScript with any required modification for the YUI Loader.
+     *
+     * @param $js String The JavaScript to manipulate.
+     * @return String the modified JS string.
+     */
+    public function update_header_js($js) {
+        // Update the names of the the configFn variables.
+        // The PHP json_encode function cannot handle literal names so we have to wrap
+        // them in @ and then replace them with literals of the same function name.
+        foreach ($this->jsconfigfunctions as $functionname => $function) {
+            $js = str_replace('"@' . $functionname . '@"', $functionname, $js);
+        }
+        return $js;
+    }
+
+    /**
+     * Add configuration for a specific module.
+     *
+     * @param String $name The name of the module to add configuration for.
+     * @param Array $config The configuration for the specified module.
+     * @param String $group The name of the group to add configuration for.
+     * If not specified, then this module is added to the global
+     * configuration.
+     * @return void
+     */
+    public function add_module_config($name, $config, $group = null) {
+        if ($group) {
+            if (!isset($this->groups[$name])) {
+                throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.');
+            }
+            if (!isset($this->groups[$group]['modules'])) {
+                $this->groups[$group]['modules'] = array();
+            }
+            $modules = &$this->groups[$group]['modules'];
+        } else {
+            $modules = &$this->modules;
+        }
+        $modules[$name] = $config;
+    }
+
+    /**
+     * Add the moodle YUI module metadata for the moodle group to the YUI_config instance.
+     *
+     * If js caching is disabled, metadata will not be served causing YUI to calculate
+     * module dependencies as each module is loaded.
+     *
+     * If metadata does not exist it will be created and stored in a MUC entry.
+     *
+     * @return void
+     */
+    public function add_moodle_metadata() {
+        global $CFG;
+        if (!isset($this->groups['moodle'])) {
+            throw new coding_exception('The Moodle YUI module does not exist. You must define the moodle module config using YUI_config->add_module_config first.');
+        }
+
+        if (!isset($this->groups['moodle']['modules'])) {
+            $this->groups['moodle']['modules'] = array();
+        }
+
+        $cache = cache::make('core', 'yuimodules');
+        if ($CFG->jsrev == -1) {
+            $metadata = array();
+            $cache->delete('metadata');
+        } else {
+            // Attempt to get the metadata from the cache.
+            if (!$metadata = $cache->get('metadata')) {
+                $metadata = $this->get_moodle_metadata();
+                $cache->set('metadata', $metadata);
+            }
+        }
+
+        // Merge with any metadata added specific to this page which was added manually.
+        $this->groups['moodle']['modules'] = array_merge($this->groups['moodle']['modules'],
+                $metadata);
+    }
+
+    /**
+     * Determine the module metadata for all moodle YUI modules.
+     *
+     * This works through all modules capable of serving YUI modules, and attempts to get
+     * metadata for each of those modules.
+     *
+     * @return Array of module metadata
+     */
+    private function get_moodle_metadata() {
+        $moodlemodules = array();
+        // Core isn't a plugin type or subsystem - handle it seperately.
+        if ($module = $this->get_moodle_path_metadata(get_component_directory('core'))) {
+            $moodlemodules = array_merge($moodlemodules, $module);
+        }
+
+        // Handle other core subsystems.
+        $subsystems = get_core_subsystems();
+        foreach ($subsystems as $subsystem => $path) {
+            if (is_null($path)) {
+                continue;
+            }
+            $path = get_component_directory($subsystem);
+            if ($module = $this->get_moodle_path_metadata($path)) {
+                $moodlemodules = array_merge($moodlemodules, $module);
+            }
+        }
+
+        // And finally the plugins.
+        $plugintypes = get_plugin_types();
+        foreach ($plugintypes as $plugintype => $pathroot) {
+            $pluginlist = get_plugin_list($plugintype);
+            foreach ($pluginlist as $plugin => $path) {
+                if ($module = $this->get_moodle_path_metadata($path)) {
+                    $moodlemodules = array_merge($moodlemodules, $module);
+                }
+            }
+        }
+
+        return $moodlemodules;
+    }
+
+    /**
+     * Helper function process and return the YUI metadata for all of the modules under the specified path.
+     *
+     * @param String $path the UNC path to the YUI src directory.
+     * @return Array the complete array for frankenstyle directory.
+     */
+    private function get_moodle_path_metadata($path) {
+        // Add module metadata is stored in frankenstyle_modname/yui/src/yui_modname/meta/yui_modname.json.
+        $baseyui = $path . '/yui/src';
+        $modules = array();
+        if (is_dir($baseyui)) {
+            $items = new DirectoryIterator($baseyui);
+            foreach ($items as $item) {
+                if ($item->isDot() or !$item->isDir()) {
+                    continue;
+                }
+                $metafile = realpath($baseyui . '/' . $item . '/meta/' . $item . '.json');
+                if (!is_readable($metafile)) {
+                    continue;
+                }
+                $metadata = file_get_contents($metafile);
+                $modules = array_merge($modules, (array) json_decode($metadata));
+            }
+        }
+        return $modules;
+    }
+}
+
 /**
  * Invalidate all server and client side JS caches.
  */
diff --git a/lib/tests/admintree_test.php b/lib/tests/admintree_test.php
new file mode 100644 (file)
index 0000000..f633bd7
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ * Unit tests for those parts of adminlib.php that implement the admin tree
+ * functionality.
+ *
+ * @package     core
+ * @category    test
+ * @copyright   2013 David Mudrak <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir.'/adminlib.php');
+
+/**
+ * Provides the unit tests for admin tree functionality.
+ */
+class admintree_testcase extends advanced_testcase {
+
+    /**
+     * Adding nodes into the admin tree
+     */
+    public function test_add_nodes() {
+
+        $tree = new admin_root(true);
+        $tree->add('root', $one = new admin_category('one', 'One'));
+        $tree->add('root', new admin_category('three', 'Three'));
+        $tree->add('one', new admin_category('one-one', 'One-one'));
+        $tree->add('one', new admin_category('one-three', 'One-three'));
+
+        // Check the order of nodes in the root.
+        $map = array();
+        foreach ($tree->children as $child) {
+            $map[] = $child->name;
+        }
+        $this->assertEquals(array('one', 'three'), $map);
+
+        // Insert a node into the middle.
+        $tree->add('root', new admin_category('two', 'Two'), 'three');
+        $map = array();
+        foreach ($tree->children as $child) {
+            $map[] = $child->name;
+        }
+        $this->assertEquals(array('one', 'two', 'three'), $map);
+
+        // Non-existing sibling.
+        $tree->add('root', new admin_category('four', 'Four'), 'five');
+        $this->assertDebuggingCalled('Sibling five not found', DEBUG_DEVELOPER);
+
+        $tree->add('root', new admin_category('five', 'Five'));
+        $map = array();
+        foreach ($tree->children as $child) {
+            $map[] = $child->name;
+        }
+        $this->assertEquals(array('one', 'two', 'three', 'four', 'five'), $map);
+
+        // Insert a node into the middle of the subcategory
+        $tree->add('one', new admin_category('one-two', 'One-two'), 'one-three');
+        $map = array();
+        foreach ($one->children as $child) {
+            $map[] = $child->name;
+        }
+        $this->assertEquals(array('one-one', 'one-two', 'one-three'), $map);
+
+        // Check just siblings, not parents or children.
+        $tree->add('one', new admin_category('one-four', 'One-four'), 'one');
+        $this->assertDebuggingCalled('Sibling one not found', DEBUG_DEVELOPER);
+
+        $tree->add('root', new admin_category('six', 'Six'), 'one-two');
+        $this->assertDebuggingCalled('Sibling one-two not found', DEBUG_DEVELOPER);
+
+        // Me! Me! I wanna be first!
+        $tree->add('root', new admin_externalpage('zero', 'Zero', 'http://foo.bar'), 'one');
+        $map = array();
+        foreach ($tree->children as $child) {
+            $map[] = $child->name;
+        }
+        $this->assertEquals(array('zero', 'one', 'two', 'three', 'four', 'five', 'six'), $map);
+    }
+
+    /**
+     * @expectedException coding_exception
+     */
+    public function test_add_nodes_before_invalid1() {
+        $tree = new admin_root(true);
+        $tree->add('root', new admin_externalpage('foo', 'Foo', 'http://foo.bar'), array('moodle:site/config'));
+    }
+
+    /**
+     * @expectedException coding_exception
+     */
+    public function test_add_nodes_before_invalid2() {
+        $tree = new admin_root(true);
+        $tree->add('root', new admin_category('bar', 'Bar'), '');
+    }
+}
index 4ce8061..6c2a80e 100644 (file)
@@ -111,7 +111,7 @@ class behat_forms extends behat_base {
         try {
 
             // Expand fieldsets.
-            $fieldsets = $this->find_all('css', 'fieldset.collapsed.jsprocessed a.fheader');
+            $fieldsets = $this->find_all('css', 'fieldset.collapsed a.fheader');
 
             // We are supposed to have fieldsets here, otherwise exception.
 
index 3b243b8..4a9e78b 100644 (file)
@@ -1,4 +1,4 @@
-This files describes API changes in core lbraries and APIs,
+This files describes API changes in core libraries and APIs,
 information provided here is intended especially for developers.
 
 === 2.5 ===
@@ -26,6 +26,11 @@ information provided here is intended especially for developers.
 * Function get_users_listing now return list of users except guest and deleted users. Previously
   deleted users were excluded by get_users_listing. As guest user is not expected while browsing users,
   and not included in get_user function, it will not be returned by get_users_listing.
+* The add_* functions in course/dnduploadlib.php have been deprecated. Plugins should be using the
+  MODNAME_dndupload_register callback instead.
+* The signature of the add() method of classes implementing the parentable_part_of_admin_tree
+  interface (such as admin_category) has been extended. The new parameter allows the caller
+  to prepend the new node before an existing sibling in the admin tree.
 
 YUI changes:
 * M.util.help_icon has been deprecated. Code should be updated to use moodle-core-popuphelp
index 33a185c..32d5239 100644 (file)
@@ -1518,15 +1518,13 @@ function upgrade_core($version, $verbose) {
 
         print_upgrade_part_start('moodle', false, $verbose);
 
-        // one time special local migration pre 2.0 upgrade script
-        if ($CFG->version < 2007101600) {
-            $pre20upgradefile = "$CFG->dirroot/local/upgrade_pre20.php";
-            if (file_exists($pre20upgradefile)) {
-                set_time_limit(0);
-                require($pre20upgradefile);
-                // reset upgrade timeout to default
-                upgrade_set_timeout();
-            }
+        // Pre-upgrade scripts for local hack workarounds.
+        $preupgradefile = "$CFG->dirroot/local/preupgrade.php";
+        if (file_exists($preupgradefile)) {
+            set_time_limit(0);
+            require($preupgradefile);
+            // Reset upgrade timeout to default.
+            upgrade_set_timeout();
         }
 
         $result = xmldb_main_upgrade($CFG->version);
index f7c5f96..07de021 100644 (file)
@@ -39,8 +39,8 @@ are supported. There may be some extra files with special meaning in /local/.
 Sample /local/ directory listing:
 /local/nicehack/         - first customisation plugin
 /local/otherhack/        - other customisation plugin
-/local/upgrade_pre20.php - one time upgrade and migration script which is
-                           executed before main 2.0 upgrade starts
+/local/preupgrade.php    - executed before each core upgrade, use $version and $CFG->version
+                           if you need to tweak specific local hacks
 /local/defaults.php      - custom admin setting defaults
 
 
index 885e0c7..953539f 100644 (file)
@@ -34,8 +34,9 @@ class login_change_password_form extends moodleform {
         global $USER, $CFG;
 
         $mform = $this->_form;
+        $mform->setDisableShortforms(true);
 
-        $mform->addElement('header', '', get_string('changepassword'), '');
+        $mform->addElement('header', 'changepassword', get_string('changepassword'), '');
 
         // visible elements
         $mform->addElement('static', 'username', get_string('username'), $USER->username);
index fbbfec8..997dcd5 100644 (file)
@@ -32,8 +32,9 @@ class login_forgot_password_form extends moodleform {
 
     function definition() {
         $mform    = $this->_form;
+        $mform->setDisableShortforms(true);
 
-        $mform->addElement('header', '', get_string('searchbyusername'), '');
+        $mform->addElement('header', 'searchbyusername', get_string('searchbyusername'), '');
 
         $mform->addElement('text', 'username', get_string('username'));
         $mform->setType('username', PARAM_RAW);
@@ -41,7 +42,7 @@ class login_forgot_password_form extends moodleform {
         $submitlabel = get_string('search');
         $mform->addElement('submit', 'submitbuttonusername', $submitlabel);
 
-        $mform->addElement('header', '', get_string('searchbyemail'), '');
+        $mform->addElement('header', 'searchbyemail', get_string('searchbyemail'), '');
 
         $mform->addElement('text', 'email', get_string('email'));
         $mform->setType('email', PARAM_RAW);
index ec230c1..87513e0 100644 (file)
@@ -35,7 +35,7 @@ class login_signup_form extends moodleform {
 
         $mform = $this->_form;
 
-        $mform->addElement('header', '', get_string('createuserandpass'), '');
+        $mform->addElement('header', 'createuserandpass', get_string('createuserandpass'), '');
 
 
         $mform->addElement('text', 'username', get_string('username'), 'maxlength="100" size="12"');
@@ -49,7 +49,7 @@ class login_signup_form extends moodleform {
         $mform->setType('password', PARAM_RAW);
         $mform->addRule('password', get_string('missingpassword'), 'required', null, 'server');
 
-        $mform->addElement('header', '', get_string('supplyinfo'),'');
+        $mform->addElement('header', 'supplyinfo', get_string('supplyinfo'),'');
 
         $mform->addElement('text', 'email', get_string('email'), 'maxlength="100" size="25"');
         $mform->setType('email', PARAM_NOTAGS);
@@ -103,7 +103,8 @@ class login_signup_form extends moodleform {
         profile_signup_fields($mform);
 
         if (!empty($CFG->sitepolicy)) {
-            $mform->addElement('header', '', get_string('policyagreement'), '');
+            $mform->addElement('header', 'policyagreement', get_string('policyagreement'), '');
+            $mform->setExpanded('policyagreement');
             $mform->addElement('static', 'policylink', '', '<a href="'.$CFG->sitepolicy.'" onclick="this.target=\'_blank\'">'.get_String('policyagreementclick').'</a>');
             $mform->addElement('checkbox', 'policyagreed', get_string('policyaccept'));
             $mform->addRule('policyagreed', get_string('policyagree'), 'required', null, 'server');
index 8d7884b..303d3d1 100644 (file)
@@ -264,8 +264,17 @@ class input_manager extends singleton_pattern {
                 if (strpos($raw, '~') !== false) {
                     throw new invalid_option_exception('Using the tilde (~) character in paths is not supported');
                 }
+                $colonpos = strpos($raw, ':');
+                if ($colonpos !== false) {
+                    if ($colonpos !== 1 or strrpos($raw, ':') !== 1) {
+                        throw new invalid_option_exception('Using the colon (:) character in paths is supported for Windows drive labels only.');
+                    }
+                    if (preg_match('/^[a-zA-Z]:/', $raw) !== 1) {
+                        throw new invalid_option_exception('Using the colon (:) character in paths is supported for Windows drive labels only.');
+                    }
+                }
                 $raw = str_replace('\\', '/', $raw);
-                $raw = preg_replace('~[[:cntrl:]]|[&<>"`\|\':]~u', '', $raw);
+                $raw = preg_replace('~[[:cntrl:]]|[&<>"`\|\']~u', '', $raw);
                 $raw = preg_replace('~\.\.+~', '', $raw);
                 $raw = preg_replace('~//+~', '/', $raw);
                 $raw = preg_replace('~/(\./)+~', '/', $raw);
index 4724172..76800eb 100644 (file)
@@ -144,7 +144,9 @@ class mdeploytest extends PHPUnit_Framework_TestCase {
             array('0', input_manager::TYPE_FLAG, true),
             array('muhehe', input_manager::TYPE_FLAG, true),
 
-            array('C:\\WINDOWS\\user.dat', input_manager::TYPE_PATH, 'C/WINDOWS/user.dat'),
+            array('C:\\WINDOWS\\user.dat', input_manager::TYPE_PATH, 'C:/WINDOWS/user.dat'),
+            array('D:\xampp\htdocs\24_integration/mdeploy.php', input_manager::TYPE_PATH, 'D:/xampp/htdocs/24_integration/mdeploy.php'),
+            array('d:/xampp/htdocs/24_integration/mdeploy.php', input_manager::TYPE_PATH, 'd:/xampp/htdocs/24_integration/mdeploy.php'),
             array('../../../etc/passwd', input_manager::TYPE_PATH, '/etc/passwd'),
             array('///////.././public_html/test.php', input_manager::TYPE_PATH, '/public_html/test.php'),
 
@@ -163,6 +165,30 @@ class mdeploytest extends PHPUnit_Framework_TestCase {
         );
     }
 
+    /**
+     * @expectedException invalid_option_exception
+     */
+    public function test_input_type_path_multiple_colons() {
+        $input = testable_input_manager::instance();
+        $input->cast_value('C:\apache\log:file', input_manager::TYPE_PATH); // must throw exception
+    }
+
+    /**
+     * @expectedException invalid_option_exception
+     */
+    public function test_input_type_path_invalid_drive_label() {
+        $input = testable_input_manager::instance();
+        $input->cast_value('0:/srv/moodledata', input_manager::TYPE_PATH); // must throw exception
+    }
+
+    /**
+     * @expectedException invalid_option_exception
+     */
+    public function test_input_type_path_invalid_colon() {
+        $input = testable_input_manager::instance();
+        $input->cast_value('/var/www/moodle:2.5', input_manager::TYPE_PATH); // must throw exception
+    }
+
     /**
      * @expectedException invalid_coding_exception
      */
index 1d28521..a0beb17 100644 (file)
@@ -44,7 +44,8 @@ class assignfeedback_file_batch_upload_files_form extends moodleform {
         $mform = $this->_form;
         $params = $this->_customdata;
 
-        $mform->addElement('header', '', get_string('batchuploadfilesforusers', 'assignfeedback_file', count($params['users'])));
+        $mform->addElement('header', 'batchuploadfilesforusers', get_string('batchuploadfilesforusers', 'assignfeedback_file',
+            count($params['users'])));
         $mform->addElement('static', 'userslist', get_string('selectedusers', 'assignfeedback_file'), $params['usershtml']);
 
         $data = new stdClass();
index 0c6b397..d9e7636 100644 (file)
@@ -43,7 +43,7 @@ class assignfeedback_file_upload_zip_form extends moodleform {
         $mform = $this->_form;
         $params = $this->_customdata;
 
-        $mform->addElement('header', '', get_string('uploadzip', 'assignfeedback_file'));
+        $mform->addElement('header', 'uploadzip', get_string('uploadzip', 'assignfeedback_file'));
 
         $fileoptions = array('subdirs'=>0,
                                 'maxbytes'=>$COURSE->maxbytes,
index 61df88f..c573efe 100644 (file)
@@ -43,7 +43,7 @@ class assignfeedback_offline_upload_grades_form extends moodleform {
         $mform = $this->_form;
         $params = $this->_customdata;
 
-        $mform->addElement('header', '', get_string('uploadgrades', 'assignfeedback_offline'));
+        $mform->addElement('header', 'uploadgrades', get_string('uploadgrades', 'assignfeedback_offline'));
 
         $fileoptions = array('subdirs'=>0,
                                 'maxbytes'=>$COURSE->maxbytes,
index aad2a58..c57fe3b 100644 (file)
@@ -131,10 +131,10 @@ function assign_reset_course_form_defaults($course) {
  *
  * This is done by calling the update_instance() method of the assignment type class
  * @param stdClass $data
- * @param mod_assign_mod_form $form
+ * @param $form
  * @return object
  */
-function assign_update_instance(stdClass $data, mod_assign_mod_form $form) {
+function assign_update_instance(stdClass $data, $form) {
     global $CFG;
     require_once($CFG->dirroot . '/mod/assign/locallib.php');
     $context = context_module::instance($data->coursemodule);
index 84ec317..cfc001c 100644 (file)
@@ -83,7 +83,7 @@ class feedback_edit_use_template_form extends moodleform {
 
         $elementgroup = array();
         //headline
-        $mform->addElement('header', '', get_string('using_templates', 'feedback'));
+        $mform->addElement('header', 'using_templates', get_string('using_templates', 'feedback'));
         // hidden elements
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
@@ -164,7 +164,7 @@ class feedback_edit_create_template_form extends moodleform {
         $mform->setType('savetemplate', PARAM_INT);
 
         //headline
-        $mform->addElement('header', '', get_string('creating_templates', 'feedback'));
+        $mform->addElement('header', 'creating_templates', get_string('creating_templates', 'feedback'));
 
         // visible elements
         $elementgroup = array();
index 9481f05..aacb692 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="mod/folder/db" VERSION="20130121" COMMENT="XMLDB file for Folder module"
+<XMLDB PATH="mod/folder/db" VERSION="20130315" COMMENT="XMLDB file for Folder module"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
@@ -14,6 +14,7 @@
         <FIELD NAME="revision" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="incremented when after each file changes, solves browser caching issues"/>
         <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="display" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Display type of folder contents - on a separate page or inline"/>
+        <FIELD NAME="show_expanded" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="false" DEFAULT="1" SEQUENCE="false" COMMENT="1 = expanded, 0 = collapsed for sub-folders"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
@@ -23,4 +24,4 @@
       </INDEXES>
     </TABLE>
   </TABLES>
-</XMLDB>
\ No newline at end of file
+</XMLDB>
index 9db0090..fabe0c9 100644 (file)
@@ -58,7 +58,6 @@ function xmldb_folder_upgrade($oldversion) {
     // Moodle v2.3.0 release upgrade line
     // Put any upgrade step following this
 
-
     // Moodle v2.4.0 release upgrade line
     // Put any upgrade step following this
 
@@ -77,5 +76,20 @@ function xmldb_folder_upgrade($oldversion) {
         upgrade_mod_savepoint(true, 2013012100, 'folder');
     }
 
+    if ($oldversion < 2013031500) {
+
+        // Define field show_expanded to be added to folder
+        $table = new xmldb_table('folder');
+        $field = new xmldb_field('show_expanded', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'revision');
+
+        // Conditionally launch add field show_expanded
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // folder savepoint reached
+        upgrade_mod_savepoint(true, 2013031500, 'folder');
+    }
+
     return true;
 }
index b713063..e5d68ce 100644 (file)
@@ -51,3 +51,5 @@ Also note that participants view actions can not be logged in this case.';
 $string['displaypage'] = 'On a separate page';
 $string['displayinline'] = 'Inline on a course page';
 $string['noautocompletioninline'] = 'Automatic completion on viewing of activity can not be selected together with "Display inline" option';
+$string['show_expanded'] = 'Show sub-folders expanded';
+$string['show_expanded_help'] = 'If enabled, will display sub-folders expanded by default. Else, sub-folders will display collapsed.';
index 70af9e1..99f1deb 100644 (file)
@@ -55,7 +55,10 @@ class mod_folder_mod_form extends moodleform_mod {
         $mform->addHelpButton('display', 'display', 'mod_folder');
         $mform->setExpanded('content');
 
-
+        // Adding option to show sub-folders expanded or collapsed by default.
+        $mform->addElement('advcheckbox', 'show_expanded', get_string('show_expanded', 'folder'));
+        $mform->addHelpButton('show_expanded', 'show_expanded', 'mod_folder');
+        $mform->setDefault('show_expanded', $config->show_expanded);
         //-------------------------------------------------------
         $this->standard_coursemodule_elements();
 
index 415d167..64097f5 100644 (file)
@@ -35,6 +35,9 @@ M.mod_folder.init_tree = function(Y, id, expand_all) {
 
         if (expand_all) {
             tree.expandAll();
+        } else {
+            // Else just expand the top node.
+            tree.getNodeByIndex(1).expand();
         }
 
         tree.render();
index c9dbbb7..55420f3 100644 (file)
@@ -61,7 +61,8 @@ class mod_folder_renderer extends plugin_renderer_base {
         $output .= $this->output->box($this->render(new folder_tree($folder, $cm)),
                 'generalbox foldertree');
 
-        if (has_capability('mod/folder:managefiles', $context)) {
+        // Do not append the edit button on the course page.
+        if ($folder->display != FOLDER_DISPLAY_INLINE && has_capability('mod/folder:managefiles', $context)) {
             $output .= $this->output->container(
                     $this->output->single_button(new moodle_url('/mod/folder/edit.php',
                     array('id' => $cm->id)), get_string('edit')),
@@ -78,7 +79,11 @@ class mod_folder_renderer extends plugin_renderer_base {
         $content .= '<div id="'.$id.'" class="filemanager">';
         $content .= $this->htmllize_tree($tree, array('files' => array(), 'subdirs' => array($tree->dir)));
         $content .= '</div>';
-        $this->page->requires->js_init_call('M.mod_folder.init_tree', array($id, true));
+        $show_expanded = true;
+        if (empty($tree->folder->show_expanded)) {
+            $show_expanded = false;
+        }
+        $this->page->requires->js_init_call('M.mod_folder.init_tree', array($id, $show_expanded));
         return $content;
     }
 
index c0034bd..4cfea08 100644 (file)
@@ -30,4 +30,8 @@ if ($ADMIN->fulltree) {
     //--- general settings -----------------------------------------------------------------------------------
     $settings->add(new admin_setting_configcheckbox('folder/requiremodintro',
         get_string('requiremodintro', 'admin'), get_string('configrequiremodintro', 'admin'), 1));
+
+    $settings->add(new admin_setting_configcheckbox('folder/show_expanded',
+            get_string('show_expanded', 'folder'),
+            get_string('show_expanded_help', 'folder'), 1));
 }
index 1e168a3..c3cf83b 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2013012100;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2013031500;       // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2012112900;    // Requires this Moodle version
 $module->component = 'mod_folder';     // Full name of the plugin (used for diagnostics)
 $module->cron      = 0;
index dc06db9..73a5000 100644 (file)
@@ -7894,8 +7894,6 @@ abstract class forum_subscriber_selector_base extends user_selector_base {
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class forum_potential_subscriber_selector extends forum_subscriber_selector_base {
-    const MAX_USERS_PER_PAGE = 100;
-
     /**
      * If set to true EVERYONE in this course is force subscribed to this forum
      * @var bool
@@ -7983,7 +7981,7 @@ class forum_potential_subscriber_selector extends forum_subscriber_selector_base
         // Check to see if there are too many to show sensibly.
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
-            if ($potentialmemberscount > self::MAX_USERS_PER_PAGE) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
index 35704e4..d8a3d55 100644 (file)
@@ -88,7 +88,7 @@ class mod_forum_mod_form extends moodleform_mod {
 
         if ($CFG->enablerssfeeds && isset($CFG->forum_enablerssfeeds) && $CFG->forum_enablerssfeeds) {
 //-------------------------------------------------------------------------------
-            $mform->addElement('header', '', get_string('rss'));
+            $mform->addElement('header', 'rssheader', get_string('rss'));
             $choices = array();
             $choices[0] = get_string('none');
             $choices[1] = get_string('discussions', 'forum');
@@ -116,7 +116,7 @@ class mod_forum_mod_form extends moodleform_mod {
         }
 
 //-------------------------------------------------------------------------------
-        $mform->addElement('header', '', get_string('blockafter', 'forum'));
+        $mform->addElement('header', 'blockafterheader', get_string('blockafter', 'forum'));
         $options = array();
         $options[0] = get_string('blockperioddisabled','forum');
         $options[60*60*24]   = '1 '.get_string('day');
index 02e935b..b3d742b 100644 (file)
@@ -119,7 +119,7 @@ class mod_forum_post_form extends moodleform {
         }
 
         if (!empty($CFG->forum_enabletimedposts) && !$post->parent && has_capability('mod/forum:viewhiddentimedposts', $coursecontext)) { // hack alert
-            $mform->addElement('header', '', get_string('displayperiod', 'forum'));
+            $mform->addElement('header', 'displayperiod', get_string('displayperiod', 'forum'));
 
             $mform->addElement('date_selector', 'timestart', get_string('displaystart', 'forum'), array('optional'=>true));
             $mform->addHelpButton('timestart', 'displaystart', 'forum');
index 6745606..e7af45a 100644 (file)
@@ -103,7 +103,7 @@ class mod_glossary_mod_form extends moodleform_mod {
 
         if ($CFG->enablerssfeeds && isset($CFG->glossary_enablerssfeeds) && $CFG->glossary_enablerssfeeds) {
 //-------------------------------------------------------------------------------
-            $mform->addElement('header', '', get_string('rss'));
+            $mform->addElement('header', 'rssheader', get_string('rss'));
             $choices = array();
             $choices[0] = get_string('none');
             $choices[1] = get_string('withauthor', 'glossary');
index a2cb2c1..9e19a97 100644 (file)
             //$params     = array();
             $i = 0;
 
-            $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition',"' '", "COALESCE(al.alias, '')");
+            if (empty($fullsearch)) {
+                // With fullsearch disabled, look only within concepts and aliases.
+                $concat = $DB->sql_concat('ge.concept', "' '", "COALESCE(al.alias, '')");
+            } else {
+                // With fullsearch enabled, look also within definitions.
+                $concat = $DB->sql_concat('ge.concept', "' '", 'ge.definition', "' '", "COALESCE(al.alias, '')");
+            }
 
             $searchterms = explode(" ",$hook);
 
     }
 
     $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
-    $allentries = $DB->get_records_sql($query, $params, $limitfrom, $limitnum);
-
+    $allentries = $DB->get_records_sql($query, $params, $limitfrom, $limitnum);
\ No newline at end of file
index 55787fc..5731413 100644 (file)
@@ -31,7 +31,7 @@ $string['dndmedia'] = 'Media drag and drop';
 $string['dndresizeheight'] = 'Resize drag and drop height';
 $string['dndresizewidth'] = 'Resize drag and drop width';
 $string['dnduploadlabel'] = 'Add image to course page';
-$string['dnduploadlabeltext'] = 'Add text to course page';
+$string['dnduploadlabeltext'] = 'Add a label to the course page';
 $string['label:addinstance'] = 'Add a new label';
 $string['labeltext'] = 'Label text';
 $string['modulename'] = 'Label';
index b914905..224f74c 100644 (file)
@@ -264,6 +264,9 @@ function label_dndupload_handle($uploadinfo) {
         }
     } else if (!empty($uploadinfo->content)) {
         $data->intro = $uploadinfo->content;
+        if ($uploadinfo->type != 'text/html') {
+            $data->introformat = FORMAT_PLAIN;
+        }
     }
 
     return label_add_instance($data, null);
index 3dd3d3c..d7fbf9a 100644 (file)
@@ -26,7 +26,7 @@
 $string['configdisplayoptions'] = 'Select all options that should be available, existing settings are not modified. Hold CTRL key to select multiple fields.';
 $string['content'] = 'Page content';
 $string['contentheader'] = 'Content';
-$string['createpage'] = 'Create a new page';
+$string['createpage'] = 'Create a new page resource';
 $string['displayoptions'] = 'Available display options';
 $string['displayselect'] = 'Display';
 $string['displayselectexplain'] = 'Select display type.';
index ffd4287..c37bfe3 100644 (file)
@@ -113,12 +113,16 @@ class mod_quiz_attempts_report_options {
      * @return array URL parameter name => value.
      */
     protected function get_url_params() {
-        return array(
+        $params = array(
             'id'         => $this->cm->id,
             'mode'       => $this->mode,
             'attempts'   => $this->attempts,
             'onlygraded' => $this->onlygraded,
         );
+        if (groups_get_activity_groupmode($this->cm, $this->course)) {
+            $params['group'] = $this->group;
+        }
+        return $params;
     }
 
     /**
index 27b4df5..fe1ee96 100644 (file)
@@ -69,7 +69,7 @@ class quiz_overview_report extends quiz_attempts_report {
         $courseshortname = format_string($course->shortname, true,
                 array('context' => context_course::instance($course->id)));
         $table = new quiz_overview_table($quiz, $this->context, $this->qmsubselect,
-                $options, $groupstudents, $students, $questions, $this->get_base_url());
+                $options, $groupstudents, $students, $questions, $options->get_url());
         $filename = quiz_report_download_filename(get_string('overviewfilename', 'quiz_overview'),
                 $courseshortname, $quiz->name);
         $table->is_downloading($options->download, $filename,
index 4dda344..854246d 100644 (file)
@@ -77,7 +77,7 @@ class quiz_responses_report extends quiz_attempts_report {
         $courseshortname = format_string($course->shortname, true,
                 array('context' => context_course::instance($course->id)));
         $table = new quiz_responses_table($quiz, $this->context, $this->qmsubselect,
-                $options, $groupstudents, $students, $questions, $this->get_base_url());
+                $options, $groupstudents, $students, $questions, $options->get_url());
         $filename = quiz_report_download_filename(get_string('responsesfilename', 'quiz_responses'),
                 $courseshortname, $quiz->name);
         $table->is_downloading($options->download, $filename,
index 3d77819..7cebddc 100644 (file)
@@ -418,8 +418,10 @@ class workshop {
             return array();
         }
 
-        list($sort, $sortparams) = users_order_by_sql('u');
-        $sql .= " ORDER BY $sort";
+        list($sort, $sortparams) = users_order_by_sql('tmp');
+        $sql = "SELECT *
+                  FROM ($sql) tmp
+              ORDER BY $sort";
 
         return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
     }
@@ -467,8 +469,10 @@ class workshop {
             return array();
         }
 
-        list($sort, $sortparams) = users_order_by_sql('u');
-        $sql .= " ORDER BY $sort";
+        list($sort, $sortparams) = users_order_by_sql('tmp');
+        $sql = "SELECT *
+                  FROM ($sql) tmp
+              ORDER BY $sort";
 
         return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
     }
@@ -518,8 +522,10 @@ class workshop {
             return array();
         }
 
-        list($sort, $sortparams) = users_order_by_sql();
-        $sql .= " ORDER BY $sort";
+        list($sort, $sortparams) = users_order_by_sql('tmp');
+        $sql = "SELECT *
+                  FROM ($sql) tmp
+              ORDER BY $sort";
 
         return $DB->get_records_sql($sql, array_merge($params, $sortparams), $limitfrom, $limitnum);
     }
index b6179e9..b41d785 100644 (file)
@@ -31,6 +31,16 @@ if (!defined('MOODLE_INTERNAL')) {
 
 //dummy class - all plugins should be based off this.
 class plagiarism_plugin {
+
+    /**
+     * Return the list of form element names.
+     *
+     * @return array contains the form element names.
+     */
+    public function get_configs() {
+        return array();
+    }
+
     /**
      * hook to allow plagiarism specific information to be displayed beside a submission 
      * @param array  $linkarraycontains all relevant information for the plugin to generate a link
index 32652d1..2fab6f8 100644 (file)
@@ -29,13 +29,20 @@ require_once($CFG->dirroot . '/question/format/blackboard_six/formatbase.php');
 require_once($CFG->dirroot . '/question/format/blackboard_six/formatqti.php');
 require_once($CFG->dirroot . '/question/format/blackboard_six/formatpool.php');
 
+class qformat_blackboard_six_file {
+    /** @var int type of file being imported, one of the constants FILETYPE_QTI or FILETYPE_POOL. */
+    public $filetype;
+    /** @var string the xml text */
+    public $text;
+    /** @var string path to path to root of image tree in unzipped archive. */
+    public $filebase = '';
+}
+
 class qformat_blackboard_six extends qformat_blackboard_six_base {
     /** @var int Blackboard assessment qti files were always imported by the blackboard_six plugin. */
     const FILETYPE_QTI = 1;
     /** @var int Blackboard question pool files were previously handled by the blackboard plugin. */
     const FILETYPE_POOL = 2;
-    /** @var int type of file being imported, one of the constants FILETYPE_QTI or FILETYPE_POOL. */
-    public $filetype;
 
     public function get_filecontent($path) {
         $fullpath = $this->tempdir . '/' . $path;
@@ -45,14 +52,6 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
         return false;
     }
 
-    /**
-     * Set the file type being imported
-     * @param int $type the imported file's type
-     */
-    public function set_filetype($type) {
-        $this->filetype = $type;
-    }
-
     /**
      * Return content of all files containing questions,
      * as an array one element for each file found,
@@ -69,23 +68,26 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
                 $this->error(get_string('filenotreadable', 'error'));
                 return false;
             }
+
+            $fileobj = new qformat_blackboard_six_file();
+
             // As we are not importing a .zip file,
             // there is no imsmanifest, and it is not possible
             // to parse it to find the file type.
             // So we need to guess the file type by looking at the content.
             // For now we will do that searching for a required tag.
             // This is certainly not bullet-proof but works for all usual files.
-            $text = file_get_contents($filename);
+            $fileobj->text = file_get_contents($filename);
             if (strpos($text, '<questestinterop>')) {
-                $this->set_filetype(self::FILETYPE_QTI);
+                $fileobj->filetype = self::FILETYPE_QTI;
             }
             if (strpos($text, '<POOL>')) {
-                $this->set_filetype(self::FILETYPE_POOL);
+                $fileobj->filetype = self::FILETYPE_POOL;
             }
             // In all other cases we are not able to handle this question file.
 
             // Readquestions is now expecting an array of strings.
-            return array($text);
+            return array($fileobj);
         }
         // We are importing a zip file.
         // Create name for temporary directory.
@@ -110,26 +112,30 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
 
                 // We starts from the root element.
                 $query = '//resources/resource';
-                $this->filebase = $this->tempdir;
                 $q_file = array();
 
                 $examfiles = $xpath->query($query);
                 foreach ($examfiles as $examfile) {
+                    $fileobj = new qformat_blackboard_six_file();
+
                     if ($examfile->getAttribute('type') == 'assessment/x-bb-qti-test'
                             || $examfile->getAttribute('type') == 'assessment/x-bb-qti-pool') {
 
                         if ($content = $this->get_filecontent($examfile->getAttribute('bb:file'))) {
-                            $this->set_filetype(self::FILETYPE_QTI);
-                            $q_file[] = $content;
+                            $fileobj->filetype = self::FILETYPE_QTI;
+                            $fileobj->filebase = $this->tempdir;
+                            $fileobj->text = $content;
+                            $q_file[] = $fileobj;
                         }
                     }
                     if ($examfile->getAttribute('type') == 'assessment/x-bb-pool') {
                         if ($examfile->getAttribute('baseurl')) {
-                            $this->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
+                            $fileobj->filebase = $this->tempdir. '/' . $examfile->getAttribute('baseurl');
                         }
                         if ($content = $this->get_filecontent($examfile->getAttribute('file'))) {
-                            $this->set_filetype(self::FILETYPE_POOL);
-                            $q_file[] = $content;
+                            $fileobj->filetype = self::FILETYPE_POOL;
+                            $fileobj->text = $content;
+                            $q_file[] = $fileobj;
                         }
                     }
                 }
@@ -152,32 +158,33 @@ class qformat_blackboard_six extends qformat_blackboard_six_base {
     }
 
     /**
-     * Parse the array of strings into an array of questions.
-     * Each string is the content of a .dat questions file.
+     * Parse the array of objects into an array of questions.
+     * Each object is the content of a .dat questions file.
      * This *could* burn memory - but it won't happen that much
      * so fingers crossed!
-     * @param array of strings from the input file.
-     * @param stdClass $context
+     * @param array $lines array of qformat_blackboard_six_file objects for each input file.
      * @return array (of objects) question objects.
      */
     public function readquestions($lines) {
 
         // Set up array to hold all our questions.
         $questions = array();
-        if ($this->filetype == self::FILETYPE_QTI) {
-            $importer = new qformat_blackboard_six_qti();
-        } else if ($this->filetype == self::FILETYPE_POOL) {
-            $importer = new qformat_blackboard_six_pool();
-        } else {
-            // In all other cases we are not able to import the file.
-            return false;
-        }
-        $importer->set_filebase($this->filebase);
 
-        // Each element of $lines is a string containing a complete xml document.
-        foreach ($lines as $text) {
-                $questions = array_merge($questions, $importer->readquestions($text));
+        // Each element of $lines is a qformat_blackboard_six_file object.
+        foreach ($lines as $fileobj) {
+            if ($fileobj->filetype == self::FILETYPE_QTI) {
+                $importer = new qformat_blackboard_six_qti();
+            } else if ($fileobj->filetype == self::FILETYPE_POOL) {
+                $importer = new qformat_blackboard_six_pool();
+            } else {
+                // In all other cases we are not able to import the file.
+                debugging('fileobj type not recognised', DEBUG_DEVELOPER);
+                continue;
+            }
+            $importer->set_filebase($fileobj->filebase);
+            $questions = array_merge($questions, $importer->readquestions($fileobj->text));
         }
+
         return $questions;
     }
 }
index fdfa5e4..52993d4 100644 (file)
@@ -41,8 +41,10 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
 class qformat_blackboard_six_pool_test extends question_testcase {
 
     public function make_test_xml() {
-        $xml = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_pool.dat');
-        return array(0=>$xml);
+        $xmlfile = new qformat_blackboard_six_file();
+        $xmlfile->filetype = 2;
+        $xmlfile->text = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_pool.dat');
+        return array(0=>$xmlfile);
     }
 
     public function test_import_match() {
@@ -50,7 +52,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
 
         $q = $questions[4];
@@ -87,7 +88,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
         $q = $questions[1];
 
@@ -147,7 +147,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
         $q = $questions[2];
 
@@ -221,7 +220,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
         $q = $questions[0];
 
@@ -251,7 +249,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
         $q = $questions[3];
 
@@ -286,7 +283,6 @@ class qformat_blackboard_six_pool_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(2);
         $questions = $importer->readquestions($xml);
         $q = $questions[5];
 
index 9d80e9b..f7627c0 100644 (file)
@@ -41,14 +41,15 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
 class qformat_blackboard_six_qti_test extends question_testcase {
 
     public function make_test_xml() {
-        $xml = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_qti.dat');
-        return array(0=>$xml);
+        $xmlfile = new qformat_blackboard_six_file();
+        $xmlfile->filetype = 1;
+        $xmlfile->text = file_get_contents(__DIR__ . '/fixtures/sample_blackboard_qti.dat');
+        return array(0=>$xmlfile);
     }
     public function test_import_match() {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[3];
 
@@ -83,7 +84,6 @@ class qformat_blackboard_six_qti_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[1];
 
@@ -143,7 +143,6 @@ class qformat_blackboard_six_qti_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[2];
 
@@ -220,7 +219,6 @@ class qformat_blackboard_six_qti_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[0];
 
@@ -251,7 +249,6 @@ class qformat_blackboard_six_qti_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[4];
 
@@ -286,7 +283,6 @@ class qformat_blackboard_six_qti_test extends question_testcase {
         $xml = $this->make_test_xml();
 
         $importer = new qformat_blackboard_six();
-        $importer->set_filetype(1);
         $questions = $importer->readquestions($xml);
         $q = $questions[5];
 
index aa2156a..a4bb3ef 100644 (file)
@@ -218,8 +218,8 @@ class question_dataset_dependent_items_form extends question_wizard_form {
         for ($i=10; $i<=100; $i+=10) {
              $showoptions["$i"]="$i";
         }
-        $mform->addElement('header', 'additemhdr', get_string('add', 'moodle'));
-        $mform->closeHeaderBefore('additemhdr');
+        $mform->addElement('header', 'addhdr', get_string('add', 'moodle'));
+        $mform->closeHeaderBefore('addhdr');
 
         if ($this->qtypeobj->supports_dataset_item_generation()) {
             $radiogrp = array();
@@ -244,7 +244,7 @@ class question_dataset_dependent_items_form extends question_wizard_form {
         $mform->addGroup($addgrp, 'addgrp', get_string('additem', 'qtype_calculated'), ' ', false);
         $mform->addElement('static', "divideradd", '', '');
         if ($this->noofitems > 0) {
-            $mform->addElement('header', 'additemhdr', get_string('delete', 'moodle'));
+            $mform->addElement('header', 'deleteitemhdr', get_string('delete', 'moodle'));
             $deletegrp = array();
             $deletegrp[] = $mform->createElement('submit', 'deletebutton',
                     get_string('delete', 'moodle'));
@@ -272,7 +272,7 @@ class question_dataset_dependent_items_form extends question_wizard_form {
         $k = optional_param('selectshow', 1, PARAM_INT);
         for ($i = $this->noofitems; $i >= 1; $i--) {
             if ($k > 0) {
-                $mform->addElement('header', '', "<b>" .
+                $mform->addElement('header', 'setnoheader' . $i, "<b>" .
                         get_string('setno', 'qtype_calculated', $i)."</b>&nbsp;&nbsp;");
             }
             foreach ($this->datasetdefs as $defkey => $datasetdef) {
index 94671d5..80b01a0 100644 (file)
@@ -587,7 +587,7 @@ class qtype_calculated extends question_type {
      */
     public function save_question($question, $form) {
         global $DB;
-        if ($this->wizardpagesnumber() == 1) {
+        if ($this->wizardpagesnumber() == 1 || $question->qtype == 'calculatedsimple') {
                 $question = parent::save_question($question, $form);
             return $question;
         }
@@ -1633,9 +1633,12 @@ class qtype_calculated extends question_type {
                                FROM {question} q
                               WHERE q.id = ?";
                     if (!isset ($datasetdefs["$r->type-$r->category-$r->name"])) {
-                        $datasetdefs["$r->type-$r->category-$r->name"]= $r;
+                        $datasetdefs["$r->type-$r->category-$r->name"] = $r;
                     }
                     if ($questionb = $DB->get_records_sql($sql1, array($r->question))) {
+                        if (!isset ($datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question])) {
+                            $datasetdefs["$r->type-$r->category-$r->name"]->questions[$r->question] = new stdClass();
+                        }
                         $datasetdefs["$r->type-$r->category-$r->name"]->questions[
                                 $r->question]->name = $questionb[$r->question]->name;
                     }
index 9690d3d..22e37f0 100644 (file)
@@ -673,7 +673,7 @@ abstract class question_edit_form extends question_wizard_form {
         }
 
         // Default mark.
-        if ($fromform['defaultmark'] < 0) {
+        if (array_key_exists('defaultmark', $fromform) && $fromform['defaultmark'] < 0) {
             $errors['defaultmark'] = get_string('defaultmarkmustbepositive', 'question');
         }
 
index a959080..d67d0fc 100644 (file)
@@ -189,9 +189,9 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
         }
 
         // Work out a good input field size.
-        $size = max(1, strlen(trim($response)) + 1);
+        $size = max(1, textlib::strlen(trim($response)) + 1);
         foreach ($subq->answers as $ans) {
-            $size = max($size, strlen(trim($ans->answer)));
+            $size = max($size, textlib::strlen(trim($ans->answer)));
         }
         $size = min(60, round($size + rand(0, $size*0.15)));
         // The rand bit is to make guessing harder.
index ba1dfc7..07b1ab0 100644 (file)
@@ -117,7 +117,7 @@ class qtype_shortanswer_question extends question_graded_by_strategy
      * @return string the normalised string.
      */
     protected static function safe_normalize($string) {
-        if (!$string) {
+        if ($string === '') {
             return '';
         }
 
@@ -126,7 +126,7 @@ class qtype_shortanswer_question extends question_graded_by_strategy
         }
 
         $normalised = normalizer_normalize($string, Normalizer::FORM_C);
-        if (!$normalised) {
+        if (is_null($normalised)) {
             // An error occurred in normalizer_normalize, but we have no idea what.
             debugging('Failed to normalise string: ' . $string, DEBUG_DEVELOPER);
             return $string; // Return the original string, since it is the best we have.
index d287ff4..5dc15ee 100644 (file)
@@ -120,6 +120,18 @@ class qtype_shortanswer_question_test extends advanced_testcase {
         }
     }
 
+    public function test_compare_0_with_wildcard() {
+        // Test the classic PHP problem case with '0'.
+        $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                '0', '0', false));
+        $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                '0', '0*', false));
+        $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                '0', '*0', false));
+        $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                '0', '*0*', false));
+    }
+
     public function test_is_complete_response() {
         $question = test_question_maker::make_question('shortanswer');
 
index b54806b..94b575b 100644 (file)
@@ -291,7 +291,7 @@ class repository_equella extends repository {
         );
         $mform->addElement('select', 'equella_select_restriction', get_string('selectrestriction', 'repository_equella'), $choices);
 
-        $mform->addElement('header', '',
+        $mform->addElement('header', 'groupheader',
             get_string('group', 'repository_equella', get_string('groupdefault', 'repository_equella')));
         $mform->addElement('text', 'equella_shareid', get_string('sharedid', 'repository_equella'));
         $mform->setType('equella_shareid', PARAM_RAW);
@@ -302,7 +302,8 @@ class repository_equella extends repository {
         $mform->addRule('equella_sharedsecret', $strrequired, 'required', null, 'client');
 
         foreach (self::get_all_editing_roles() as $role) {
-            $mform->addElement('header', '', get_string('group', 'repository_equella', format_string($role->name)));
+            $mform->addElement('header', 'groupheader_'.$role->shortname, get_string('group', 'repository_equella',
+                format_string($role->name)));
             $mform->addElement('text', "equella_{$role->shortname}_shareid", get_string('sharedid', 'repository_equella'));
             $mform->setType("equella_{$role->shortname}_shareid", PARAM_RAW);
             $mform->addElement('text', "equella_{$role->shortname}_sharedsecret",
index 69d7db8..65619ce 100644 (file)
@@ -649,8 +649,11 @@ abstract class repository {
     public final function check_capability() {
         global $USER;
 
+        // The context we are on.
+        $currentcontext = $this->context;
+
         // Ensure that the user can view the repository in the current context.
-        $can = has_capability('repository/'.$this->get_typename().':view', $this->context);
+        $can = has_capability('repository/'.$this->get_typename().':view', $currentcontext);
 
         // Context in which the repository has been created.
         $repocontext = context::instance_by_id($this->instance->contextid);
@@ -662,14 +665,29 @@ abstract class repository {
             }
         }
 
-        // Ensure that the user can view the repository in the context of the repository.
-        // We need to perform the check when already disallowed.
+        // We are going to ensure that the current context was legit, and reliable to check
+        // the capability against. (No need to do that if we already cannot).
         if ($can) {
-            if ($repocontext->contextlevel == CONTEXT_USER && $repocontext->instanceid != $USER->id) {
-                // Prevent URL hijack to access someone else's repository.
-                $can = false;
+            if ($repocontext->contextlevel == CONTEXT_USER) {
+                // The repository is a user instance, ensure we're the right user to access it!
+                if ($repocontext->instanceid != $USER->id) {
+                    $can = false;
+                }
+            } else if ($repocontext->contextlevel == CONTEXT_COURSE) {
+                // The repository is a course one. Let's check that we are on the right course.
+                if (in_array($currentcontext->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK))) {
+                    $coursecontext = $currentcontext->get_course_context();
+                    if ($coursecontext->instanceid != $repocontext->instanceid) {
+                        $can = false;
+                    }
+                } else {
+                    // We are on a parent context, therefore it's legit to check the permissions
+                    // in the current context.
+                }
             } else {
-                $can = has_capability('repository/'.$this->get_typename().':view', $repocontext);
+                // Nothing to check here, system instances can have different permissions on different
+                // levels. We do not want to prevent URL hack here, because it does not make sense to
+                // prevent a user to access a repository in a context if it's accessible in another one.
             }
         }
 
index cda4b3c..778c8a7 100644 (file)
@@ -96,7 +96,7 @@ if ($context->contextlevel == CONTEXT_COURSE) {
 }
 
 /// Security: we cannot perform any action if the type is not visible or if the context has been disabled
-if (!empty($new)){
+if (!empty($new) && empty($edit)){
     $type = repository::get_type_by_typename($new);
 } else if (!empty($edit)){
     $instance = repository::get_instance($edit);
index f679c78..d2b1dec 100644 (file)
@@ -57,7 +57,6 @@ require_login($course, false, $cm);
 $PAGE->set_context($context);
 
 echo $OUTPUT->header(); // send headers
-@header('Content-type: text/html; charset=utf-8');
 
 // If uploaded file is larger than post_max_size (php.ini) setting, $_POST content will be empty.
 if (empty($_POST) && !empty($action)) {
index 6d50030..fe84633 100644 (file)
@@ -231,7 +231,7 @@ a.skip:active {position: static;display: block;}
  * Mforms
  */
 .mform fieldset {border:1px solid;}
-.mform fieldset fieldset {border-width:0px;}
+.mform fieldset fieldset {border-width:0;}
 .mform fieldset legend {font-weight:bold;margin-left:0.5em;}
 .mform fieldset div {margin:10px;margin-top:0;}
 .mform fieldset div div {margin:0;}
@@ -239,16 +239,16 @@ a.skip:active {position: static;display: block;}
 .mform fieldset.hidden {border-width:0;}
 .mform fieldset.group {margin-bottom: 0}
 .mform fieldset.error {border: 1px solid #A00;}
-.mform fieldset.collapsible legend a.fheader {padding: 0 5px 0 20px; background: url([[pix:t/expanded]]) 2px center no-repeat;}
-.mform fieldset.collapsed legend a.fheader {background: url([[pix:t/collapsed]]) 2px center no-repeat;}
-.mform fieldset.collapsed.jsprocessed {border-width: 1px 0 0 0; padding: 0;}
-.mform fieldset.collapsed.jsprocessed div.fcontainer {display: none;}
+.mform fieldset legend {padding: 0 0.35em;}
+.mform fieldset.collapsible legend a.fheader {padding-left: 18px; background: url([[pix:t/expanded]]) left center no-repeat;}
+.mform fieldset.collapsed legend a.fheader {background-image: url([[pix:t/collapsed]]);}
+.jsenabled .mform fieldset.collapsed {border-width: 1px 0 0 1px; padding: 0; border-color: transparent;}
+.jsenabled .mform fieldset.collapsed div.fcontainer {display: none;}
 .mform .fitem {width:100%;overflow:hidden;margin-top:5px;margin-bottom:1px;clear:right;}
 .mform .fitem .fitemtitle {width:15%;text-align:right;float:left;}
 .mform .fitem .fitemtitle div {display: inline;}
 .mform .fitem .felement {border-width: 0;width:80%;margin-left:16%;}
 .mform .fitem fieldset.felement {margin-left:15%;padding-left:1%;margin-bottom:0}
-.dir-rtl .mform .fitem fieldset.felement {padding-right: 1%;margin-right: 15%;}
 .mform .error,
 .mform .required {color:#A00;}
 .mform .required .fgroup span label {color:#000;}
@@ -274,6 +274,12 @@ a.skip:active {position: static;display: block;}
 .dir-rtl .urlselect label,
 .dir-rtl .singleselect label { margin-left: .3em; margin-right: 0; }
 
+.dir-rtl .mform fieldset legend {margin-right:0.5em; margin-left: 0;}
+.dir-rtl .mform fieldset.collapsible legend a.fheader {background-position: right center; padding-right: 18px; padding-left: 0;}
+.dir-rtl .mform fieldset.collapsed legend a.fheader {background-image: url([[pix:t/collapsed_rtl]]); }
+.dir-rtl.jsenabled .mform fieldset.collapsed {border-width: 1px 1px 0 0; }
+.dir-rtl .mform .fitem fieldset.felement {padding-right: 1%;margin-right: 15%;}
+
 input#id_externalurl {direction:ltr;}
 
 /** Browser corrections for mforms **/
index 6e75f60..86551c6 100644 (file)
@@ -419,7 +419,7 @@ class theme_mymobile_core_renderer extends core_renderer {
             } else if (is_role_switched($course->id)) { // Has switched roles
                 $rolename = '';
                 if ($role = $DB->get_record('role', array('id'=>$USER->access['rsw'][$context->path]))) {
-                    $rolename = ': '.format_string($role->name);
+                    $rolename = ': '.role_get_name($role, $context);
                 }
                 $loggedinas = get_string('loggedinas', 'moodle', $username).$rolename." (<a href=\"$CFG->wwwroot/course/view.php?id=$course->id&amp;switchrole=0&amp;sesskey=".sesskey()."\">".get_string('switchrolereturn').'</a>)';
             } else {
index cd0098f..441d0b0 100644 (file)
@@ -5,6 +5,8 @@ information provided here is intended especially for theme designer.
 
 DOM changes:
 * changed the h1 title in the help popup to a h2.
+* new setting $THEME->yuicssmodules = array('cssreset', 'cssfonts', 'cssgrids', 'cssbase'); which
+  allows themes to use different CSS reset normalisers such as cssnormalize YUI module
 
 === 2.4 ===
 
index 4c136b6..36f2694 100644 (file)
@@ -83,6 +83,11 @@ foreach ($parts as $part) {
     //debug($bits);
     $version = array_shift($bits);
     if ($version === 'moodle') {
+        if (count($bits) <= 3) {
+            // This is an invalid module load attempt.
+            $content .= "\n// Incorrect moodle module inclusion. Not enough component information in {$part}.\n";
+            continue;
+        }
         if (!defined('ABORT_AFTER_CONFIG_CANCEL')) {
             define('ABORT_AFTER_CONFIG_CANCEL', true);
             define('NO_UPGRADE_CHECK', true);
index f1d9533..18e9dfc 100644 (file)
@@ -70,13 +70,15 @@ abstract class user_selector_base {
     protected static $jsmodule = array(
                 'name' => 'user_selector',
                 'fullpath' => '/user/selector/module.js',
-                'requires'  => array('node', 'event-custom', 'datasource', 'json'),
+                'requires'  => array('node', 'event-custom', 'datasource', 'json', 'moodle-core-notification'),
                 'strings' => array(
                     array('previouslyselectedusers', 'moodle', '%%SEARCHTERM%%'),
                     array('nomatchingusers', 'moodle', '%%SEARCHTERM%%'),
                     array('none', 'moodle')
                 ));
 
+    /** @var int this is used to define maximum number of users visible in list */
+    public $maxusersperpage = 100;
 
     // Public API ==============================================================
 
@@ -120,6 +122,10 @@ abstract class user_selector_base {
         $this->preserveselected = $this->initialise_option('userselector_preserveselected', $this->preserveselected);
         $this->autoselectunique = $this->initialise_option('userselector_autoselectunique', $this->autoselectunique);
         $this->searchanywhere = $this->initialise_option('userselector_searchanywhere', $this->searchanywhere);
+
+        if (!empty($CFG->maxusersperpage)) {
+            $this->maxusersperpage = $CFG->maxusersperpage;
+        }
     }
 
     /**
@@ -751,8 +757,6 @@ class group_members_selector extends groups_user_selector_base {
  * Used on the add group members page.
  */
 class group_non_members_selector extends groups_user_selector_base {
-    const MAX_USERS_PER_PAGE = 100;
-
     /**
      * An array of user ids populated by find_users() used in print_user_summaries()
      */
@@ -860,7 +864,7 @@ class group_non_members_selector extends groups_user_selector_base {
 
         if (!$this->is_validating()) {
             $potentialmemberscount = $DB->count_records_sql("SELECT COUNT(DISTINCT u.id) $sql", $params);
-            if ($potentialmemberscount > group_non_members_selector::MAX_USERS_PER_PAGE) {
+            if ($potentialmemberscount > $this->maxusersperpage) {
                 return $this->too_many_results($search, $potentialmemberscount);
             }
         }
index 24daf82..ca89434 100644 (file)
@@ -151,8 +151,7 @@ M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearc
                 method: 'POST',
                 data: 'selectorid='+hash+'&sesskey='+M.cfg.sesskey+'&search='+value + '&userselector_searchanywhere=' + this.get_option('searchanywhere'),
                 on: {
-                    success:this.handle_response,
-                    failure:this.handle_failure
+                    complete: this.handle_response
                 },
                 context:this
             });
@@ -175,26 +174,15 @@ M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearc
                 }
                 this.listbox.setStyle('background','');
                 var data = Y.JSON.parse(response.responseText);
+                if (data.error) {
+                    this.searchfield.addClass('error');
+                    return new M.core.ajaxException(data);
+                }
                 this.output_options(data);
             } catch (e) {
-                this.handle_failure(requestid);
-            }
-        },
-        /**
-         * Handles what happens when the ajax request fails.
-         */
-        handle_failure : function(requestid) {
-            delete this.iotransactions[requestid];
-            if (!Y.Object.isEmpty(this.iotransactions)) {
-                // More searches pending. Wait until they are all done.
-                return;
-            }
-            this.listbox.setStyle('background','');
-            this.searchfield.addClass('error');
-
-            // If we are in developer debug mode, output a link to help debug the failure.
-            if (M.cfg.developerdebug) {
-                this.searchfield.insert(Y.Node.create('<a href="'+M.cfg.wwwroot +'/user/selector/search.php?selectorid='+hash+'&sesskey='+M.cfg.sesskey+'&search='+this.get_search_text()+'&debug=1">Ajax call failed. Click here to try the search call directly.</a>'));
+                this.listbox.setStyle('background','');
+                this.searchfield.addClass('error');
+                return new M.core.exception(e);
             }
         },
         /**
@@ -379,4 +367,4 @@ M.core_user.init_user_selector_options_tracker = function(Y) {
     user_selector_options_tracker.init();
     // Return it just incase it is ever wanted
     return user_selector_options_tracker;
-};
\ No newline at end of file
+};
index a7b8d9c..22826a6 100644 (file)
  * @package user
  */
 
+define('AJAX_SCRIPT', true);
+
 require_once(dirname(__FILE__) . '/../../config.php');
 require_once($CFG->dirroot . '/user/selector/lib.php');
 
 $PAGE->set_context(context_system::instance());
 $PAGE->set_url('/user/selector/search.php');
 
-// In developer debug mode, when there is a debug=1 in the URL send as plain text
-// for easier debugging.
-if (debugging('', DEBUG_DEVELOPER) && optional_param('debug', false, PARAM_BOOL)) {
-    header('Content-type: text/plain; charset=UTF-8');
-    $debugmode = true;
-} else {
-    header('Content-type: application/json; charset=utf-8');
-    $debugmode = false;
-}
+echo $OUTPUT->header();
 
 // Check access.
-if (!isloggedin()) {;
-    print_error('mustbeloggedin');
-}
-if (!confirm_sesskey()) {
-    print_error('invalidsesskey');
-}
+require_login();
+require_sesskey();
 
 // Get the search parameter.
 $search = required_param('search', PARAM_RAW);
@@ -59,13 +49,6 @@ if (!isset($USER->userselectors[$selectorhash])) {
 // Get the options.
 $options = $USER->userselectors[$selectorhash];
 
-if ($debugmode) {
-    echo 'Search string: ', $search, "\n";
-    echo 'Options: ';
-    print_r($options);
-    echo "\n";
-}
-
 // Create the appropriate userselector.
 $classname = $options['class'];
 unset($options['class']);
index dfd2ffd..019a237 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2013031400.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2013031400.05;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes