Merge branch 'w02_MDL-29684_m23_metaenrol' of git://github.com/skodak/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 11 Jan 2012 22:48:06 +0000 (23:48 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Wed, 11 Jan 2012 22:48:06 +0000 (23:48 +0100)
74 files changed:
admin/cli/install.php
admin/search.php
admin/settings/appearance.php
admin/tool/uploaduser/index.php
admin/tool/uploaduser/lang/en/tool_uploaduser.php
admin/tool/uploaduser/locallib.php
admin/tool/uploaduser/user_form.php
auth/db/auth.php
backup/cc/cc112moodle.php
backup/cc/cc2moodle.php
backup/cc/cc_lib/cc_asssesment.php
backup/cc/entity.resource.class.php
backup/cc/sheets/course_sections_section_mods_mod.xml
enrol/cohort/addinstance_form.php
enrol/cohort/cli/sync.php [new file with mode: 0644]
enrol/cohort/db/access.php
enrol/cohort/db/uninstall.php [new file with mode: 0644]
enrol/cohort/lang/en/enrol_cohort.php
enrol/cohort/lib.php
enrol/cohort/locallib.php
enrol/cohort/settings.php
enrol/cohort/version.php
lang/en/admin.php
lang/en/langconfig.php
lang/en/moodle.php
lib/accesslib.php
lib/cronlib.php
lib/datalib.php
lib/form/dndupload.js [new file with mode: 0644]
lib/form/filemanager.js
lib/form/filemanager.php
lib/form/filepicker.js
lib/form/filepicker.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/simpletest/testweblib.php
lib/upgradelib.php
lib/weblib.php
mod/quiz/accessrule/upgrade.txt [new file with mode: 0644]
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/lib.php
mod/quiz/renderer.php
mod/quiz/report/statistics/cron.php [deleted file]
mod/quiz/report/statistics/db/install.php
mod/quiz/report/statistics/lib.php
mod/quiz/report/statistics/version.php
mod/quiz/report/upgrade.txt [new file with mode: 0644]
mod/quiz/version.php
mod/scorm/report/basic/report.php
mod/scorm/report/interactions/report.php
question/behaviour/adaptive/behaviour.php
question/behaviour/adaptive/lang/en/qbehaviour_adaptive.php
question/behaviour/adaptive/renderer.php
question/behaviour/adaptive/simpletest/testwalkthrough.php
question/behaviour/adaptivenopenalty/renderer.php
question/behaviour/adaptivenopenalty/simpletest/testwalkthrough.php
question/behaviour/upgrade.txt
question/editlib.php
question/format/upgrade.txt
question/type/match/renderer.php
question/type/upgrade.txt
repository/coursefiles/db/access.php
repository/coursefiles/version.php
repository/filesystem/db/access.php
repository/filesystem/version.php
repository/lib.php
repository/local/db/access.php
repository/local/version.php
repository/webdav/db/access.php
repository/webdav/version.php
theme/base/style/core.css

index 04d1ee8..facda35 100644 (file)
@@ -78,6 +78,16 @@ Example:
 "; //TODO: localize, mark as needed in install - to be translated later when everything is finished
 
 
