Merge branch 'w02_MDL-31005_m23_contextlevelname' of git://github.com/skodak/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 9 Jan 2012 03:12:24 +0000 (16:12 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 9 Jan 2012 03:12:24 +0000 (16:12 +1300)
45 files changed:
admin/cli/install.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
backup/cc/cc112moodle.php
backup/cc/cc2moodle.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
lib/cronlib.php
lib/datalib.php
lib/moodlelib.php
lib/outputrenderers.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/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/upgrade.txt
repository/lib.php

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 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 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 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 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'));
+    }
 }
 
 /**
index 53df7d6..286afc1 100644 (file)
@@ -3607,7 +3607,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 +3671,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 +3797,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 +4283,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 7288f57..847fdf1 100644 (file)
@@ -2433,14 +2433,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);
     }
 
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');
 }
 
 /**
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..efa3ed7 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,37 @@ 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_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 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..39f8bfd 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2011112900;       // The current module version (Date: YYYYMMDDXX)
+$module->version   = 2011120701;       // 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;
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 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 67b1a13..786826b 100644 (file)
@@ -2146,7 +2146,7 @@ function initialise_filepicker($args) {
     $return->return_types = $args->return_types;
     foreach ($repositories as $repository) {
         $meta = $repository->get_meta();
-        $return->repositories[$repository->id] = $meta;
+        $return->repositories[] = $meta;
     }
     return $return;
 }