+// distro specific customisation
+$distrolibfile = dirname(dirname(dirname(__FILE__))).'/install/distrolib.php';
+$distro = null;
+if (file_exists($distrolibfile)) {
+    require_once($distrolibfile);
+    if (function_exists('distro_get_config')) {
+        $distro = distro_get_config();
+    }
+}
+
 // Nothing to do if config.php exists
 $configfile = dirname(dirname(dirname(__FILE__))).'/config.php';
 if (file_exists($configfile)) {
@@ -180,14 +190,14 @@ if (empty($databases)) {
 // now get cli options
 list($options, $unrecognized) = cli_get_params(
     array(
-        'chmod'             => '2777',
+        'chmod'             => isset($distro->directorypermissions) ? sprintf('%04o',$distro->directorypermissions) : '2777', // let distros set dir permissions
         'lang'              => $CFG->lang,
         'wwwroot'           => '',
-        'dataroot'          => str_replace('\\', '/', dirname(dirname(dirname(dirname(__FILE__)))).'/moodledata'),
-        'dbtype'            => $defaultdb,
-        'dbhost'            => 'localhost',
+        'dataroot'          => empty($distro->dataroot) ? str_replace('\\', '/', dirname(dirname(dirname(dirname(__FILE__)))).'/moodledata'): $distro->dataroot, // initialised later after including libs or by distro
+        'dbtype'            => empty($distro->dbtype) ? $defaultdb : $distro->dbtype, // let distro skip dbtype selection
+        'dbhost'            => empty($distro->dbhost) ? 'localhost' : $distro->dbhost, // let distros set dbhost
         'dbname'            => 'moodle',
-        'dbuser'            => 'root',
+        'dbuser'            => empty($distro->dbuser) ? 'root' : $distro->dbuser, // let distros set dbuser
         'dbpass'            => '',
         'dbsocket'          => false,
         'prefix'            => 'mdl_',
@@ -523,6 +533,9 @@ if ($interactive) {
         }
 
         $CFG->dbpass = cli_input($prompt, $options['dbpass']);
+        if (function_exists('distro_pre_create_db')) { // Hook for distros needing to do something before DB creation
+            $distro = distro_pre_create_db($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbsocket'=>$options['dbsocket']), $distro);
+        }
         $hint_database = install_db_validate($database, $CFG->dbhost, $CFG->dbuser, $CFG->dbpass, $CFG->dbname, $CFG->prefix, array('dbpersist'=>0, 'dbsocket'=>$options['dbsocket']));
     } while ($hint_database !== '');
 
index 55d93c0..40fbeb4 100644 (file)
@@ -8,7 +8,8 @@ require_once($CFG->libdir.'/adminlib.php');
 $query = trim(optional_param('query', '', PARAM_NOTAGS));  // Search string
 
 $PAGE->set_context(get_context_instance(CONTEXT_SYSTEM));
-$PAGE->set_course($SITE);
+
+admin_externalpage_setup('search', '', array('query' => $query)); // now hidden page
 
 $adminroot = admin_get_root(); // need all settings here
 $adminroot->search = $query; // So we can reference it in search boxes later in this invocation
@@ -16,8 +17,6 @@ $statusmsg = '';
 $errormsg  = '';
 $focus = '';
 
-admin_externalpage_setup('search', '', array('query' => $query)); // now hidden page
-
 // now we'll deal with the case that the admin has submitted the form with changed settings
 if ($data = data_submitted() and confirm_sesskey()) {
     if (admin_write_settings($data)) {
index 7bc3b58..9c86f4f 100644 (file)
@@ -102,6 +102,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('navlinkcoursesections', new lang_string('navlinkcoursesections', 'admin'), new lang_string('navlinkcoursesections_help', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('usesitenameforsitepages', new lang_string('usesitenameforsitepages', 'admin'), new lang_string('configusesitenameforsitepages', 'admin'), 0));
     $temp->add(new admin_setting_configcheckbox('linkadmincategories', new lang_string('linkadmincategories', 'admin'), new lang_string('linkadmincategories_help', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('navshowfrontpagemods', new lang_string('navshowfrontpagemods', 'admin'), new lang_string('navshowfrontpagemods_help', 'admin'), 1));
     $temp->add(new admin_setting_configcheckbox('navadduserpostslinks', new lang_string('navadduserpostslinks', 'admin'), new lang_string('navadduserpostslinks_help', 'admin'), 1));
 
     $ADMIN->add('appearance', $temp);
index 44a0fe8..a0a69b0 100644 (file)
@@ -73,6 +73,10 @@ $stremailduplicate          = get_string('useremailduplicate', 'error');
 $strinvalidpasswordpolicy   = get_string('invalidpasswordpolicy', 'error');
 $errorstr                   = get_string('error');
 
+$stryes                     = get_string('yes');
+$strno                      = get_string('no');
+$stryesnooptions = array(0=>$strno, 1=>$stryes);
+
 $returnurl = new moodle_url('/admin/tool/uploaduser/index.php');
 $bulknurl  = new moodle_url('/admin/user/user_bulk.php');
 
@@ -88,6 +92,7 @@ $STD_FIELDS = array('id', 'firstname', 'lastname', 'username', 'email',
         'url', 'description', 'descriptionformat', 'password',
         'auth',        // watch out when changing auth type or using external auth plugins!
         'oldusername', // use when renaming users - this is the original username
+        'suspended',   // 1 means suspend user account, 0 means activate user account, nothing means keep as is for existing users
         'deleted',     // 1 means delete user
     );
 
@@ -154,6 +159,7 @@ if ($formdata = $mform2->is_cancelled()) {
     $updatepasswords   = (!empty($formdata->uupasswordold)  and $optype != UU_USER_ADDNEW and $optype != UU_USER_ADDINC and ($updatetype == UU_UPDATE_FILEOVERRIDE or $updatetype == UU_UPDATE_ALLOVERRIDE));
     $allowrenames      = (!empty($formdata->uuallowrenames) and $optype != UU_USER_ADDNEW and $optype != UU_USER_ADDINC);
     $allowdeletes      = (!empty($formdata->uuallowdeletes) and $optype != UU_USER_ADDNEW and $optype != UU_USER_ADDINC);
+    $allowsuspends     = (!empty($formdata->uuallowsuspends));
     $bulk              = $formdata->uubulk;
     $noemailduplicates = $formdata->uunoemailduplicates;
     $standardusernames = $formdata->uustandardusernames;
@@ -446,6 +452,7 @@ if ($formdata = $mform2->is_cancelled()) {
             $user->id = $existinguser->id;
 
             $upt->track('username', html_writer::link(new moodle_url('/user/profile.php', array('id'=>$existinguser->id)), s($existinguser->username)), 'normal', false);
+            $upt->track('suspended', $stryesnooptions[$existinguser->suspended] , 'normal', false);
 
             if (is_siteadmin($user->id)) {
                 $upt->track('status', $strusernotupdatedadmin, 'error');
@@ -462,6 +469,7 @@ if ($formdata = $mform2->is_cancelled()) {
             $upt->track('auth', $existinguser->auth, 'normal', false);
 
             $doupdate = false;
+            $dologout = false;
 
             if ($updatetype != UU_UPDATE_NOCHANGES) {
                 if (!empty($user->auth) and $user->auth !== $existinguser->auth) {
@@ -471,10 +479,13 @@ if ($formdata = $mform2->is_cancelled()) {
                         $upt->track('auth', $struserauthunsupported, 'warning');
                     }
                     $doupdate = true;
+                    if ($existinguser->auth === 'nologin') {
+                        $dologout = true;
+                    }
                 }
                 $allcolumns = array_merge($STD_FIELDS, $PRF_FIELDS);
                 foreach ($allcolumns as $column) {
-                    if ($column === 'username' or $column === 'password' or $column === 'auth') {
+                    if ($column === 'username' or $column === 'password' or $column === 'auth' or $column === 'suspended') {
                         // these can not be changed here
                         continue;
                     }
@@ -531,6 +542,20 @@ if ($formdata = $mform2->is_cancelled()) {
             }
             $isinternalauth = $auth->is_internal();
 
+            // deal with suspending and activating of accounts
+            if ($allowsuspends and isset($user->suspended) and $user->suspended !== '') {
+                $user->suspended = $user->suspended ? 1 : 0;
+                if ($existinguser->suspended != $user->suspended) {
+                    $upt->track('suspended', '', 'normal', false);
+                    $upt->track('suspended', $stryesnooptions[$existinguser->suspended].'-->'.$stryesnooptions[$user->suspended], 'info', false);
+                    $existinguser->suspended = $user->suspended;
+                    $doupdate = true;
+                    if ($existinguser->suspended) {
+                        $dologout = true;
+                    }
+                }
+            }
+
             // changing of passwords is a special case
             // do not force password changes for external auth plugins!
             $oldpw = $existinguser->password;
@@ -593,6 +618,10 @@ if ($formdata = $mform2->is_cancelled()) {
                 }
             }
 
+            if ($dologout) {
+                session_kill_user($existinguser->id);
+            }
+
         } else {
             // save the new user to the database
             $user->confirmed    = 1;
@@ -600,6 +629,13 @@ if ($formdata = $mform2->is_cancelled()) {
             $user->timecreated  = time();
             $user->mnethostid   = $CFG->mnet_localhost_id; // we support ONLY local accounts here, sorry
 
+            if (!isset($user->suspended) or $user->suspended === '') {
+                $user->suspended = 0;
+            } else {
+                $user->suspended = $user->suspended ? 1 : 0;
+            }
+            $upt->track('suspended', $stryesnooptions[$user->suspended], 'normal', false);
+
             if (empty($user->auth)) {
                 $user->auth = 'manual';
             }
index 90713c2..74bb55e 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['allowdeletes'] = 'Allow deletes';
 $string['allowrenames'] = 'Allow renames';
+$string['allowsuspends'] = 'Allow suspending and activating of accounts';
 $string['csvdelimiter'] = 'CSV delimiter';
 $string['defaultvalues'] = 'Default values';
 $string['deleteerrors'] = 'Delete errors';
index 0917c2e..83b648c 100644 (file)
@@ -56,7 +56,7 @@ define('UU_PWRESET_ALL', 2);
  */
 class uu_progress_tracker {
     private $_row;
-    public $columns = array('status', 'line', 'id', 'username', 'firstname', 'lastname', 'email', 'password', 'auth', 'enrolments', 'deleted');
+    public $columns = array('status', 'line', 'id', 'username', 'firstname', 'lastname', 'email', 'password', 'auth', 'enrolments', 'suspended', 'deleted');
 
     /**
      * Print table header.
@@ -76,6 +76,7 @@ class uu_progress_tracker {
         echo '<th class="header c'.$ci++.'" scope="col">'.get_string('password').'</th>';
         echo '<th class="header c'.$ci++.'" scope="col">'.get_string('authentication').'</th>';
         echo '<th class="header c'.$ci++.'" scope="col">'.get_string('enrolments', 'enrol').'</th>';
+        echo '<th class="header c'.$ci++.'" scope="col">'.get_string('suspended', 'auth').'</th>';
         echo '<th class="header c'.$ci++.'" scope="col">'.get_string('delete').'</th>';
         echo '</tr>';
         $this->_row = null;
index 6faa4dd..33b1119 100644 (file)
@@ -134,6 +134,11 @@ class admin_uploaduser_form2 extends moodleform {
         $mform->disabledIf('uuallowdeletes', 'uutype', 'eq', UU_USER_ADDNEW);
         $mform->disabledIf('uuallowdeletes', 'uutype', 'eq', UU_USER_ADDINC);
 
+        $mform->addElement('selectyesno', 'uuallowsuspends', get_string('allowsuspends', 'tool_uploaduser'));
+        $mform->setDefault('uuallowsuspends', 1);
+        $mform->disabledIf('uuallowsuspends', 'uutype', 'eq', UU_USER_ADDNEW);
+        $mform->disabledIf('uuallowsuspends', 'uutype', 'eq', UU_USER_ADDINC);
+
         $mform->addElement('selectyesno', 'uunoemailduplicates', get_string('uunoemailduplicates', 'tool_uploaduser'));
         $mform->setDefault('uunoemailduplicates', 1);
 
index f7773de..5709513 100644 (file)
@@ -255,6 +255,7 @@ class auth_plugin_db extends auth_plugin_base {
                         $updateuser = new stdClass();
                         $updateuser->id   = $user->id;
                         $updateuser->auth = 'nologin';
+                        $updateuser->timemodified = time();
                         $DB->update_record('user', $updateuser);
                         if ($verbose) {
                             mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
@@ -352,7 +353,6 @@ class auth_plugin_db extends auth_plugin_base {
 
                 // prep a few params
                 $user->username   = $username;
-                $user->modified   = time();
                 $user->confirmed  = 1;
                 $user->auth       = $this->authtype;
                 $user->mnethostid = $CFG->mnet_localhost_id;
@@ -361,14 +361,17 @@ class auth_plugin_db extends auth_plugin_base {
                 }
 
                 // maybe the user has been deleted before
-                if ($old_user = $DB->get_record('user', array('username'=>$user->username, 'deleted'=>1, 'mnethostid'=>$user->mnethostid))) {
-                    $user->id = $old_user->id;
-                    $DB->set_field('user', 'deleted', 0, array('username'=>$user->username));
+                if ($old_user = $DB->get_record('user', array('username'=>$user->username, 'deleted'=>1, 'mnethostid'=>$user->mnethostid, 'auth'=>$user->auth))) {
+                    // note: this undeleting is deprecated and will be eliminated soon
+                    $DB->set_field('user', 'deleted', 0, array('id'=>$old_user->id));
+                    $DB->set_field('user', 'timemodified', time(), array('id'=>$old_user->id));
                     if ($verbose) {
-                        mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
+                        mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$old_user->username, 'id'=>$old_user->id)));
                     }
 
                 } else {
+                    $user->timecreated = time();
+                    $user->timemodified = $user->timecreated;
                     $id = $DB->insert_record ('user', $user); // it is truly a new user
                     if ($verbose) {
                         mtrace("\t".get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)));
@@ -477,6 +480,7 @@ class auth_plugin_db extends auth_plugin_base {
 
         // Ensure userid is not overwritten
         $userid = $user->id;
+        $updated = false;
 
         if ($newinfo = $this->get_userinfo($username)) {
             $newinfo = truncate_userinfo($newinfo);
@@ -495,10 +499,14 @@ class auth_plugin_db extends auth_plugin_base {
                 if (!empty($this->config->{'field_updatelocal_' . $key})) {
                     if (isset($user->{$key}) and $user->{$key} != $value) { // only update if it's changed
                         $DB->set_field('user', $key, $value, array('id'=>$userid));
+                        $updated = true;
                     }
                 }
             }
         }
+        if ($updated) {
+            $DB->set_field('user', 'timemodified', time(), array('id'=>$userid));
+        }
         return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
     }
 
index 79928ba..eb15d00 100644 (file)
@@ -177,6 +177,28 @@ class cc112moodle extends cc2moodle {
         return $result . $blti_mod;
     }
 
+    /**
+    * (non-PHPdoc)
+    * @see cc2moodle::get_module_visible()
+    */
+    protected function get_module_visible($identifier) {
+        //Should item be hidden or not
+        $mod_visible = 1;
+        if (!empty($identifier)) {
+            $xpath = static::newx_path(static::$manifest, static::$namespaces);
+            $query  = '/imscc:manifest/imscc:resources/imscc:resource[@identifier="' . $identifier . '"]';
+            $query .= '//lom:intendedEndUserRole/lom:value';
+            $intendeduserrole = $xpath->query($query);
+            if (!empty($intendeduserrole) && ($intendeduserrole->length > 0)) {
+                $role = trim($intendeduserrole->item(0)->nodeValue);
+                if ((strcasecmp('Instructor', $role) === 0) || (strcasecmp('Mentor', $role) === 0)) {
+                    $mod_visible = 0;
+                }
+            }
+        }
+        return $mod_visible;
+    }
+
     protected function create_node_course_modules_mod () {
         $labels = new cc_label();
         $resources = new cc11_resource();
index 045de97..cf73d77 100644 (file)
@@ -426,6 +426,30 @@ class cc2moodle {
 
     }
 
+    /**
+    *
+    * Is activity visible or not
+    * @param string $identifier
+    * @return number
+    */
+    protected function get_module_visible($identifier) {
+        //Should item be hidden or not
+        $mod_visible = 1;
+        if (!empty($identifier)) {
+            $xpath = static::newx_path(static::$manifest, static::$namespaces);
+            $query  = '/imscc:manifest/imscc:resources/imscc:resource[@identifier="' . $identifier . '"]';
+            $query .= '//lom:intendedEndUserRole/voc:vocabulary/lom:value';
+            $intendeduserrole = $xpath->query($query);
+            if (!empty($intendeduserrole) && ($intendeduserrole->length > 0)) {
+                $role = trim($intendeduserrole->item(0)->nodeValue);
+                if (strcasecmp('Instructor', $role) == 0) {
+                    $mod_visible = 0;
+                }
+            }
+        }
+        return $mod_visible;
+    }
+
     protected function create_node_course_sections_section_mods_mod ($root_parent) {
 
         $sheet_course_sections_section_mods_mod = static::loadsheet(SHEET_COURSE_SECTIONS_SECTION_MODS_MOD);
@@ -459,13 +483,15 @@ class cc2moodle {
                                        '[#mod_instance_id#]',
                                        '[#mod_type#]',
                                        '[#date_now#]',
-                                       '[#mod_indent#]');
+                                       '[#mod_indent#]',
+                                       '[#mod_visible#]');
 
                     $replace_values = array($child['index'],
                                             $child['instance'],
                                             $child['moodle_type'],
                                             time(),
-                                            $indent);
+                                            $indent,
+                                            $this->get_module_visible($child['resource_indentifier']));
 
                     $node_course_sections_section_mods_mod .= str_replace($find_tags, $replace_values, $sheet_course_sections_section_mods_mod);
                 }
index f4f6642..d3d7c7b 100644 (file)
@@ -1,4 +1,25 @@
 <?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+/**
+ * @package    backup-convert
+ * @copyright  2011 Darko Miletic <dmiletic@moodlerooms.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
 
 require_once 'cc_utils.php';
 require_once 'cc_general.php';
@@ -2059,6 +2080,11 @@ class cc_assesment_question_proc_base {
     public function on_generate_metadata() {
         if (empty($this->qmetadata)) {
             $this->qmetadata = new cc_question_metadata($this->qtype);
+            //Get weighting value
+            $weighting_value = (int)$this->questions->nodeValue('defaultmark', $this->question_node);
+            if ($weighting_value > 1) {
+                $this->qmetadata->set_weighting($weighting_value);
+            }
             $rts = new cc_assesment_itemmetadata();
             $rts->add_metadata($this->qmetadata);
             $this->qitem->set_itemmetadata($rts);
@@ -2142,12 +2168,21 @@ class cc_assesment_question_multichoice extends cc_assesment_question_proc_base
         parent::__construct($quiz, $questions, $manifest, $section, $question_node, $rootpath, $contextid, $outdir);
         $this->qtype = cc_qti_profiletype::multiple_choice;
 
-        $correct_grade_value = $this->questions->nodeValue('defaultmark', $this->question_node);
-        $correct_answer_node = $this->questions->node("plugin_qtype_multichoice_question/answers/answer[fraction={$correct_grade_value}]", $this->question_node);
+        /**
+        *
+        * What is needed is a maximum grade value taken from the answer fraction
+        * It is supposed to always be between 1 and 0 in decimal representation,
+        * however that is not always the case so a change in test was needed
+        * but since we support here one correct answer type
+        * correct answer would always have to be 1
+        */
+        $correct_answer_node = $this->questions->node("plugin_qtype_multichoice_question/answers/answer[fraction!=0.0000000]", $this->question_node);
+        if (empty($correct_answer_node)) {
+            throw new RuntimeException('No correct answer!');
+        }
         $this->correct_answer_node_id = $this->questions->nodeValue('@id', $correct_answer_node);
         $maximum_quiz_grade = (int)$this->quiz->nodeValue('/activity/quiz/grade');
-        $this->total_grade_value = ($maximum_quiz_grade + (int)$correct_grade_value).'.0000000';
-
+        $this->total_grade_value = ($maximum_quiz_grade + 1).'.0000000';
     }
 
     public function on_generate_answers() {
@@ -2179,7 +2214,7 @@ class cc_assesment_question_multichoice extends cc_assesment_question_proc_base
             pkg_resource_dependencies::instance()->add($result[1]);
             $answer_ident = $qresponse_label->get_ident();
             $feedback_ident = $answer_ident.'_fb';
-            if ($id) {
+            if (empty($this->correct_answer_ident) && $id) {
                 $this->correct_answer_ident = $answer_ident;
             }
             //add answer specific feedbacks if not empty
index b2f7b87..6de8116 100644 (file)
@@ -132,32 +132,33 @@ class cc_resource extends entities {
                     $cdir = getcwd();
                     chdir($dirpath);
                     try {
-                        $doc->loadHTML($mod_alltext);
-                        $xpath = new DOMXPath($doc);
-                        $attributes = array('href', 'src', 'background', 'archive', 'code');
-                        $qtemplate = "//*[@##][not(contains(@##,'://'))]/@##";
-                        $query = '';
-                        foreach ($attributes as $attrname) {
-                            if (!empty($query)) {
-                                $query .= " | ";
+                        if (!empty($mod_alltext) && $doc->loadHTML($mod_alltext)) {
+                            $xpath = new DOMXPath($doc);
+                            $attributes = array('href', 'src', 'background', 'archive', 'code');
+                            $qtemplate = "//*[@##][not(contains(@##,'://'))]/@##";
+                            $query = '';
+                            foreach ($attributes as $attrname) {
+                                if (!empty($query)) {
+                                    $query .= " | ";
+                                }
+                                $query .= str_replace('##', $attrname, $qtemplate);
                             }
-                            $query .= str_replace('##', $attrname, $qtemplate);
-                        }
-                        $list = $xpath->query($query);
-                        $searches = array();
-                        $replaces = array();
-                        foreach ($list as $resrc) {
-                            $rpath = $resrc->nodeValue;
-                            $rtp = realpath($rpath);
-                            if (($rtp !== false) && is_file($rtp)) {
-                                //file is there - we are in business
-                                $strip = str_replace("\\", "/", str_ireplace($rootpath, '', $rtp));
-                                $encoded_file = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $strip);
-                                $searches[] = $resrc->nodeValue;
-                                $replaces[] = $encoded_file;
+                            $list = $xpath->query($query);
+                            $searches = array();
+                            $replaces = array();
+                            foreach ($list as $resrc) {
+                                $rpath = $resrc->nodeValue;
+                                $rtp = realpath($rpath);
+                                if (($rtp !== false) && is_file($rtp)) {
+                                    //file is there - we are in business
+                                    $strip = str_replace("\\", "/", str_ireplace($rootpath, '', $rtp));
+                                    $encoded_file = '$@FILEPHP@$'.str_replace('/', '$@SLASH@$', $strip);
+                                    $searches[] = $resrc->nodeValue;
+                                    $replaces[] = $encoded_file;
+                                }
                             }
+                            $mod_alltext = str_replace($searches, $replaces, $mod_alltext);
                         }
-                        $mod_alltext = str_replace($searches, $replaces, $mod_alltext);
                     } catch (Exception $e) {
                         //silence the complaints
                     }
index 7a5f36c..c170ced 100644 (file)
@@ -5,7 +5,7 @@
             <ADDED>[#date_now#]</ADDED>
             <SCORE>0</SCORE>
             <INDENT>[#mod_indent#]</INDENT>
-            <VISIBLE>1</VISIBLE>
+            <VISIBLE>[#mod_visible#]</VISIBLE>
             <GROUPMODE>0</GROUPMODE>
             <GROUPINGID>0</GROUPINGID>
             <GROUPMEMBERSONLY>0</GROUPMEMBERSONLY>
index a6bcbbc..7e15755 100644 (file)
@@ -55,6 +55,7 @@ class enrol_cohort_addinstance_form extends moodleform {
         $rs->close();
 
         $roles = get_assignable_roles($coursecontext);
+        $roles[0] = get_string('none');
         $roles = array_reverse($roles, true); // descending default sortorder
 
         $mform->addElement('header','general', get_string('pluginname', 'enrol_cohort'));
diff --git a/enrol/cohort/cli/sync.php b/enrol/cohort/cli/sync.php
new file mode 100644 (file)
index 0000000..068c18b
--- /dev/null
@@ -0,0 +1,66 @@
+<?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/>.
+
+/**
+ * CLI sync for cohort enrolments, use for debugging or immediate sync
+ * of all courses.
+ *
+ * Notes:
+ *   - it is required to use the web server account when executing PHP CLI scripts
+ *   - you need to change the "www-data" to match the apache user account
+ *   - use "su" if "sudo" not available
+ *
+ * @package    enrol
+ * @subpackage cohort
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once("$CFG->dirroot/enrol/cohort/locallib.php");
+
+// now get cli options
+list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help =
+        "Execute cohort course enrol sync.
+
+Options:
+-v, --verbose         Print verbose progess information
+-h, --help            Print out this help
+
+Example:
+\$sudo -u www-data /usr/bin/php enrol/cohort/cli/sync.php
+";
+
+    echo $help;
+    die;
+}
+
+$verbose = !empty($options['verbose']);
+
+$result = enrol_cohort_sync(null, $verbose);
+
+exit($result);
\ No newline at end of file
index 4530353..dcd09e8 100644 (file)
@@ -38,6 +38,16 @@ $capabilities = array(
         )
     ),
 
+    /* This is used only when sync suspends users instead of full unenrolment */
+    'enrol/cohort:unenrol' => array(
+
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(
+            'manager' => CAP_ALLOW,
+        )
+    ),
+
 );
 
 
diff --git a/enrol/cohort/db/uninstall.php b/enrol/cohort/db/uninstall.php
new file mode 100644 (file)
index 0000000..58b0c05
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Meta link enrolment plugin uninstallation.
+ *
+ * @package    enrol
+ * @subpackage cohort
+ * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+function xmldb_enrol_cohort_uninstall() {
+    global $CFG, $DB;
+
+    $cohort = enrol_get_plugin('cohort');
+    $rs = $DB->get_recordset('enrol', array('enrol'=>'cohort'));
+    foreach ($rs as $instance) {
+        $cohort->delete_instance($instance);
+    }
+    $rs->close();
+
+    role_unassign_all(array('component'=>'enrol_cohort'));
+
+    return true;
+}
\ No newline at end of file
index a2dcbf4..8196bb0 100644 (file)
@@ -27,5 +27,6 @@
 $string['ajaxmore'] = 'More...';
 $string['cohortsearch'] = 'Search';
 $string['cohort:config'] = 'Configure cohort instances';
+$string['cohort:unenrol'] = 'Unenrol suspended users';
 $string['pluginname'] = 'Cohort sync';
 $string['pluginname_desc'] = 'Cohort enrolment plugin synchronises cohort members with course participants.';
index 11c22f7..2009867 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -44,15 +43,16 @@ class enrol_cohort_plugin extends enrol_plugin {
         if (empty($instance)) {
             $enrol = $this->get_name();
             return get_string('pluginname', 'enrol_'.$enrol);
+
         } else if (empty($instance->name)) {
             $enrol = $this->get_name();
             if ($role = $DB->get_record('role', array('id'=>$instance->roleid))) {
                 $role = role_get_name($role, get_context_instance(CONTEXT_COURSE, $instance->courseid));
+                return get_string('pluginname', 'enrol_'.$enrol) . ' (' . format_string($DB->get_field('cohort', 'name', array('id'=>$instance->customint1))) . ' - ' . $role .')';
             } else {
-                $role = get_string('error');
+                return get_string('pluginname', 'enrol_'.$enrol) . ' (' . format_string($DB->get_field('cohort', 'name', array('id'=>$instance->customint1))) . ')';
             }
 
-            return get_string('pluginname', 'enrol_'.$enrol) . ' (' . format_string($DB->get_field('cohort', 'name', array('id'=>$instance->customint1))) . ' - ' . $role .')';
         } else {
             return format_string($instance->name);
         }
@@ -100,6 +100,7 @@ class enrol_cohort_plugin extends enrol_plugin {
         return false;
     }
 
+
     /**
      * Called for all enabled enrol plugins that returned true from is_cron_required().
      * @return void
@@ -107,12 +108,6 @@ class enrol_cohort_plugin extends enrol_plugin {
     public function cron() {
         global $CFG;
 
-        // purge all roles if cohort sync disabled, those can be recreated later here in cron
-        if (!enrol_is_enabled('cohort')) {
-            role_unassign_all(array('component'=>'cohort_enrol'));
-            return;
-        }
-
         require_once("$CFG->dirroot/enrol/cohort/locallib.php");
         enrol_cohort_sync();
     }
@@ -138,6 +133,59 @@ class enrol_cohort_plugin extends enrol_plugin {
 
     }
 
+    /**
+     * Update instance status
+     *
+     * @param stdClass $instance
+     * @param int $newstatus ENROL_INSTANCE_ENABLED, ENROL_INSTANCE_DISABLED
+     * @return void
+     */
+    public function update_status($instance, $newstatus) {
+        global $CFG;
+
+        parent::update_status($instance, $newstatus);
+
+        require_once("$CFG->dirroot/enrol/cohort/locallib.php");
+        enrol_cohort_sync($instance->courseid);
+    }
+
+    /**
+     * Does this plugin allow manual unenrolment of a specific user?
+     * Yes, but only if user suspended...
+     *
+     * @param stdClass $instance course enrol instance
+     * @param stdClass $ue record from user_enrolments table
+     *
+     * @return bool - true means user with 'enrol/xxx:unenrol' may unenrol this user, false means nobody may touch this user enrolment
+     */
+    public function allow_unenrol_user(stdClass $instance, stdClass $ue) {
+        if ($ue->status == ENROL_USER_SUSPENDED) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Gets an array of the user enrolment actions
+     *
+     * @param course_enrolment_manager $manager
+     * @param stdClass $ue A user enrolment object
+     * @return array An array of user_enrolment_actions
+     */
+    public function get_user_enrolment_actions(course_enrolment_manager $manager, $ue) {
+        $actions = array();
+        $context = $manager->get_context();
+        $instance = $ue->enrolmentinstance;
+        $params = $manager->get_moodlepage()->url->params();
+        $params['ue'] = $ue->id;
+        if ($this->allow_unenrol_user($instance, $ue) && has_capability('enrol/cohort:unenrol', $context)) {
+            $url = new moodle_url('/enrol/unenroluser.php', $params);
+            $actions[] = new user_enrolment_action(new pix_icon('t/delete', ''), get_string('unenrol', 'enrol'), $url, array('class'=>'unenrollink', 'rel'=>$ue->id));
+        }
+        return $actions;
+    }
+
     /**
      * Returns a button to enrol a cohort or its users through the manual enrolment plugin.
      *
index 3462638..2a8057b 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -36,6 +35,11 @@ require_once($CFG->dirroot . '/enrol/locallib.php');
  * it may fail sometimes, so we always do a full sync in cron too.
  */
 class enrol_cohort_handler {
+    /**
+     * Event processor - cohort member added
+     * @param stdClass $ca
+     * @return bool
+     */
     public function member_added($ca) {
         global $DB;
 
@@ -43,153 +47,252 @@ class enrol_cohort_handler {
             return true;
         }
 
-        // does anything want to sync with this parent?
-        //TODO: add join to role table to make sure that roleid actually exists
-        if (!$enrols = $DB->get_records('enrol', array('customint1'=>$ca->cohortid, 'enrol'=>'cohort'), 'id ASC')) {
+        // does any enabled cohort instance want to sync with this cohort?
+        $sql = "SELECT e.*, r.id as roleexists
+                  FROM {enrol} e
+             LEFT JOIN {role} r ON (r.id = e.roleid)
+                 WHERE customint1 = :cohortid AND enrol = 'cohort'
+              ORDER BY id ASC";
+        if (!$instances = $DB->get_records_sql($sql, array('cohortid'=>$ca->cohortid))) {
             return true;
         }
 
         $plugin = enrol_get_plugin('cohort');
-        foreach ($enrols as $enrol) {
+        foreach ($instances as $instance) {
+            if ($instance->status != ENROL_INSTANCE_ENABLED ) {
+                // no roles for disabled instances
+                $instance->roleid = 0;
+            } else if ($instance->roleid and !$instance->roleexists) {
+                // invalid role - let's just enrol, they will have to create new sync and delete this one
+                $instance->roleid = 0;
+            }
+            unset($instance->roleexists);
             // no problem if already enrolled
-            $plugin->enrol_user($enrol, $ca->userid, $enrol->roleid);
+            $plugin->enrol_user($instance, $ca->userid, $instance->roleid, 0, 0, ENROL_USER_ACTIVE);
         }
 
         return true;
     }
 
+    /**
+     * Event processor - cohort member removed
+     * @param stdClass $ca
+     * @return bool
+     */
     public function member_removed($ca) {
         global $DB;
 
-        // does anything want to sync with this parent?
-        if (!$enrols = $DB->get_records('enrol', array('customint1'=>$ca->cohortid, 'enrol'=>'cohort'), 'id ASC')) {
+        // does anything want to sync with this cohort?
+        if (!$instances = $DB->get_records('enrol', array('customint1'=>$ca->cohortid, 'enrol'=>'cohort'), 'id ASC')) {
             return true;
         }
 
         $plugin = enrol_get_plugin('cohort');
-        foreach ($enrols as $enrol) {
-            // no problem if already enrolled
-            $plugin->unenrol_user($enrol, $ca->userid);
+        $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
+
+        foreach ($instances as $instance) {
+            if (!$ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$ca->userid))) {
+                continue;
+            }
+            if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
+                $plugin->unenrol_user($instance, $ca->userid);
+
+            } else {
+                if ($ue->status != ENROL_USER_SUSPENDED) {
+                    $plugin->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                    $context = context_course::instance($instance->courseid);
+                    role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_cohort', 'itemid'=>$instance->id));
+                }
+            }
         }
 
         return true;
     }
 
+    /**
+     * Event processor - cohort deleted
+     * @param stdClass $cohort
+     * @return bool
+     */
     public function deleted($cohort) {
         global $DB;
 
-        // does anything want to sync with this parent?
-        if (!$enrols = $DB->get_records('enrol', array('customint1'=>$cohort->id, 'enrol'=>'cohort'), 'id ASC')) {
+        // does anything want to sync with this cohort?
+        if (!$instances = $DB->get_records('enrol', array('customint1'=>$cohort->id, 'enrol'=>'cohort'), 'id ASC')) {
             return true;
         }
 
         $plugin = enrol_get_plugin('cohort');
-        foreach ($enrols as $enrol) {
-            $plugin->delete_instance($enrol);
+        $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
+
+        foreach ($instances as $instance) {
+            if ($unenrolaction == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+                $context = context_course::instance($instance->courseid);
+                role_unassign_all(array('contextid'=>$context->id, 'component'=>'enrol_cohort', 'itemid'=>$instance->id));
+                $plugin->update_status($instance, ENROL_INSTANCE_DISABLED);
+            } else {
+                $plugin->delete_instance($instance);
+            }
         }
 
         return true;
     }
 }
 
+
 /**
  * Sync all cohort course links.
  * @param int $courseid one course, empty mean all
- * @return void
+ * @param bool $verbose verbose CLI output
+ * @return int 0 means ok, 1 means error, 2 means plugin disabled
  */
-function enrol_cohort_sync($courseid = NULL) {
+function enrol_cohort_sync($courseid = NULL, $verbose = false) {
     global $CFG, $DB;
 
-    // unfortunately this may take a long time
-    @set_time_limit(0); //if this fails during upgrade we can continue from cron, no big deal
+    // purge all roles if cohort sync disabled, those can be recreated later here by cron or CLI
+    if (!enrol_is_enabled('cohort')) {
+        if ($verbose) {
+            mtrace('Cohort sync plugin is disabled, unassigning all plugin roles and stopping.');
+        }
+        role_unassign_all(array('component'=>'enrol_cohort'));
+        return 2;
+    }
 
-    $cohort = enrol_get_plugin('cohort');
+    // unfortunately this may take a long time, this script can be interrupted without problems
+    @set_time_limit(0);
+    raise_memory_limit(MEMORY_HUGE);
+
+    if ($verbose) {
+        mtrace('Starting user enrolment synchronisation...');
+    }
+
+    $allroles = get_all_roles();
+    $instances = array(); //cache
+
+    $plugin = enrol_get_plugin('cohort');
+    $unenrolaction = $plugin->get_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL);
 
-    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
 
     // iterate through all not enrolled yet users
-    if (enrol_is_enabled('cohort')) {
-        $params = array();
-        $onecourse = "";
-        if ($courseid) {
-            $params['courseid'] = $courseid;
-            $onecourse = "AND e.courseid = :courseid";
+    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
+    $sql = "SELECT cm.userid, e.id AS enrolid, ue.status
+              FROM {cohort_members} cm
+              JOIN {enrol} e ON (e.customint1 = cm.cohortid AND e.enrol = 'cohort' $onecourse)
+         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cm.userid)
+             WHERE ue.id IS NULL OR ue.status = :suspended";
+    $params = array();
+    $params['courseid'] = $courseid;
+    $params['suspended'] = ENROL_USER_SUSPENDED;
+    $rs = $DB->get_recordset_sql($sql, $params);
+    foreach($rs as $ue) {
+        if (!isset($instances[$ue->enrolid])) {
+            $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
         }
-        $sql = "SELECT cm.userid, e.id AS enrolid
-                  FROM {cohort_members} cm
-                  JOIN {enrol} e ON (e.customint1 = cm.cohortid AND e.status = :statusenabled AND e.enrol = 'cohort' $onecourse)
-             LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = cm.userid)
-                 WHERE ue.id IS NULL";
-        $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
-        $params['courseid'] = $courseid;
-        $rs = $DB->get_recordset_sql($sql, $params);
-        $instances = array(); //cache
-        foreach($rs as $ue) {
-            if (!isset($instances[$ue->enrolid])) {
-                $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+        $instance = $instances[$ue->enrolid];
+        if ($ue->status == ENROL_USER_SUSPENDED) {
+            $plugin->update_user_enrol($instance, $ue->userid, ENROL_USER_ACTIVE);
+            if ($verbose) {
+                mtrace("  unsuspending: $ue->userid ==> $instance->courseid via cohort $instance->customint1");
+            }
+        } else {
+            $plugin->enrol_user($instance, $ue->userid);
+            if ($verbose) {
+                mtrace("  enrolling: $ue->userid ==> $instance->courseid via cohort $instance->customint1");
             }
-            $cohort->enrol_user($instances[$ue->enrolid], $ue->userid);
         }
-        $rs->close();
-        unset($instances);
     }
+    $rs->close();
+
 
-    // unenrol as necessary - ignore enabled flag, we want to get rid of all
-    $sql = "SELECT ue.userid, e.id AS enrolid
+    // unenrol as necessary
+    $sql = "SELECT ue.*, e.courseid
               FROM {user_enrolments} ue
               JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' $onecourse)
-         LEFT JOIN {cohort_members} cm ON (cm.cohortid  = e.customint1 AND cm.userid = ue.userid)
+         LEFT JOIN {cohort_members} cm ON (cm.cohortid = e.customint1 AND cm.userid = ue.userid)
              WHERE cm.id IS NULL";
-    //TODO: this may use a bit of SQL optimisation
     $rs = $DB->get_recordset_sql($sql, array('courseid'=>$courseid));
-    $instances = array(); //cache
     foreach($rs as $ue) {
         if (!isset($instances[$ue->enrolid])) {
             $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
         }
-        $cohort->unenrol_user($instances[$ue->enrolid], $ue->userid);
+        $instance = $instances[$ue->enrolid];
+        if ($unenrolaction == ENROL_EXT_REMOVED_UNENROL) {
+            // remove enrolment together with group membership, grades, preferences, etc.
+            $plugin->unenrol_user($instance, $ue->userid);
+            if ($verbose) {
+                mtrace("  unenrolling: $ue->userid ==> $instance->courseid via cohort $instance->customint1");
+            }
+
+        } else { // ENROL_EXT_REMOVED_SUSPENDNOROLES
+            // just disable and ignore any changes
+            if ($ue->status != ENROL_USER_SUSPENDED) {
+                $plugin->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                $context = context_course::instance($instance->courseid);
+                role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$context->id, 'component'=>'enrol_cohort', 'itemid'=>$instance->id));
+                if ($verbose) {
+                    mtrace("  suspending and unsassigning all roles: $ue->userid ==> $instance->courseid");
+                }
+            }
+        }
     }
     $rs->close();
     unset($instances);
 
-    // now assign all necessary roles
-    if (enrol_is_enabled('cohort')) {
-        $sql = "SELECT e.roleid, ue.userid, c.id AS contextid, e.id AS itemid
-                  FROM {user_enrolments} ue
-                  JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' AND e.status = :statusenabled $onecourse)
-                  JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :coursecontext)
-             LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid AND ra.itemid = e.id AND ra.component = 'enrol_cohort' AND e.roleid = ra.roleid)
-                 WHERE ra.id IS NULL";
-        $params = array();
-        $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
-        $params['coursecontext'] = CONTEXT_COURSE;
-        $params['courseid'] = $courseid;
-
-        $rs = $DB->get_recordset_sql($sql, $params);
-        foreach($rs as $ra) {
-            role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_cohort', $ra->itemid);
+
+    // now assign all necessary roles to enrolled users - skip suspended instances and users
+    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
+    $sql = "SELECT e.roleid, ue.userid, c.id AS contextid, e.id AS itemid, e.courseid
+              FROM {user_enrolments} ue
+              JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort' AND e.status = :statusenabled $onecourse)
+              JOIN {role} r ON (r.id = e.roleid)
+              JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :coursecontext)
+         LEFT JOIN {role_assignments} ra ON (ra.contextid = c.id AND ra.userid = ue.userid AND ra.itemid = e.id AND ra.component = 'enrol_cohort' AND e.roleid = ra.roleid)
+             WHERE ue.status = :useractive AND ra.id IS NULL";
+    $params = array();
+    $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
+    $params['useractive'] = ENROL_USER_ACTIVE;
+    $params['coursecontext'] = CONTEXT_COURSE;
+    $params['courseid'] = $courseid;
+
+    $rs = $DB->get_recordset_sql($sql, $params);
+    foreach($rs as $ra) {
+        role_assign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_cohort', $ra->itemid);
+        if ($verbose) {
+            mtrace("  assigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
         }
-        $rs->close();
     }
+    $rs->close();
+
 
-    // remove unwanted roles - include ignored roles and disabled plugins too
-    $onecourse = $courseid ? "AND c.instanceid = :courseid" : "";
-    $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid
+    // remove unwanted roles - sync role can not be changed, we only remove role when unenrolled
+    $onecourse = $courseid ? "AND e.courseid = :courseid" : "";
+    $sql = "SELECT ra.roleid, ra.userid, ra.contextid, ra.itemid, e.courseid
               FROM {role_assignments} ra
-              JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :coursecontext $onecourse)
-         LEFT JOIN (SELECT e.id AS enrolid, e.roleid, ue.userid
-                      FROM {user_enrolments} ue
-                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'cohort')
-                   ) x ON (x.enrolid = ra.itemid AND ra.component = 'enrol_cohort' AND x.roleid = ra.roleid AND x.userid = ra.userid)
-             WHERE x.userid IS NULL AND ra.component = 'enrol_cohort'";
-    $params = array('coursecontext' => CONTEXT_COURSE, 'courseid' => $courseid);
+              JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :coursecontext)
+              JOIN {enrol} e ON (e.id = ra.itemid AND e.enrol = 'cohort' $onecourse)
+         LEFT JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid AND ue.status = :useractive)
+             WHERE ra.component = 'enrol_cohort' AND (ue.id IS NULL OR e.status <> :statusenabled)";
+    $params = array();
+    $params['statusenabled'] = ENROL_INSTANCE_ENABLED;
+    $params['useractive'] = ENROL_USER_ACTIVE;
+    $params['coursecontext'] = CONTEXT_COURSE;
+    $params['courseid'] = $courseid;
 
     $rs = $DB->get_recordset_sql($sql, $params);
     foreach($rs as $ra) {
         role_unassign($ra->roleid, $ra->userid, $ra->contextid, 'enrol_cohort', $ra->itemid);
+        if ($verbose) {
+            mtrace("  unassigning role: $ra->userid ==> $ra->courseid as ".$allroles[$ra->roleid]->shortname);
+        }
     }
     $rs->close();
 
+
+    if ($verbose) {
+        mtrace('...user enrolment synchronisation finished.');
+    }
+
+    return 0;
 }
 
 /**
@@ -326,7 +429,7 @@ function enrol_cohort_search_cohorts(course_enrolment_manager $manager, $offset
     // Add some additional sensible conditions
     $tests = array('contextid ' . $sqlparents);
 
-    // Modify the quesry to perform the search if requred
+    // Modify the query to perform the search if required
     if (!empty($search)) {
         $conditions = array(
             'name',
index 6856d08..8bfc71c 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -39,6 +38,11 @@ if ($ADMIN->fulltree) {
         $student = reset($student);
         $settings->add(new admin_setting_configselect('enrol_cohort/roleid',
             get_string('defaultrole', 'role'), '', $student->id, $options));
+
+        $options = array(
+            ENROL_EXT_REMOVED_UNENROL        => get_string('extremovedunenrol', 'enrol'),
+            ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'));
+        $settings->add(new admin_setting_configselect('enrol_cohort/unenrolaction', get_string('extremovedaction', 'enrol'), get_string('extremovedaction_help', 'enrol'), ENROL_EXT_REMOVED_UNENROL, $options));
     }
 }
 
index 34200b6..ef4adcb 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2011112901;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'enrol_cohort';    // Full name of the plugin (used for diagnostics)
-$plugin->cron      = 60;
\ No newline at end of file
+$plugin->cron      = 60*60;             // run cron every hour by default, it is not out-of-sync often
\ No newline at end of file
index 331888e..ba6d8f7 100644 (file)
@@ -693,6 +693,8 @@ $string['navigationupgrade'] = 'This upgrade introduces two new navigation block
 $string['navcourselimit'] = 'Course limit';
 $string['navlinkcoursesections'] = 'Link course sections';
 $string['navlinkcoursesections_help'] = 'If enabled course sections will be shown as links within the navigation.';
+$string['navshowfrontpagemods'] = 'Show front page activities in the navigation';
+$string['navshowfrontpagemods_help'] = 'If enabled, front page activities will be shown on the navigation under site pages.';
 $string['navshowallcourses'] = 'Show all courses';
 $string['navshowcategories'] = 'Show course categories';
 $string['neverdeleteruns'] = 'Never delete runs';
index 7905f66..4da2ea8 100644 (file)
@@ -39,16 +39,16 @@ $string['parentlanguage'] = '';
 $string['strftimedate'] = '%d %B %Y';
 $string['strftimedatefullshort'] = '%d/%m/%y';
 $string['strftimedateshort'] = '%d %B';
-$string['strftimedatetime'] = '%d %B %Y, %I:%M %p';
+$string['strftimedatetime'] = '%d %B %Y, %l:%M %p';
 $string['strftimedatetimeshort'] = '%d/%m/%y, %H:%M';
 $string['strftimedaydate'] = '%A, %d %B %Y';
-$string['strftimedaydatetime'] = '%A, %d %B %Y, %I:%M %p';
+$string['strftimedaydatetime'] = '%A, %d %B %Y, %l:%M %p';
 $string['strftimedayshort'] = '%A, %d %B';
 $string['strftimedaytime'] = '%a, %H:%M';
 $string['strftimemonthyear'] = '%B %Y';
 $string['strftimerecent'] = '%d %b, %H:%M';
-$string['strftimerecentfull'] = '%a, %d %b %Y, %I:%M %p';
-$string['strftimetime'] = '%I:%M %p';
+$string['strftimerecentfull'] = '%a, %d %b %Y, %l:%M %p';
+$string['strftimetime'] = '%l:%M %p';
 $string['thisdirection'] = 'ltr';
 $string['thisdirectionvertical'] = 'btt';
 $string['thislanguage'] = 'English';
index 9cae46d..7f8fb12 100644 (file)
@@ -451,6 +451,9 @@ $string['displayingfirst'] = 'Only the first {$a->count} {$a->things} are displa
 $string['displayingrecords'] = 'Displaying {$a} records';
 $string['displayingusers'] = 'Displaying users {$a->start} to {$a->end}';
 $string['displayonpage'] = 'Display on page';
+$string['dndenabled'] = 'You can drag and drop files into this box to upload them';
+$string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.<br />Note: this may not work with other web browsers';
+$string['dndenabled_single'] = 'you can drag and drop a file into this box to upload it';
 $string['documentation'] = 'Moodle documentation';
 $string['down'] = 'Down';
 $string['download'] = 'Download';
index b7c0c94..18edded 100644 (file)
@@ -4978,17 +4978,6 @@ abstract class context extends stdClass {
         return $record;
     }
 
-    /**
-     * Returns human readable context level name.
-     *
-     * @static
-     * @return string the human readable context level name.
-     */
-    protected static function get_level_name() {
-        // must be implemented in all context levels
-        throw new coding_exception('can not get level name of abstract context');
-    }
-
     /**
      * Returns human readable context identifier.
      *
@@ -5476,7 +5465,7 @@ class context_system extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('coresystem');
     }
 
@@ -5714,7 +5703,7 @@ class context_user extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('user');
     }
 
@@ -5882,7 +5871,7 @@ class context_coursecat extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('category');
     }
 
@@ -6102,7 +6091,7 @@ class context_course extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('course');
     }
 
@@ -6317,7 +6306,7 @@ class context_module extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('activitymodule');
     }
 
@@ -6553,7 +6542,7 @@ class context_block extends context {
      * @static
      * @return string the human readable context level name.
      */
-    protected static function get_level_name() {
+    public static function get_level_name() {
         return get_string('block');
     }
 
index 83978d6..11c3def 100644 (file)
@@ -300,35 +300,6 @@ function cron_run() {
     mtrace('Finished blocks');
 
 
-    //TODO: get rid of this bloody hardcoded quiz module stuff, this must be done from quiz_cron()!
-    mtrace("Starting quiz reports");
-    if ($reports = $DB->get_records_select('quiz_reports', "cron > 0 AND ((? - lastcron) > cron)", array($timenow))) {
-        foreach ($reports as $report) {
-            $cronfile = "$CFG->dirroot/mod/quiz/report/$report->name/cron.php";
-            if (file_exists($cronfile)) {
-                include_once($cronfile);
-                $cron_function = 'quiz_report_'.$report->name."_cron";
-                if (function_exists($cron_function)) {
-                    mtrace("Processing quiz report cron function $cron_function ...", '');
-                    $pre_dbqueries = null;
-                    $pre_dbqueries = $DB->perf_get_queries();
-                    $pre_time      = microtime(1);
-                    if ($cron_function()) {
-                        $DB->set_field('quiz_reports', "lastcron", $timenow, array("id"=>$report->id));
-                    }
-                    if (isset($pre_dbqueries)) {
-                        mtrace("... used " . ($DB->perf_get_queries() - $pre_dbqueries) . " dbqueries");
-                        mtrace("... used " . (microtime(1) - $pre_time) . " seconds");
-                    }
-                    @set_time_limit(0);
-                    mtrace("done.");
-                }
-            }
-        }
-    }
-    mtrace("Finished quiz reports");
-
-
     mtrace('Starting admin reports');
     cron_execute_plugin_type('report');
     mtrace('Finished admin reports');
@@ -428,6 +399,8 @@ function cron_run() {
     // TODO: Repository lib.php files are messed up (include many other files, etc), so it is
     // currently not possible to implement repository plugin cron using this infrastructure
     // cron_execute_plugin_type('repository', 'repository plugins');
+    cron_execute_plugin_type('qbehaviour', 'question behaviours');
+    cron_execute_plugin_type('qformat', 'question import/export formats');
     cron_execute_plugin_type('qtype', 'question types');
     cron_execute_plugin_type('plagiarism', 'plagiarism plugins');
     cron_execute_plugin_type('theme', 'themes');
index a41945b..91d97bd 100644 (file)
@@ -1957,19 +1957,26 @@ function count_login_failures($mode, $username, $lastlogin) {
 /// GENERAL HELPFUL THINGS  ///////////////////////////////////
 
 /**
- * Dump a given object's information in a PRE block.
+ * Dumps a given object's information for debugging purposes
  *
- * Mostly just used for debugging.
+ * When used in a CLI script, the object's information is written to the standard
+ * error output stream. When used in a web script, the object is dumped to a
+ * pre-formatted block with the "notifytiny" CSS class.
  *
  * @param mixed $object The data to be printed
- * @return void OUtput is echo'd
+ * @return void output is echo'd
  */
 function print_object($object) {
-    echo '<pre class="notifytiny">';
+
     // we may need a lot of memory here
     raise_memory_limit(MEMORY_EXTRA);
-    echo s(print_r($object, true));
-    echo '</pre>';
+
+    if (CLI_SCRIPT) {
+        fwrite(STDERR, print_r($object, true));
+        fwrite(STDERR, PHP_EOL);
+    } else {
+        echo html_writer::tag('pre', s(print_r($object, true)), array('class' => 'notifytiny'));
+    }
 }
 
 /**
diff --git a/lib/form/dndupload.js b/lib/form/dndupload.js
new file mode 100644 (file)
index 0000000..df0fb19
--- /dev/null
@@ -0,0 +1,398 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Javascript library for enableing a drag and drop upload interface
+ *
+ * @package    moodlecore
+ * @subpackage form
+ * @copyright  2011 Davo Smith
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+M.form_dndupload = {
+    // YUI object.
+    Y: null,
+    // URL for upload requests
+    url: M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload',
+    // itemid used for repository upload
+    itemid: null,
+    // accepted filetypes accepted by this form passed to repository
+    acceptedtypes: [],
+    // maximum number of files this form allows
+    maxfiles: 0,
+    // maximum size of files allowed in this form
+    maxbytes: 0,
+    // unqiue id of this form field used for html elements
+    clientid: '',
+    // upload repository id, used for upload
+    repositoryid: 0,
+    // container which holds the node which recieves drag events
+    container: null,
+    // filemanager element we are working with
+    filemanager: null,
+    // callback  to filepicker element to refesh when uploaded
+    callback: null,
+    // Nasty hack to distinguish between dragenter(first entry),
+    // dragenter+dragleave(moving between child elements) and dragleave (leaving element)
+    entercount: 0,
+
+
+    /**
+     * Initalise the drag and drop upload interface
+     * Note: one and only one of options.filemanager and options.formcallback must be defined
+     *
+     * @param Y the YUI object
+     * @param object options {
+     *            itemid: itemid used for repository upload in this form
+     *            acceptdtypes: accepted filetypes by this form
+     *            maxfiles: maximum number of files this form allows
+     *            maxbytes: maximum size of files allowed in this form
+     *            clientid: unqiue id of this form field used for html elements
+     *            containerprefix: prefix of htmlid of container
+     *            repositories: array of repository objects passed from filepicker
+     *            filemanager: filemanager element we are working with
+     *            callback: callback  to filepicker element to refesh when uploaded
+     *          }
+     */
+    init: function(Y, options) {
+        this.Y = Y;
+
+        if (!this.browser_supported()) {
+            return; // Browser does not support the required functionality
+        }
+
+        // try and retrieve enabled upload repository
+        this.repositoryid = this.get_upload_repositoryid(options.repositories);
+
+        if (!this.repositoryid) {
+            return; // no upload repository is enabled to upload to
+        }
+
+        this.acceptedtypes = options.acceptedtypes;
+        this.clientid = options.clientid;
+        this.maxfiles = options.maxfiles;
+        this.maxbytes = options.maxbytes;
+        this.itemid = options.itemid;
+        this.container = this.Y.one(options.containerprefix + this.clientid);
+
+        if (options.filemanager) {
+            // Needed to tell the filemanager to redraw when files uploaded
+            // and to check how many files are already uploaded
+            this.filemanager = options.filemanager;
+        } else if (options.formcallback) {
+
+            // Needed to tell the filepicker to update when a new
+            // file is uploaded
+            this.callback = options.formcallback;
+        } else {
+            if (M.cfg.developerdebug) {
+                alert('dndupload: Need to define either options.filemanager or options.callback');
+            }
+            return;
+        }
+
+        this.init_events();
+        this.Y.one('#dndenabled-'+this.clientid).setStyle('display', 'inline');
+    },
+
+    /**
+     * Check the browser has the required functionality
+     * @return true if browser supports drag/drop upload
+     */
+    browser_supported: function() {
+
+        if (typeof FileReader == 'undefined') {
+            return false;
+        }
+        if (typeof FormData == 'undefined') {
+            return false;
+        }
+        return true;
+    },
+
+    /**
+     * Get upload repoistory from array of enabled repositories
+     *
+     * @param array repositories repository objects passed from filepicker
+     * @param returns int id of upload repository or false if not found
+     */
+    get_upload_repositoryid: function(repositories) {
+         for (var i in repositories) {
+             if (repositories[i].type == "upload") {
+                 return repositories[i].id;
+             }
+         }
+
+         return false;
+    },
+
+    /**
+     * Initialise drag events on node container, all events need
+     * to be processed for drag and drop to work
+     */
+    init_events: function() {
+        this.Y.on('dragenter', this.drag_enter, this.container, this);
+        this.Y.on('dragleave', this.drag_leave, this.container, this);
+        this.Y.on('dragover',  this.drag_over,  this.container, this);
+        this.Y.on('drop',      this.drop,      this.container, this);
+    },
+
+    /**
+     * Check if the drag contents are valid and then call
+     * preventdefault / stoppropagation to let the browser know
+     * we will handle this drag/drop
+     *
+     * @param e event object
+     * @return boolean true if a valid file drag event
+     */
+    check_drag: function(e) {
+        if (!this.has_files(e)) {
+            return false;
+        }
+
+        if (this.reached_maxfiles()) {
+            return false;
+        }
+
+        e.preventDefault();
+        e.stopPropagation();
+
+        return true;
+    },
+
+    /**
+     * Handle a dragenter event, highlight the destination node
+     * when a suitable drag event occurs
+     */
+    drag_enter: function(e) {
+        if (!this.check_drag(e)) {
+            return true;
+        }
+
+        this.entercount++;
+        if (this.entercount >= 2) {
+            this.entercount = 2; // Just moved over a child element - nothing to do
+            return false;
+        }
+
+        this.show_upload_ready();
+        return false;
+    },
+
+    /**
+     * Handle a dragleave event, Remove the highlight if dragged from
+     * node
+     */
+    drag_leave: function(e) {
+        if (!this.check_drag(e)) {
+            return true;
+        }
+
+        this.entercount--;
+        if (this.entercount == 1) {
+            return false; // Just moved over a child element - nothing to do
+        }
+
+        this.entercount = 0;
+        this.hide_upload_ready();
+        return false;
+    },
+
+    /**
+     * Handle a dragover event. Required to intercept to prevent the browser from
+     * handling the drag and drop event as normal
+     */
+    drag_over: function(e) {
+        if (!this.check_drag(e)) {
+            return true;
+        }
+
+        return false;
+    },
+
+    /**
+     * Handle a drop event.  Remove the highlight and then upload each
+     * of the files (until we reach the file limit, or run out of files)
+     */
+    drop: function(e) {
+        if (!this.check_drag(e)) {
+            return true;
+        }
+
+        this.entercount = 0;
+        this.hide_upload_ready();
+        this.show_progress_spinner();
+
+        var files = e._event.dataTransfer.files;
+        if (this.filemanager) {
+            var currentfilecount = this.filemanager.filecount;
+            for (var i=0, f; f=files[i]; i++) {
+                if (currentfilecount >= this.maxfiles && this.maxfiles != -1) {
+                    break;
+                }
+                if (this.upload_file(f)) {
+                    currentfilecount++;
+                }
+            }
+        } else {
+            if (files.length >= 1) {
+                this.upload_file(files[0]);
+            }
+        }
+
+        return false;
+    },
+
+    /**
+     * Check to see if the drag event has any files in it
+     *
+     * @param e event object
+     * @return boolean true if event has files
+     */
+    has_files: function(e) {
+        var types = e._event.dataTransfer.types;
+        for (var i=0; i<types.length; i++) {
+            if (types[i] == 'Files') {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    /**
+     * Check if reached the maximumum number of allowed files
+     *
+     * @return boolean true if reached maximum number of files
+     */
+    reached_maxfiles: function() {
+        if (this.filemanager) {
+            if (this.filemanager.filecount >= this.maxfiles && this.maxfiles != -1) {
+                return true;
+            }
+        }
+        return false;
+    },
+
+    /**
+     * Highlight the destination node
+     */
+    show_upload_ready: function() {
+        this.container.addClass('dndupload-over');
+    },
+
+    /**
+     * Remove highlight on destination node
+     */
+    hide_upload_ready: function() {
+        this.container.removeClass('dndupload-over');
+    },
+
+    /**
+     * Display a progress spinner in the destination node
+     */
+    show_progress_spinner: function() {
+        // add a loading spinner to show something is happening
+        var loadingspinner = this.Y.Node.create('<div id="dndprogresspinner-'+this.clientid+'" style="text-align: center">');
+        loadingspinner.append('<img src="'+M.util.image_url('i/loading_small')+'" />');
+        this.container.append(loadingspinner);
+    },
+
+    /**
+     * Remove progress spinner in the destination node
+     */
+    hide_progress_spinner: function() {
+        this.Y.one('#dndprogresspinner-'+this.clientid).remove();
+    },
+
+    /**
+     * Tell the attached filemanager element (if any) to refresh on file
+     * upload
+     */
+    update_filemanager: function() {
+        if (this.filemanager) {
+            // update the filemanager that we've uploaded the files
+            this.hide_progress_spinner();
+            this.filemanager.filepicker_callback();
+        }
+    },
+
+    /**
+     * Upload a single file via an AJAX call to the 'upload' repository
+     */
+    upload_file: function(file) {
+        if (file.size > this.maxbytes && this.maxbytes > 0) {
+            // Check filesize before attempting to upload
+            alert(M.util.get_string('uploadformlimit', 'moodle')+"\n'"+file.name+"'");
+            return false;
+        }
+
+        // This would be an ideal place to use the Y.io function
+        // however, this does not support data encoded using the
+        // FormData object, which is needed to transfer data from
+        // the DataTransfer object into an XMLHTTPRequest
+        // This can be converted when the YUI issue has been integrated:
+        // http://yuilibrary.com/projects/yui3/ticket/2531274
+        var xhr = new XMLHttpRequest();
+        var self = this;
+        xhr.onreadystatechange = function() { // Process the server response
+            if (xhr.readyState == 4 && xhr.status == 200) {
+                var result = JSON.parse(xhr.responseText);
+                if (result) {
+                    if (result.error) {
+                        self.hide_progress_spinner();
+                        alert(result.error);
+                    } else if (self.callback) {
+                        // Only update the filepicker if there were no errors
+                        self.hide_progress_spinner();
+                        if (result.event == 'fileexists') {
+                            // Do not worry about this, as we only care about the last
+                            // file uploaded, with the filepicker
+                            result.file = result.newfile.filename;
+                            result.url = result.newfile.url;
+                        }
+                        result.client_id = self.clientid;
+                        self.callback(result);
+                    } else {
+                        self.update_filemanager();
+                    }
+                }
+            }
+        };
+
+        // Prepare the data to send
+        var formdata = new FormData();
+        formdata.append('repo_upload_file', file); // The FormData class allows us to attach a file
+        formdata.append('sesskey', M.cfg.sesskey);
+        formdata.append('repo_id', this.repositoryid);
+        formdata.append('itemid', this.itemid);
+        if (this.filemanager) { // Filepickers do not have folders
+            formdata.append('savepath', this.filemanager.currentpath);
+        }
+
+        if (this.acceptedtypes.constructor == Array) {
+            for (var i=0; i<this.acceptedtypes.length; i++) {
+                formdata.append('accepted_types[]', this.acceptedtypes[i]);
+            }
+        } else {
+            formdata.append('accepted_types[]', this.acceptedtypes);
+        }
+
+        // Send the file & required details
+        xhr.open("POST", this.url, true);
+        xhr.send(formdata);
+        return true;
+    }
+};
index cf6837b..ba2939e 100644 (file)
@@ -763,5 +763,16 @@ M.form_filemanager.init = function(Y, options) {
         item.style.display = '';
     }
 
-    new FileManagerHelper(options);
+    var manager = new FileManagerHelper(options);
+    var dndoptions = {
+        filemanager: manager,
+        acceptedtypes: options.accepted_types,
+        clientid: options.client_id,
+        maxfiles: options.maxfiles,
+        maxbytes: options.maxbytes,
+        itemid: options.itemid,
+        repositories: manager.filepicker_options.repositories,
+        containerprefix: '#filemanager-',
+    };
+    M.form_dndupload.init(Y, dndoptions);
 };
index 3670fef..774a509 100644 (file)
@@ -274,6 +274,7 @@ function form_filemanager_render($options) {
     }
 
     $maxsize = get_string('maxfilesize', 'moodle', display_size(get_max_upload_file_size($CFG->maxbytes, $course_maxbytes, $options->maxbytes)));
+    $strdndenabled = get_string('dndenabled', 'moodle').$OUTPUT->help_icon('dndenabled');
     $html .= <<<FMHTML
 <div class="filemanager-loading mdl-align" id='filemanager-loading-{$client_id}'>
 $icon_progress
@@ -285,6 +286,7 @@ $icon_progress
         <input type="button" class="fm-btn-mkdir" id="btncrt-{$client_id}" onclick="return false" value="{$strmakedir}" />
         <input type="button" class="fm-btn-download" id="btndwn-{$client_id}" onclick="return false" {$extra} value="{$strdownload}" />
         <span> $maxsize </span>
+        <span id="dndenabled-{$client_id}" style="display: none"> - $strdndenabled </span>
     </div>
     <div class="filemanager-container" id="filemanager-{$client_id}">
         <ul id="draftfiles-{$client_id}" class="fm-filelist">
@@ -304,7 +306,7 @@ FMHTML;
     $module = array(
         'name'=>'form_filemanager',
         'fullpath'=>'/lib/form/filemanager.js',
-        'requires' => array('core_filepicker', 'base', 'io-base', 'node', 'json', 'yui2-button', 'yui2-container', 'yui2-layout', 'yui2-menu', 'yui2-treeview'),
+        'requires' => array('core_filepicker', 'base', 'io-base', 'node', 'json', 'yui2-button', 'yui2-container', 'yui2-layout', 'yui2-menu', 'yui2-treeview', 'core_dndupload'),
         'strings' => array(array('loading', 'repository'), array('nomorefiles', 'repository'), array('confirmdeletefile', 'repository'),
              array('add', 'repository'), array('accessiblefilepicker', 'repository'), array('move', 'moodle'),
              array('cancel', 'moodle'), array('download', 'moodle'), array('ok', 'moodle'),
index e27d4af..51d8ef6 100644 (file)
@@ -43,4 +43,16 @@ M.form_filepicker.init = function(Y, options) {
     if (item) {
         item.style.display = '';
     }
+
+    var dndoptions = {
+        clientid: options.client_id,
+        acceptedtypes: options.accepted_types,
+        maxfiles: -1,
+        maxbytes: options.maxbytes,
+        itemid: options.itemid,
+        repositories: options.repositories,
+        formcallback: options.formcallback,
+        containerprefix: '#file_info_',
+    };
+    M.form_dndupload.init(Y, dndoptions);
 };
index c37da6d..771daa1 100644 (file)
@@ -88,7 +88,7 @@ class MoodleQuickForm_filepicker extends HTML_QuickForm_input {
         $html .= $OUTPUT->render($fp);
         $html .= '<input type="hidden" name="'.$elname.'" id="'.$id.'" value="'.$draftitemid.'" class="filepickerhidden"/>';
 
-        $module = array('name'=>'form_filepicker', 'fullpath'=>'/lib/form/filepicker.js', 'requires'=>array('core_filepicker', 'node', 'node-event-simulate'));
+        $module = array('name'=>'form_filepicker', 'fullpath'=>'/lib/form/filepicker.js', 'requires'=>array('core_filepicker', 'node', 'node-event-simulate', 'core_dndupload'));
         $PAGE->requires->js_init_call('M.form_filepicker.init', array($fp->options), true, $module);
 
         $nonjsfilepicker = new moodle_url('/repository/draftfiles_manager.php', array(
index 53df7d6..2914205 100644 (file)
@@ -1987,8 +1987,10 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true) {
            $textlib = textlib_get_instance();
            $datestring = $textlib->convert($datestring, $localewincharset, 'utf-8');
        }
-   }
+    }
 
+    // When using the %l (12-hour time with no leading zero), it adds unwanted spaces
+    $datestring = trim(str_replace('  ', ' ', $datestring));
     return $datestring;
 }
 
@@ -3607,7 +3609,8 @@ function create_user_record($username, $password, $auth = 'manual') {
     }
     $newuser->confirmed = 1;
     $newuser->lastip = getremoteaddr();
-    $newuser->timemodified = time();
+    $newuser->timecreated = time();
+    $newuser->timemodified = $newuser->timecreated;
     $newuser->mnethostid = $CFG->mnet_localhost_id;
 
     $newuser->id = $DB->insert_record('user', $newuser);
@@ -3670,6 +3673,7 @@ function update_user_record($username) {
         }
         if ($newuser) {
             $newuser['id'] = $oldinfo->id;
+            $newuser['timemodified'] = time();
             $DB->update_record('user', $newuser);
             // fetch full user record for the event, the complete user data contains too much info
             // and we want to be consistent with other places that trigger this event
@@ -3795,6 +3799,10 @@ function delete_user($user) {
 
     $DB->update_record('user', $updateuser);
 
+    // We will update the user's timemodified, as it will be passed to the user_deleted event, which
+    // should know about this updated property persisted to the user's table.
+    $user->timemodified = $updateuser->timemodified;
+
     // notify auth plugin - do not block the delete even when plugin fails
     $authplugin = get_auth_plugin($user->auth);
     $authplugin->user_delete($user);
@@ -4277,6 +4285,11 @@ function delete_course($courseorid, $showfeedback = true) {
 
     // delete the course and related context instance
     delete_context(CONTEXT_COURSE, $courseid);
+
+    // We will update the course's timemodified, as it will be passed to the course_deleted event,
+    // which should know about this updated property, as this event is meant to pass the full course record
+    $course->timemodified = time();
+
     $DB->delete_records("course", array("id"=>$courseid));
 
     //trigger events
index f75299b..22a7f68 100644 (file)
@@ -1608,6 +1608,7 @@ class global_navigation extends navigation_node {
                         continue;
                     }
                     $activity = new stdClass;
+                    $activity->course = $course->id;
                     $activity->section = $section->section;
                     $activity->name = $cm->name;
                     $activity->icon = $cm->icon;
@@ -1714,7 +1715,8 @@ class global_navigation extends navigation_node {
      * @param course_modinfo $modinfo Object returned from {@see get_fast_modinfo()}
      * @return array Array of activity nodes
      */
-    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $activities) {
+    protected function load_section_activities(navigation_node $sectionnode, $sectionnumber, $activities, $course = null) {
+        global $CFG;
         // A static counter for JS function naming
         static $legacyonclickcounter = 0;
 
@@ -1724,6 +1726,18 @@ class global_navigation extends navigation_node {
         }
 
         $activitynodes = array();
+        if (empty($activities)) {
+            return $activitynodes;
+        }
+
+        if (!is_object($course)) {
+            $activity = reset($activities);
+            $courseid = $activity->course;
+        } else {
+            $courseid = $course->id;
+        }
+        $showactivities = ($courseid != SITEID || !empty($CFG->navshowfrontpagemods));
+
         foreach ($activities as $activity) {
             if ($activity->section != $sectionnumber) {
                 continue;
@@ -1763,7 +1777,7 @@ class global_navigation extends navigation_node {
             $activitynode = $sectionnode->add($activityname, $action, navigation_node::TYPE_ACTIVITY, null, $activity->id, $icon);
             $activitynode->title(get_string('modulename', $activity->modname));
             $activitynode->hidden = $activity->hidden;
-            $activitynode->display = $activity->display;
+            $activitynode->display = $showactivities && $activity->display;
             $activitynode->nodetype = $activity->nodetype;
             $activitynodes[$activity->id] = $activitynode;
         }
index 7288f57..f7083ec 100644 (file)
@@ -1902,6 +1902,7 @@ class core_renderer extends renderer_base {
         $strsaved = get_string('filesaved', 'repository');
         $straddfile = get_string('openpicker', 'repository');
         $strloading  = get_string('loading', 'repository');
+        $strdndenabled = get_string('dndenabled_single', 'moodle');
         $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
 
         $currentfile = $options->currentfile;
@@ -1935,7 +1936,9 @@ $icon_progress
 EOD;
         if ($options->env != 'url') {
             $html .= <<<EOD
-    <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist">$currentfile</div>
+    <div id="file_info_{$client_id}" class="mdl-left filepicker-filelist">
+    $currentfile<span id="dndenabled-{$client_id}" style="display: none"> - $strdndenabled </span>
+    </div>
 EOD;
         }
         $html .= '</div>';
@@ -2433,14 +2436,18 @@ EOD;
      *    Settings: Administration > Appearance > Themes > Theme settings
      * and then configuring the custommenu config setting as described.
      *
+     * @param string $custommenuitems - custom menuitems set by theme instead of global theme settings
      * @return string
      */
-    public function custom_menu() {
+    public function custom_menu($custommenuitems = '') {
         global $CFG;
-        if (empty($CFG->custommenuitems)) {
+        if (empty($custommenuitems) && !empty($CFG->custommenuitems)) {
+            $custommenuitems = $CFG->custommenuitems;
+        }
+        if (empty($custommenuitems)) {
             return '';
         }
-        $custommenu = new custom_menu($CFG->custommenuitems, current_language());
+        $custommenu = new custom_menu($custommenuitems, current_language());
         return $this->render_custom_menu($custommenu);
     }
 
index f216297..0e31bdd 100644 (file)
@@ -464,6 +464,12 @@ class page_requirements_manager {
                                     'fullpath' => '/files/module.js',
                                     'requires' => array('node', 'event', 'overlay', 'io-base', 'json', 'yui2-treeview'));
                     break;
+                case 'core_dndupload':
+                    $module = array('name'     => 'core_dndupload',
+                                    'fullpath' => '/lib/form/dndupload.js',
+                                    'requires' => array('node', 'event', 'json'),
+                                    'strings'  => array(array('uploadformlimit', 'moodle')));
+                    break;
             }
 
         } else {
index 27359ce..35caaca 100644 (file)
@@ -23,6 +23,8 @@ class web_test extends UnitTestCase {
     }
 
     function test_format_string() {
+        global $CFG;
+
         // Ampersands
         $this->assertEqual(format_string("& &&&&& &&"), "&amp; &amp;&amp;&amp;&amp;&amp; &amp;&amp;");
         $this->assertEqual(format_string("ANother & &&&&& Category"), "ANother &amp; &amp;&amp;&amp;&amp;&amp; Category");
@@ -37,6 +39,21 @@ class web_test extends UnitTestCase {
 
         // Unicode entities
         $this->assertEqual(format_string("&#4475;"), "&#4475;");
+
+        // < and > signs
+        $originalformatstringstriptags = $CFG->formatstringstriptags;
+
+        $CFG->formatstringstriptags = false;
+        $this->assertEqual(format_string('x < 1'), 'x &lt; 1');
+        $this->assertEqual(format_string('x > 1'), 'x &gt; 1');
+        $this->assertEqual(format_string('x < 1 and x > 0'), 'x &lt; 1 and x &gt; 0');
+
+        $CFG->formatstringstriptags = true;
+        $this->assertEqual(format_string('x < 1'), 'x &lt; 1');
+        $this->assertEqual(format_string('x > 1'), 'x &gt; 1');
+        $this->assertEqual(format_string('x < 1 and x > 0'), 'x &lt; 1 and x &gt; 0');
+
+        $CFG->formatstringstriptags = $originalformatstringstriptags;
     }
 
     function test_s() {
index fadfe7c..b2db2b8 100644 (file)
@@ -465,6 +465,10 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
             }
         }
 
+        if (empty($module->cron)) {
+            $module->cron = 0;
+        }
+
         // all modules must have en lang pack
         if (!is_readable("$fullmod/lang/en/$mod.php")) {
             throw new plugin_defective_exception($component, 'Missing mandatory en language pack.');
@@ -508,7 +512,7 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
                 require_once("$fullmod/db/install.php");
                 // Set installation running flag, we need to recover after exception or error
                 set_config('installrunning', 1, $module->name);
-                $post_install_function = 'xmldb_'.$module->name.'_install';;
+                $post_install_function = 'xmldb_'.$module->name.'_install';
                 $post_install_function();
                 unset_config('installrunning', $module->name);
             }
@@ -542,7 +546,12 @@ function upgrade_plugins_modules($startcallback, $endcallback, $verbose) {
                 upgrade_mod_savepoint($result, $module->version, $mod, false);
             }
 
-        /// Upgrade various components
+            // update cron flag if needed
+            if ($currmodule->cron != $module->cron) {
+                $DB->set_field('modules', 'cron', $module->cron, array('name' => $module->name));
+            }
+
+            // Upgrade various components
             update_capabilities($component);
             log_update_descriptions($component);
             external_update_descriptions($component);
index eadfe5d..89bdce5 100644 (file)
@@ -1179,11 +1179,9 @@ function reset_text_filters_cache() {
  * need filter processing e.g. activity titles, post subjects,
  * glossary concepts.
  *
- * @global object
- * @global object
- * @global object
  * @staticvar bool $strcache
- * @param string $string The string to be filtered.
+ * @param string $string The string to be filtered. Should be plain text, expect
+ * possibly for multilang tags.
  * @param boolean $striplinks To strip any link in the result text.
                               Moodle 1.8 default changed from false to true! MDL-8713
  * @param array $options options array/object or courseid
@@ -1241,7 +1239,7 @@ function format_string($string, $striplinks = true, $options = NULL) {
 
     // If the site requires it, strip ALL tags from this string
     if (!empty($CFG->formatstringstriptags)) {
-        $string = strip_tags($string);
+        $string = str_replace(array('<', '>'), array('&lt;', '&gt;'), strip_tags($string));
 
     } else {
         // Otherwise strip just links if that is required (default)
diff --git a/mod/quiz/accessrule/upgrade.txt b/mod/quiz/accessrule/upgrade.txt
new file mode 100644 (file)
index 0000000..12f117b
--- /dev/null
@@ -0,0 +1,15 @@
+This files describes API changes for quiz access rule plugins.
+
+Overview of this plugin type at http://docs.moodle.org/dev/Quiz_access_rules
+
+
+=== 2.2 ===
+
+* This plugin type was new in Moodle 2.2!
+
+
+=== 2.3 ===
+
+* This plugin type now supports cron in the standard way. If required, Create a
+  lib.php file containing
+function quizaccess_mypluginname_cron() {};
index 8ed07e5..d57135c 100644 (file)
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="name"/>
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="name of the report, same as the directory name" PREVIOUS="id" NEXT="displayorder"/>
-        <FIELD NAME="displayorder" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="display order for report tabs" PREVIOUS="name" NEXT="lastcron"/>
-        <FIELD NAME="lastcron" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="timestamp when cron was last run for this report." PREVIOUS="displayorder" NEXT="cron"/>
-        <FIELD NAME="cron" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" COMMENT="0 if there is no cron for this report (default) or the time between crons otherwise." PREVIOUS="lastcron" NEXT="capability"/>
-        <FIELD NAME="capability" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Capability required to see this report. May be blank which means use the default of mod/quiz:viewreport. This is used when deciding which tabs to render." PREVIOUS="cron"/>
+        <FIELD NAME="displayorder" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" COMMENT="display order for report tabs" PREVIOUS="name" NEXT="capability"/>
+        <FIELD NAME="capability" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="Capability required to see this report. May be blank which means use the default of mod/quiz:viewreport. This is used when deciding which tabs to render." PREVIOUS="displayorder"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
index 367448e..92d7d98 100644 (file)
@@ -40,6 +40,36 @@ function xmldb_quiz_upgrade($oldversion) {
     // Moodle v2.2.0 release upgrade line
     // Put any upgrade step following this
 
+    if ($oldversion < 2011120700) {
+
+        // Define field lastcron to be dropped from quiz_reports
+        $table = new xmldb_table('quiz_reports');
+        $field = new xmldb_field('lastcron');
+
+        // Conditionally launch drop field lastcron
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+
+        // quiz savepoint reached
+        upgrade_mod_savepoint(true, 2011120700, 'quiz');
+    }
+
+    if ($oldversion < 2011120701) {
+
+        // Define field cron to be dropped from quiz_reports
+        $table = new xmldb_table('quiz_reports');
+        $field = new xmldb_field('cron');
+
+        // Conditionally launch drop field cron
+        if ($dbman->field_exists($table, $field)) {
+            $dbman->drop_field($table, $field);
+        }
+
+        // quiz savepoint reached
+        upgrade_mod_savepoint(true, 2011120701, 'quiz');
+    }
+
     return true;
 }
 
index 9e2bf23..dc24934 100644 (file)
@@ -435,14 +435,13 @@ function quiz_user_complete($course, $user, $mod, $quiz) {
 }
 
 /**
- * Function to be run periodically according to the moodle cron
- * This function searches for things that need to be done, such
- * as sending out mail, toggling flags etc ...
- *
- * @return bool true
+ * Quiz periodic clean-up tasks.
  */
 function quiz_cron() {
-    return true;
+
+    // Run cron for our sub-plugin types.
+    cron_execute_plugin_type('quiz', 'quiz reports');
+    cron_execute_plugin_type('quizaccess', 'quiz access rules');
 }
 
 /**
index 14d5d79..33d731f 100644 (file)
@@ -489,11 +489,11 @@ class mod_quiz_renderer extends plugin_renderer_base {
         $output .= $this->box_start();
 
         if ($message) {
-            $output .= html_writer('p', $message);
-            $output .= html_writer('p', get_string('windowclosing', 'quiz'));
+            $output .= html_writer::tag('p', $message);
+            $output .= html_writer::tag('p', get_string('windowclosing', 'quiz'));
             $delay = 5;
         } else {
-            $output .= html_writer('p', get_string('pleaseclose', 'quiz'));
+            $output .= html_writer::tag('p', get_string('pleaseclose', 'quiz'));
             $delay = 0;
         }
         $this->page->requires->js_function_call('M.mod_quiz.secure_window.close',
diff --git a/mod/quiz/report/statistics/cron.php b/mod/quiz/report/statistics/cron.php
deleted file mode 100644 (file)
index 35ebabc..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-<?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/>.
-
-/**
- * Quiz statistics report cron code.
- *
- * @package    quiz
- * @subpackage statistics
- * @copyright  2008 Jamie Pratt
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-
-/**
- * Quiz statistics report cron code. Deletes cached data more than a certain age.
- */
-function quiz_report_statistics_cron() {
-    global $DB;
-
-    $expiretime = time() - 5*HOURSECS;
-    $todelete = $DB->get_records_select_menu('quiz_statistics', 'timemodified < ?',
-            array($expiretime), '', 'id, 1');
-
-    if (!$todelete) {
-        return true;
-    }
-
-    list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
-
-    if (!$DB->delete_records_select('quiz_question_statistics',
-            'quizstatisticsid ' . $todeletesql, $todeleteparams)) {
-        mtrace('Error deleting out of date quiz_question_statistics records.');
-    }
-
-    if (!$DB->delete_records_select('quiz_question_response_stats',
-            'quizstatisticsid ' . $todeletesql, $todeleteparams)) {
-        mtrace('Error deleting out of date quiz_question_response_stats records.');
-    }
-
-    if (!$DB->delete_records_select('quiz_statistics',
-            'id ' . $todeletesql, $todeleteparams)) {
-        mtrace('Error deleting out of date quiz_statistics records.');
-    }
-
-    return true;
-}
index d127a59..24ab53f 100644 (file)
@@ -37,7 +37,6 @@ function xmldb_quiz_statistics_install() {
     $record = new stdClass();
     $record->name         = 'statistics';
     $record->displayorder = 8000;
-    $record->cron         = 18000;
     $record->capability   = 'quiz/statistics:view';
 
     if ($dbman->table_exists('quiz_reports')) {
index 06d7fd9..0f3e262 100644 (file)
@@ -24,6 +24,9 @@
  */
 
 
+defined('MOODLE_INTERNAL') || die();
+
+
 /**
  * Serve questiontext files in the question text when they are displayed in this report.
  * @param context $context the context
@@ -44,3 +47,31 @@ function quiz_statistics_questiontext_preview_pluginfile($context, $questionid,
 
     question_send_questiontext_file($questionid, $args, $forcedownload);
 }
+
+/**
+ * Quiz statistics report cron code. Deletes cached data more than a certain age.
+ */
+function quiz_statistics_cron() {
+    global $DB;
+
+    $expiretime = time() - 5*HOURSECS;
+    $todelete = $DB->get_records_select_menu('quiz_statistics',
+            'timemodified < ?', array($expiretime), '', 'id, 1');
+
+    if (!$todelete) {
+        return true;
+    }
+
+    list($todeletesql, $todeleteparams) = $DB->get_in_or_equal(array_keys($todelete));
+
+    $DB->delete_records_select('quiz_question_statistics',
+            'quizstatisticsid ' . $todeletesql, $todeleteparams);
+
+    $DB->delete_records_select('quiz_question_response_stats',
+            'quizstatisticsid ' . $todeletesql, $todeleteparams);
+
+    $DB->delete_records_select('quiz_statistics',
+            'id ' . $todeletesql, $todeleteparams);
+
+    return true;
+}
index 235a722..cfd14ce 100644 (file)
@@ -25,6 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version  = 2011062600;
-$plugin->requires = 2011060313;
+$plugin->version   = 2011062600;
+$plugin->requires  = 2011060313;
+$plugin->cron      = 18000;
 $plugin->component = 'quiz_statistics';
diff --git a/mod/quiz/report/upgrade.txt b/mod/quiz/report/upgrade.txt
new file mode 100644 (file)
index 0000000..c12e7ff
--- /dev/null
@@ -0,0 +1,22 @@
+This files describes API changes for quiz report plugins.
+
+Overview of this plugin type at http://docs.moodle.org/dev/Quiz_reports
+
+
+=== earlier versions ===
+
+* ... API changes were not documented properly. Sorry. (There weren't many!)
+
+
+=== 2.2 ===
+
+* Plugins should be converted to implement cron in the standard way. In lib.php,
+define a
+function quiz_myreportname_cron() {};
+This replaces the old way of having a separate cron.php file. Also, the cron
+frequency should be defined in version.php, not in the quiz_reports table.
+
+
+=== 2.3 ===
+
+* Support for the old way of doing cron in a separate cron.php file has been removed.
index 12deea0..33124f0 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2011112900;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2011120702;       // The current module version (Date: YYYYMMDDXX)
 $module->requires  = 2011112900;       // Requires this Moodle version
 $module->component = 'mod_quiz';       // Full name of the plugin (used for diagnostics)
-$module->cron      = 0;
+$module->cron      = 60;
index d2f9f68..a201cd4 100644 (file)
@@ -155,11 +155,9 @@ class scorm_basic_report extends scorm_default_report {
                 $table->sortable(true);
                 $table->collapsible(true);
 
+                // This is done to prevent redundant data, when a user has multiple attempts
                 $table->column_suppress('picture');
                 $table->column_suppress('fullname');
-                // I wonder why it is doing all this suppress malarkey?
-                // However, if it was suppressing idnumber field before, I guess
-                // it needs to suppress all the user identity fields now
                 foreach ($extrafields as $field) {
                     $table->column_suppress($field);
                 }
@@ -521,17 +519,18 @@ class scorm_basic_report extends scorm_default_report {
                         echo '</tr></table>';
                     }
                 }
-                if (!$download) {
-                    $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
-                    $mform->display();
-                }
             } else {
                 if ($candelete && !$download) {
                     echo '</div>';
                     echo '</form>';
+                    $table->finish_output();
                 }
                 echo '</div>';
-                echo $OUTPUT->notification(get_string('noactivity', 'scorm'));
+            }
+            // Show preferences form irrespective of attempts are there to report or not
+            if (!$download) {
+                $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
+                $mform->display();
             }
             if ($download == 'Excel' or $download == 'ODS') {
                 $workbook->close();
index 12e61ab..930d835 100644 (file)
@@ -211,9 +211,9 @@ class scorm_interactions_report extends scorm_default_report {
                 $table->sortable(true);
                 $table->collapsible(true);
 
+                // This is done to prevent redundant data, when a user has multiple attempts
                 $table->column_suppress('picture');
                 $table->column_suppress('fullname');
-                // This is done to prevent redundant data, when a user has multiple attempts
                 foreach ($extrafields as $field) {
                     $table->column_suppress($field);
                 }
@@ -583,17 +583,18 @@ class scorm_interactions_report extends scorm_default_report {
                         echo '</tr></table>';
                     }
                 }
-                if (!$download) {
-                    $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
-                    $mform->display();
-                }
             } else {
                 if ($candelete && !$download) {
                     echo '</div>';
                     echo '</form>';
+                    $table->finish_output();
                 }
                 echo '</div>';
-                echo $OUTPUT->notification(get_string('noactivity', 'scorm'));
+            }
+            // Show preferences form irrespective of attempts are there to report or not
+            if (!$download) {
+                $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
+                $mform->display();
             }
             if ($download == 'Excel' or $download == 'ODS') {
                 $workbook->close();
index 9144296..e85bc68 100644 (file)
@@ -119,7 +119,7 @@ class qbehaviour_adaptive extends question_behaviour_with_save {
         $status = $this->process_save($pendingstep);
 
         $response = $pendingstep->get_qt_data();
-        if (!$this->question->is_gradable_response($response)) {
+        if (!$this->question->is_complete_response($response)) {
             $pendingstep->set_state(question_state::$invalid);
             if ($this->qa->get_state() != question_state::$invalid) {
                 $status = question_attempt::KEEP;
index aa64f5b..6c415de 100644 (file)
@@ -23,6 +23,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['disregardedwithoutpenalty'] = 'The submission was invalid, and has been disregarded without penalty.';
 $string['gradingdetails'] = 'Marks for this submission: {$a->raw}/{$a->max}.';
 $string['gradingdetailsadjustment'] = 'Accounting for previous tries, this gives <strong>{$a->cur}/{$a->max}</strong>.';
 $string['gradingdetailspenalty'] = 'This submission attracted a penalty of {$a}.';
index 2bc4fdb..aa88f9b 100644 (file)
@@ -42,6 +42,16 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
     }
 
     public function feedback(question_attempt $qa, question_display_options $options) {
+        if ($qa->get_state() == question_state::$invalid) {
+            // If the latest answer was invalid, display an informative message
+            $output = '';
+            $info = $this->disregarded_info();
+            if ($info) {
+                $output = html_writer::tag('div', $info, array('class' => 'gradingdetails'));
+            }
+            return $output;
+        }
+
         // Try to find the last graded step.
 
         $gradedstep = $qa->get_behaviour()->get_graded_step($qa);
@@ -116,4 +126,12 @@ class qbehaviour_adaptive_renderer extends qbehaviour_renderer {
 
         return $output;
     }
+
+    /**
+     * Display information about a disregarded (incomplete) response.
+     */
+    protected function disregarded_info() {
+        return get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
+    }
+
 }
index 99727d4..5d5a498 100644 (file)
@@ -63,6 +63,16 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         return new NoPatternExpectation($penaltypattern);
     }
 
+    protected function get_contains_disregarded_info_expectation() {
+        $penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
+        return new PatternExpectation('/'.preg_quote($penaltyinfo).'/');
+    }
+
+    protected function get_does_not_contain_disregarded_info_expectation() {
+        $penaltyinfo = get_string('disregardedwithoutpenalty', 'qbehaviour_adaptive');
+        return new NoPatternExpectation('/'.preg_quote($penaltyinfo).'/');
+    }
+
     public function test_adaptive_multichoice() {
 
         // Create a multiple choice, single response question.
@@ -542,7 +552,8 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_does_not_contain_correctness_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
                 $this->get_does_not_contain_total_penalty_expectation(),
-                $this->get_contains_validation_error_expectation());
+                $this->get_contains_validation_error_expectation(),
+                $this->get_contains_disregarded_info_expectation());
         $this->assertNull($this->quba->get_response_summary($this->slot));
 
         // Now get it wrong.
@@ -568,7 +579,7 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
         $this->check_current_output(
                 $this->get_contains_mark_summary(0.8),
                 $this->get_contains_submit_button_expectation(true),
-                $this->get_contains_partcorrect_expectation(),
+                $this->get_does_not_contain_correctness_expectation(),
                 $this->get_does_not_contain_penalty_info_expectation(),
                 $this->get_does_not_contain_total_penalty_expectation(),
                 $this->get_contains_validation_error_expectation());
@@ -628,4 +639,108 @@ class qbehaviour_adaptive_walkthrough_test extends qbehaviour_walkthrough_test_b
                 $this->get_contains_incorrect_expectation(),
                 $this->get_does_not_contain_validation_error_expectation());
     }
+
+    public function test_adaptive_numerical_invalid() {
+
+        // Create a numerical question
+        $numq = test_question_maker::make_question('numerical', 'pi');
+        $numq->penalty = 0.1;
+        $this->start_attempt_at_question($numq, 'adaptive');
+
+        // Check the initial state.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+                $this->get_contains_marked_out_of_summary(),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_feedback_expectation());
+
+        // Submit a non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+                $this->get_contains_marked_out_of_summary(1),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_contains_disregarded_info_expectation());
+
+        // Submit an incorrect answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '-5'));
+
+        // Verify.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_contains_penalty_info_expectation(0.1),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_does_not_contain_validation_error_expectation(),
+                $this->get_does_not_contain_disregarded_info_expectation());
+
+        // Submit another non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi*2'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_contains_disregarded_info_expectation());
+
+        // Submit the correct answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(0.9);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.9),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_correct_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_does_not_contain_validation_error_expectation(),
+                $this->get_does_not_contain_disregarded_info_expectation());
+
+        // Submit another non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi/3'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(0.9);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.9),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_does_not_contain_penalty_info_expectation(),
+                $this->get_does_not_contain_total_penalty_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_contains_disregarded_info_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedwrong);
+        $this->check_current_mark(0.9);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0.9),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation(),
+                $this->get_does_not_contain_disregarded_info_expectation());
+    }
 }
index 60ca96b..d82d73b 100644 (file)
@@ -41,4 +41,7 @@ class qbehaviour_adaptivenopenalty_renderer extends qbehaviour_adaptive_renderer
     protected function penalty_info($qa, $mark) {
         return '';
     }
+    protected function disregarded_info() {
+        return '';
+    }
 }
index ecc45bd..44ec3fb 100644 (file)
@@ -38,6 +38,11 @@ require_once(dirname(__FILE__) . '/../../../engine/simpletest/helpers.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrough_test_base {
+
+    protected function get_does_not_contain_gradingdetails_expectation() {
+        return new NoPatternExpectation('/class="gradingdetails"/');
+    }
+
     public function test_multichoice() {
 
         // Create a multiple choice, single response question.
@@ -192,4 +197,95 @@ class qbehaviour_adaptivenopenalty_walkthrough_test extends qbehaviour_walkthrou
                 $this->get_contains_submit_button_expectation(false),
                 $this->get_contains_correct_expectation());
     }
+
+    public function test_numerical_invalid() {
+
+        // Create a numerical question
+        $numq = test_question_maker::make_question('numerical', 'pi');
+        $numq->penalty = 0.1;
+        $this->start_attempt_at_question($numq, 'adaptivenopenalty');
+
+        // Check the initial state.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+                $this->get_contains_marked_out_of_summary(),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_feedback_expectation());
+
+        // Submit a non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(null);
+        $this->check_current_output(
+                $this->get_contains_marked_out_of_summary(1),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_does_not_contain_feedback_expectation());
+
+        // Submit an incorrect answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '-5'));
+
+        // Verify.
+        $this->check_current_state(question_state::$todo);
+        $this->check_current_mark(0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+
+        // Submit another non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi*2'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_does_not_contain_gradingdetails_expectation());
+
+        // Submit the correct answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => '3.14'));
+
+        // Verify.
+        $this->check_current_state(question_state::$complete);
+        $this->check_current_mark(1.0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1.0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_contains_correct_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+
+        // Submit another non-numerical answer.
+        $this->process_submission(array('-submit' => 1, 'answer' => 'Pi/3'));
+
+        // Verify.
+        $this->check_current_state(question_state::$invalid);
+        $this->check_current_mark(1.0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1.0),
+                $this->get_contains_submit_button_expectation(true),
+                $this->get_does_not_contain_correctness_expectation(),
+                $this->get_contains_validation_error_expectation(),
+                $this->get_does_not_contain_gradingdetails_expectation());
+
+        // Finish the attempt.
+        $this->quba->finish_all_questions();
+
+        // Verify.
+        $this->check_current_state(question_state::$gradedwrong);
+        $this->check_current_mark(1.0);
+        $this->check_current_output(
+                $this->get_contains_mark_summary(1.0),
+                $this->get_contains_submit_button_expectation(false),
+                $this->get_contains_incorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
+    }
 }
index 63b941e..5f51d0a 100644 (file)
@@ -14,3 +14,10 @@ $plugin->dependencies = array(
 is_compatible_question method. You should change your behaviour to override the
 new method, not the old one. This change has been implemented in a
 backwards-compatible way, so behaviours will not break.
+
+
+=== 2.3 ===
+
+* This plugin type now supports cron in the standard way. If required, Create a
+  lib.php file containing
+function qbehaviour_mypluginname_cron() {};
index 3501a44..7f781a1 100644 (file)
@@ -1700,12 +1700,6 @@ function question_edit_setup($edittab, $baseurl, $requirecmid = false, $requirec
     return array($thispageurl, $contexts, $cmid, $cm, $module, $pagevars);
 }
 
-/**
- * Required for legacy reasons. Was originally global then changed to class static
- * as of Moodle 2.0
- */
-$QUESTION_EDITTABCAPS = question_edit_contexts::$caps;
-
 /**
  * Make sure user is logged in as required in this context.
  */
index 1516b9c..595dbff 100644 (file)
@@ -15,3 +15,10 @@ other plugin types.
 $string['pluginname'] = 'Aiken format';
 $string['pluginname_help'] = 'This is a simple format ...';
 $string['pluginname_link'] = 'qformat/aiken';
+
+
+=== 2.3 ===
+
+* This plugin type now supports cron in the standard way. If required, Create a
+  lib.php file containing
+function qformat_mypluginname_cron() {};
index 074bb47..8a3372c 100644 (file)
@@ -108,7 +108,8 @@ class qtype_match_renderer extends qtype_with_combined_feedback_renderer {
     protected function format_choices($question) {
         $choices = array();
         foreach ($question->get_choice_order() as $key => $choiceid) {
-            $choices[$key] = htmlspecialchars($question->choices[$choiceid]);
+            $choices[$key] = format_string($question->choices[$choiceid], true,
+                    array('context' => $question->contextid));
         }
         return $choices;
     }
index acaacd3..949d8d2 100644 (file)
@@ -1,5 +1,20 @@
 This files describes API changes for question type plugins.
 
+=== 2.0 ===
+
+* Lots of changes due to all the API changes in Moodle 2.0.
+
+* This plugin type now supports cron in the standard way. If required, Create a
+  lib.php file containing
+function qtype_mypluginname_cron() {};
+
+
+=== 2.1 ===
+
+* Lots of API changes due to the new question engine. See
+http://docs.moodle.org/dev/Developing_a_Question_Type#Converting_a_Moodle_2.0_question_type
+
+
 === 2.2 ===
 
 * The XML import/export base class has had some minor API changes. The
index 3c670c9..0e2f514 100644 (file)
@@ -6,7 +6,10 @@ $capabilities = array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
-            'user' => CAP_ALLOW
+            'coursecreator' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
         )
     )
 );
index a1878d8..3da60ab 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012010100;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'repository_coursefiles'; // Full name of the plugin (used for diagnostics)
index 364cf89..e6e0034 100644 (file)
@@ -6,7 +6,10 @@ $capabilities = array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
-            'user' => CAP_ALLOW
+            'coursecreator' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
         )
     )
 );
index 31d2a74..10cf700 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012010100;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'repository_filesystem'; // Full name of the plugin (used for diagnostics)
index 67b1a13..d390420 100644 (file)
@@ -2146,6 +2146,8 @@ function initialise_filepicker($args) {
     $return->return_types = $args->return_types;
     foreach ($repositories as $repository) {
         $meta = $repository->get_meta();
+        // Please note that the array keys for repositories are used within
+        // JavaScript a lot, the key NEEDS to be the repository id.
         $return->repositories[$repository->id] = $meta;
     }
     return $return;
index 2eaac0a..f8a26a1 100644 (file)
@@ -6,7 +6,10 @@ $capabilities = array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
-            'user' => CAP_ALLOW
+            'coursecreator' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
         )
     )
 );
index c777296..49a9d0d 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012010100;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'repository_local'; // Full name of the plugin (used for diagnostics)
index 5862778..a15f12f 100644 (file)
@@ -6,7 +6,10 @@ $capabilities = array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
         'archetypes' => array(
-            'user' => CAP_ALLOW
+            'coursecreator' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
         )
     )
 );
index e6118b5..de7e214 100644 (file)
@@ -26,6 +26,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2011112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012010100;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2011112900;        // Requires this Moodle version
 $plugin->component = 'repository_webdav'; // Full name of the plugin (used for diagnostics)
index 4981c01..d7c95c8 100644 (file)
@@ -518,6 +518,7 @@ body.tag .managelink {padding: 5px;}
 .filemanager-toolbar {margin: 5px 0;}
 .filemanager-toolbar a {border: 1px solid #AACCEE;background: #F4FAFF;color: black;padding: 3px;}
 .filemanager-toolbar a:hover {background: #FFFFFF;}
+.filemanager-toolbar .helplink a {border: 0px; background: transparent;}
 .fm-breadcrumb {margin:0;}
 .filemanager-container {padding: 5px;margin: 6px 0;background: #E9F4FF;border: #AACCEE 1px solid}
 .filemanager-container ul{margin:0;padding:0;}
@@ -531,6 +532,9 @@ body.tag .managelink {padding: 5px;}
 .fm-file-entry{border: 1px solid red;}
 .fm-operation {font-weight: bold;}
 
+.filemanager-container.dndupload-over,
+.filepicker-filelist.dndupload-over {background: #8EF947;}
+
 /*
  * Backup and Restore CSS
  */