Merge branch 'wip-MDL-25290-m24-compact' of git://github.com/samhemelryk/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Sun, 14 Oct 2012 21:32:40 +0000 (23:32 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Sun, 14 Oct 2012 21:32:40 +0000 (23:32 +0200)
99 files changed:
admin/cli/install.php
admin/renderer.php
admin/tool/customlang/locallib.php
admin/tool/xmldb/actions/check_bigints/check_bigints.class.php
admin/tool/xmldb/lang/en/tool_xmldb.php
auth/cas/auth.php
auth/cas/config.html
auth/cas/lang/en/auth_cas.php
auth/db/auth.php
auth/ldap/auth.php
auth/ldap/config.html
auth/ldap/lang/en/auth_ldap.php
blocks/course_overview/renderer.php
config-dist.php
course/dndupload.js
course/dnduploadlib.php
course/lib.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/yui/dragdrop/dragdrop.js
enrol/ajax.php
enrol/cohort/tests/sync_test.php
enrol/instances.php
enrol/locallib.php
enrol/manual/ajax.php
enrol/manual/cli/sync.php
enrol/manual/db/messages.php [new file with mode: 0644]
enrol/manual/db/upgrade.php [new file with mode: 0644]
enrol/manual/edit.php
enrol/manual/edit_form.php
enrol/manual/lang/en/enrol_manual.php
enrol/manual/lib.php
enrol/manual/manage.php
enrol/manual/settings.php
enrol/manual/version.php
enrol/manual/yui/quickenrolment/assets/skins/sam/quickenrolment.css
enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css
enrol/yui/otherusersmanager/otherusersmanager.js
lang/en/enrol.php
lang/en/mimetypes.php
lib/db/install.xml
lib/dml/mysqli_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/editor/tinymce/adminlib.php
lib/editor/tinymce/plugins/dragmath/lang/en/tinymce_dragmath.php
lib/editor/tinymce/plugins/dragmath/lib.php
lib/editor/tinymce/plugins/dragmath/settings.php [new file with mode: 0644]
lib/editor/tinymce/plugins/dragmath/version.php
lib/editor/tinymce/plugins/moodleemoticon/lang/en/tinymce_moodleemoticon.php
lib/editor/tinymce/plugins/moodleemoticon/lib.php
lib/editor/tinymce/plugins/moodleemoticon/settings.php [new file with mode: 0644]
lib/editor/tinymce/plugins/moodleemoticon/version.php
lib/enrollib.php
lib/filelib.php
lib/filestorage/zip_archive.php
lib/formslib.php
lib/grade/grade_category.php
lib/grade/tests/grade_category_test.php
lib/javascript-static.js
lib/navigationlib.php
lib/outputlib.php
lib/outputrequirementslib.php
lib/phpunit/classes/util.php
lib/tests/outputcomponents_test.php
lib/tests/outputlib_test.php
message/renderer.php
mod/assign/feedback/offline/importgradesform.php
mod/assign/gradingoptionsform.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderer.php
mod/book/lang/en/book.php
mod/book/tool/importhtml/index.php
mod/book/view.php
mod/data/lib.php
mod/data/tests/fixtures/test_data_records.csv
mod/data/tests/search_test.php
mod/data/view.php
mod/quiz/styles.css
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/view.php
mod/upgrade.txt
pix/f/epub.png [new file with mode: 0644]
pix/i/test.png [new file with mode: 0644]
pix/i/test.svg [new file with mode: 0644]
question/type/multianswer/module.js
question/type/multianswer/renderer.php
repository/upload/lang/en/repository_upload.php
theme/formal_white/lang/en/theme_formal_white.php
theme/image.php
theme/serenity/style/core.css
theme/standard/style/core.css
user/selector/module.js
user/selector/search.php

index 79ff0b7..38aa06e 100644 (file)
@@ -240,18 +240,8 @@ echo get_string('cliinstallheader', 'install', $CFG->target_release)."\n";
 if ($interactive) {
     cli_separator();
     $languages = get_string_manager()->get_list_of_translations();
-    // format the langs nicely - 3 per line
-    $c = 0;
-    $langlist = '';
-    foreach ($languages as $key=>$lang) {
-        $c++;
-        $length = iconv_strlen($lang, 'UTF-8');
-        $padded = $lang.str_repeat(' ', 38-$length);
-        $langlist .= $padded;
-        if ($c % 3 == 0) {
-            $langlist .= "\n";
-        }
-    }
+    // Do not put the langs into columns because it is not compatible with RTL.
+    $langlist = implode("\n", $languages);
     $default = $CFG->lang;
     cli_heading(get_string('availablelangs', 'install'));
     echo $langlist."\n";
index 3a3c2bd..293bf52 100644 (file)
@@ -470,7 +470,7 @@ class core_admin_renderer extends plugin_renderer_base {
         $copyrighttext = '<a href="http://moodle.org/">Moodle</a> '.
                          '<a href="http://docs.moodle.org/dev/Releases" title="'.$CFG->version.'">'.$CFG->release.'</a><br />'.
                          'Copyright &copy; 1999 onwards, Martin Dougiamas<br />'.
-                         'and <a href="http://docs.moodle.org/dev/Credits">many other contributors</a>.<br />'.
+                         'and <a href="http://moodle.org/dev">many other contributors</a>.<br />'.
                          '<a href="http://docs.moodle.org/dev/License">GNU Public License</a>';
         //////////////////////////////////////////////////////////////////////////////////////////////////
 
@@ -683,7 +683,7 @@ class core_admin_renderer extends plugin_renderer_base {
                 $row = new html_table_row();
                 $row->attributes['class'] = 'type-' . $plugin->type . ' name-' . $plugin->type . '_' . $plugin->name;
 
-                if ($this->page->theme->resolve_image_location('icon', $plugin->type . '_' . $plugin->name)) {
+                if ($this->page->theme->resolve_image_location('icon', $plugin->type . '_' . $plugin->name, null)) {
                     $icon = $this->output->pix_icon('icon', '', $plugin->type . '_' . $plugin->name, array('class' => 'smallicon pluginicon'));
                 } else {
                     $icon = $this->output->pix_icon('spacer', '', 'moodle', array('class' => 'smallicon pluginicon noicon'));
index 47bbdfa..878eb4c 100644 (file)
@@ -488,7 +488,7 @@ class tool_customlang_translator implements renderable {
         list($insql, $inparams) = $DB->get_in_or_equal($filter->component, SQL_PARAMS_NAMED);
 
         $csql = "SELECT COUNT(*)";
-        $fsql = "SELECT s.id, s.*, c.name AS component";
+        $fsql = "SELECT s.*, c.name AS component";
         $sql  = "  FROM {tool_customlang_components} c
                    JOIN {tool_customlang} s ON s.componentid = c.id
                   WHERE s.lang = :lang
index ef7b622..69bfb84 100644 (file)
@@ -29,9 +29,6 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class check_bigints extends XMLDBCheckAction {
-    private $correct_type;
-    private $dbfamily;
-
     /**
      * Init method, every subclass will have its own
      */
@@ -51,19 +48,6 @@ class check_bigints extends XMLDBCheckAction {
             'nowrongintsfound' => 'tool_xmldb',
             'yeswrongintsfound' => 'tool_xmldb',
         ));
-
-        // Correct fields must be type bigint for MySQL and int8 for PostgreSQL
-        $this->dbfamily = $DB->get_dbfamily();
-        switch ($this->dbfamily) {
-            case 'mysql':
-                $this->correct_type = 'bigint';
-                break;
-            case 'postgres':
-                $this->correct_type = 'int8';
-                break;
-            default:
-                $this->correct_type = NULL;
-        }
     }
 
     protected function check_table(xmldb_table $xmldb_table, array $metacolumns) {
@@ -75,19 +59,24 @@ class check_bigints extends XMLDBCheckAction {
             $o.='        <ul>';
             foreach ($xmldb_fields as $xmldb_field) {
                 // If the field isn't integer(10), skip
-                if ($xmldb_field->getType() != XMLDB_TYPE_INTEGER || $xmldb_field->getLength() != 10) {
+                if ($xmldb_field->getType() != XMLDB_TYPE_INTEGER) {
                     continue;
                 }
                 // If the metadata for that column doesn't exist, skip
                 if (!isset($metacolumns[$xmldb_field->getName()])) {
                     continue;
                 }
+                $minlength = $xmldb_field->getLength();
+                if ($minlength > 18) {
+                    // Anything above 18 is borked, just ignore it here.
+                    $minlength = 18;
+                }
                 // To variable for better handling
                 $metacolumn = $metacolumns[$xmldb_field->getName()];
                 // Going to check this field in DB
                 $o.='            <li>' . $this->str['field'] . ': ' . $xmldb_field->getName() . ' ';
                 // Detect if the physical field is wrong
-                if ($metacolumn->type != $this->correct_type) {
+                if (($metacolumn->meta_type != 'I' and $metacolumn->meta_type != 'R') or $metacolumn->max_length < $minlength) {
                     $o.='<font color="red">' . $this->str['wrong'] . '</font>';
                     // Add the wrong field to the list
                     $obj = new stdClass();
@@ -124,17 +113,7 @@ class check_bigints extends XMLDBCheckAction {
             foreach ($wrong_fields as $obj) {
                 $xmldb_table = $obj->table;
                 $xmldb_field = $obj->field;
-                // MySQL directly supports this
-
-// TODO: move this hack to generators!!
-
-                if ($this->dbfamily == 'mysql') {
-                    $sqlarr = $dbman->generator->getAlterFieldSQL($xmldb_table, $xmldb_field);
-                // PostgreSQL (XMLDB implementation) is a bit, er... imperfect.
-                } else if ($this->dbfamily == 'postgres') {
-                    $sqlarr = array('ALTER TABLE ' . $DB->get_prefix() . $xmldb_table->getName() .
-                              ' ALTER COLUMN ' . $xmldb_field->getName() . ' TYPE BIGINT;');
-                }
+                $sqlarr = $dbman->generator->getAlterFieldSQL($xmldb_table, $xmldb_field);
                 $r.= '            <li>' . $this->str['table'] . ': ' . $xmldb_table->getName() . '. ' .
                                           $this->str['field'] . ': ' . $xmldb_field->getName() . '</li>';
                 // Add to output if we have sentences
index 7b384b2..4dfc221 100644 (file)
@@ -101,7 +101,7 @@ $string['generate_documentation'] = 'Documentation';
 $string['gotolastused'] = 'Go to last used file';
 $string['change'] = 'Change';
 $string['charincorrectlength'] = 'Incorrect length for char field';
-$string['checkbigints'] = 'Check bigints';
+$string['checkbigints'] = 'Check integers';
 $string['check_bigints'] = 'Look for incorrect DB integers';
 $string['checkdefaults'] = 'Check defaults';
 $string['check_defaults'] = 'Look for inconsistent default values';
@@ -111,10 +111,13 @@ $string['checkindexes'] = 'Check indexes';
 $string['check_indexes'] = 'Look for missing DB indexes';
 $string['checkoraclesemantics'] = 'Check semantics';
 $string['check_oracle_semantics'] = 'Look for incorrect length semantics';
+$string['duplicateindexname'] = 'Duplicate index name';
 $string['incorrectfieldname'] = 'Incorrect name';
 $string['index'] = 'Index';
 $string['indexes'] = 'Indexes';
+$string['indexnameempty'] = 'Index name is empty';
 $string['integerincorrectlength'] = 'Incorrect length for integer field';
+$string['incorrectindexname'] = 'Incorrect index name';
 $string['incorrectkeyname'] = 'Incorrect key name';
 $string['incorrecttablename'] = 'Incorrect table name';
 $string['key'] = 'Key';
index 94ea981..ce84377 100644 (file)
@@ -268,6 +268,9 @@ class auth_plugin_cas extends auth_plugin_ldap {
         if (!isset($config->certificate_path)) {
             $config->certificate_path = '';
         }
+        if (!isset($config->logout_return_url)) {
+            $config->logout_return_url = '';
+        }
 
         // LDAP settings
         if (!isset($config->host_url)) {
@@ -331,6 +334,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
         set_config('multiauth', $config->multiauth, $this->pluginconfig);
         set_config('certificate_check', $config->certificate_check, $this->pluginconfig);
         set_config('certificate_path', $config->certificate_path, $this->pluginconfig);
+        set_config('logout_return_url', $config->logout_return_url, $this->pluginconfig);
 
         // save LDAP settings
         set_config('host_url', trim($config->host_url), $this->pluginconfig);
@@ -439,4 +443,19 @@ class auth_plugin_cas extends auth_plugin_ldap {
         }
         parent::sync_users($do_updates);
     }
+
+    /**
+    * Hook for logout page
+    */
+    function logoutpage_hook() {
+        global $USER, $redirect;
+        // Only do this if the user is actually logged in via CAS
+        if ($USER->auth === $this->authtype) {
+            // Check if there is an alternative logout return url defined
+            if (isset($this->config->logout_return_url) && !empty($this->config->logout_return_url)) {
+                // Set redirect to alternative return url
+                $redirect = $this->config->logout_return_url;
+            }
+        }
+    }
 }
index 28557dd..031df6e 100644 (file)
@@ -33,6 +33,9 @@ if (!isset ($config->certificate_check)) {
 if (!isset ($config->certificate_path)) {
     $config->certificate_path = '';
 }
+if (!isset($config->logout_return_url)) {
+    $config->logout_return_url = '';
+}
 
 // set to defaults if undefined (LDAP)
 if (!isset($config->host_url)) {
@@ -201,6 +204,16 @@ $yesno = array( get_string('no'), get_string('yes') );
         <?php print_string('auth_cas_certificate_path', 'auth_cas') ?>
     </td>
 </tr>
+<tr valign="top" class="required">
+    <td align="right"><?php print_string('auth_cas_logout_return_url_key', 'auth_cas') ?>:</td>
+    <td>
+       <input name="logout_return_url" type="text" size="30" value="<?php echo $config->logout_return_url ?>" />
+       <?php if (isset($err['logout_return_url'])) { echo $OUTPUT->error_text($err['logout_return_url']); } ?>
+    </td>
+    <td>
+        <?php print_string('auth_cas_logout_return_url', 'auth_cas') ?>
+    </td>
+</tr>
 <tr>
    <td colspan="2">
         <h4><?php print_string('auth_ldap_server_settings', 'auth_ldap') ?></h4>
index 15cb616..70a82a0 100644 (file)
@@ -47,6 +47,8 @@ $string['auth_cas_invalidcaslogin'] = 'Sorry, your login has failed - you could
 $string['auth_cas_language'] = 'Select language for authentication pages';
 $string['auth_cas_language_key'] = 'Language';
 $string['auth_cas_logincas'] = 'Secure connection access';
+$string['auth_cas_logout_return_url_key'] = 'Alternative logout return URL';
+$string['auth_cas_logout_return_url'] = 'Provide the URL that CAS users shall be redirected to after logging out.<br />If left empty, users will be redirected to the location that moodle will redirect users to';
 $string['auth_cas_logoutcas'] = 'Select \'yes\' if you want to logout from CAS when you disconnect from Moodle';
 $string['auth_cas_logoutcas_key'] = 'CAS logout option';
 $string['auth_cas_multiauth'] = 'Select \'yes\' if you want to have multi-authentication (CAS + other authentication)';
index 60babc8..4c95249 100644 (file)
@@ -46,12 +46,22 @@ class auth_plugin_db extends auth_plugin_base {
         $extusername = textlib::convert($username, 'utf-8', $this->config->extencoding);
         $extpassword = textlib::convert($password, 'utf-8', $this->config->extencoding);
 
-        $authdb = $this->db_init();
-
         if ($this->is_internal()) {
             // lookup username externally, but resolve
             // password locally -- to support backend that
             // don't track passwords
+
+            if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
+                // No need to connect to external database in this case because users are never removed and we verify password locally.
+                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
+                    return validate_internal_user_password($user, $password);
+                } else {
+                    return false;
+                }
+            }
+
+            $authdb = $this->db_init();
+
             $rs = $authdb->Execute("SELECT * FROM {$this->config->table}
                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
             if (!$rs) {
@@ -78,6 +88,8 @@ class auth_plugin_db extends auth_plugin_base {
         } else {
             // normal case: use external db for both usernames and passwords
 
+            $authdb = $this->db_init();
+
             if ($this->config->passtype === 'md5') {   // Re-format password accordingly
                 $extpassword = md5($extpassword);
             } else if ($this->config->passtype === 'sha1') {
index 049fd97..a90a41b 100644 (file)
@@ -41,6 +41,18 @@ if (!defined('AUTH_GID_NOGROUP')) {
     define('AUTH_GID_NOGROUP', -2);
 }
 
+// Regular expressions for a valid NTLM username and domain name.
+if (!defined('AUTH_NTLM_VALID_USERNAME')) {
+    define('AUTH_NTLM_VALID_USERNAME', '[^/\\\\\\\\\[\]:;|=,+*?<>@"]+');
+}
+if (!defined('AUTH_NTLM_VALID_DOMAINNAME')) {
+    define('AUTH_NTLM_VALID_DOMAINNAME', '[^\\\\\\\\\/:*?"<>|]+');
+}
+// Default format for remote users if using NTLM SSO
+if (!defined('AUTH_NTLM_DEFAULT_FORMAT')) {
+    define('AUTH_NTLM_DEFAULT_FORMAT', '%domain%\\%username%');
+}
+
 require_once($CFG->libdir.'/authlib.php');
 require_once($CFG->libdir.'/ldaplib.php');
 
@@ -1570,8 +1582,11 @@ class auth_plugin_ldap extends auth_plugin_base {
 
             switch ($this->config->ntlmsso_type) {
                 case 'ntlm':
-                    // Format is DOMAIN\username
-                    $username = substr(strrchr($username, '\\'), 1);
+                    // The format is now configurable, so try to extract the username
+                    $username = $this->get_ntlm_remote_user($username);
+                    if (empty($username)) {
+                        return false;
+                    }
                     break;
                 case 'kerberos':
                     // Format is username@DOMAIN
@@ -1779,6 +1794,9 @@ class auth_plugin_ldap extends auth_plugin_base {
         if (!isset($config->ntlmsso_type)) {
             $config->ntlmsso_type = 'ntlm';
         }
+        if (!isset($config->ntlmsso_remoteuserformat)) {
+            $config->ntlmsso_remoteuserformat = '';
+        }
 
         // Try to remove duplicates before storing the contexts (to avoid problems in sync_users()).
         $config->contexts = explode(';', $config->contexts);
@@ -1818,6 +1836,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         set_config('ntlmsso_subnet', trim($config->ntlmsso_subnet), $this->pluginconfig);
         set_config('ntlmsso_ie_fastpath', (int)$config->ntlmsso_ie_fastpath, $this->pluginconfig);
         set_config('ntlmsso_type', $config->ntlmsso_type, 'auth/ldap');
+        set_config('ntlmsso_remoteuserformat', trim($config->ntlmsso_remoteuserformat), 'auth/ldap');
 
         return true;
     }
@@ -2018,4 +2037,60 @@ class auth_plugin_ldap extends auth_plugin_base {
                                 $this->config->user_attribute, $this->config->search_sub);
     }
 
+
+    /**
+     * A chance to validate form data, and last chance to do stuff
+     * before it is inserted in config_plugin
+     *
+     * @param object object with submitted configuration settings (without system magic quotes)
+     * @param array $err array of error messages (passed by reference)
+     */
+    function validate_form($form, &$err) {
+        if ($form->ntlmsso_type == 'ntlm') {
+            $format = trim($form->ntlmsso_remoteuserformat);
+            if (!empty($format) && !preg_match('/%username%/i', $format)) {
+                $err['ntlmsso_remoteuserformat'] = get_string('auth_ntlmsso_missing_username', 'auth_ldap');
+            }
+        }
+    }
+
+
+    /**
+     * When using NTLM SSO, the format of the remote username we get in
+     * $_SERVER['REMOTE_USER'] may vary, depending on where from and how the web
+     * server gets the data. So we let the admin configure the format using two
+     * place holders (%domain% and %username%). This function tries to extract
+     * the username (stripping the domain part and any separators if they are
+     * present) from the value present in $_SERVER['REMOTE_USER'], using the
+     * configured format.
+     *
+     * @param string $remoteuser The value from $_SERVER['REMOTE_USER'] (converted to UTF-8)
+     *
+     * @return string The remote username (without domain part or
+     *                separators). Empty string if we can't extract the username.
+     */
+    protected function get_ntlm_remote_user($remoteuser) {
+        if (empty($this->config->ntlmsso_remoteuserformat)) {
+            $format = AUTH_NTLM_DEFAULT_FORMAT;
+        } else {
+            $format = $this->config->ntlmsso_remoteuserformat;
+        }
+
+        $format = preg_quote($format);
+        $formatregex = preg_replace(array('#%domain%#', '#%username%#'),
+                                    array('('.AUTH_NTLM_VALID_DOMAINNAME.')', '('.AUTH_NTLM_VALID_USERNAME.')'),
+                                    $format);
+        if (preg_match('#^'.$formatregex.'$#', $remoteuser, $matches)) {
+            $user = end($matches);
+            return $user;
+        }
+
+        /* We are unable to extract the username with the configured format. Probably
+         * the format specified is wrong, so log a warning for the admin and return
+         * an empty username.
+         */
+        error_log($this->errorlogtag.get_string ('auth_ntlmsso_maybeinvalidformat', 'auth_ldap'));
+        return '';
+    }
+
 } // End of the class
index 8ad27ef..3efe253 100644 (file)
@@ -94,6 +94,9 @@ if (!isset($config->ntlmsso_ie_fastpath)) {
 if (!isset($config->ntlmsso_type)) {
     $config->ntlmsso_type = 'ntlm';
 }
+if (!isset($config->ntlmsso_remoteuserformat)) {
+    $config->ntlmsso_remoteuserformat = '';
+}
 
 $yesno = array(get_string('no'), get_string('yes'));
 
@@ -539,6 +542,18 @@ $yesno = array(get_string('no'), get_string('yes'));
         <?php print_string('auth_ntlmsso_type','auth_ldap') ?>
     </td>
 </tr>
+<tr valign="top">
+    <td align="right">
+        <label for="ntlmsso_remoteuserformat"><?php print_string('auth_ntlmsso_remoteuserformat_key', 'auth_ldap') ?></label>
+    </td>
+    <td>
+        <input name="ntlmsso_remoteuserformat" id="ntlmsso_remoteuserformat" type="text" size="30" value="<?php echo $config->ntlmsso_remoteuserformat?>" />
+        <?php if (isset($err['ntlmsso_remoteuserformat'])) { echo $OUTPUT->error_text($err['ntlmsso_remoteuserformat']); } ?>
+    </td>
+    <td>
+        <?php print_string('auth_ntlmsso_remoteuserformat', 'auth_ldap') ?>
+    </td>
+</tr>
 <?php
 $help  = get_string('auth_ldapextrafields', 'auth_ldap');
 $help .= get_string('auth_updatelocal_expl', 'auth');
index bbb0ddb..848169a 100644 (file)
@@ -101,6 +101,10 @@ $string['auth_ntlmsso_enabled'] = 'Set to yes to attempt Single Sign On with the
 $string['auth_ntlmsso_enabled_key'] = 'Enable';
 $string['auth_ntlmsso_ie_fastpath'] = 'Set to yes to enable the NTLM SSO fast path (bypasses certain steps and only works if the client\'s browser is MS Internet Explorer).';
 $string['auth_ntlmsso_ie_fastpath_key'] = 'MS IE fast path?';
+$string['auth_ntlmsso_maybeinvalidformat'] = 'Unable to extract the username from the REMOTE_USER header. Is the configured format right?';
+$string['auth_ntlmsso_missing_username'] = 'You need to specify at least %username% in the remote username format';
+$string['auth_ntlmsso_remoteuserformat_key'] = 'Remote username format';
+$string['auth_ntlmsso_remoteuserformat'] = 'If you have chosen \'NTLM\' in \'Authentication type\', you can specify the remote username format here. If you leave this empty, the default DOMAIN\\username format will be used. You can use the optional <b>%domain%</b> placeholder to specify where the domain name appears, and the mandatory <b>%username%</b> placeholder to specify where the username appears. <br /><br />Some widely used formats are <tt>%domain%\\%username%</tt> (MS Windows default), <tt>%domain%/%username%</tt>, <tt>%domain%+%username%</tt> and just <tt>%username%</tt> (if there is no domain part).';
 $string['auth_ntlmsso_subnet'] = 'If set, it will only attempt SSO with clients in this subnet. Format: xxx.xxx.xxx.xxx/bitmask. Separate multiple subnets with \',\' (comma).';
 $string['auth_ntlmsso_subnet_key'] = 'Subnet';
 $string['auth_ntlmsso_type_key'] = 'Authentication type';
index 7c19c9e..a83e51f 100644 (file)
@@ -111,7 +111,7 @@ class block_course_overview_renderer extends plugin_renderer_base {
             } else {
                 $html .= $this->output->heading(html_writer::link(
                     new moodle_url('/auth/mnet/jump.php', array('hostid' => $course->hostid, 'wantsurl' => '/course/view.php?id='.$course->remoteid)),
-                    format_string($course->shortname, true, $course->id), $attributes) . ' (' . format_string($course->hostname) . ')', 2, 'title');
+                    format_string($course->shortname, true), $attributes) . ' (' . format_string($course->hostname) . ')', 2, 'title');
             }
             $html .= $this->output->box('', 'flush');
             $html .= html_writer::end_tag('div');
index 0f7298c..2e38635 100644 (file)
@@ -453,6 +453,18 @@ $CFG->admin = 'admin';
 //
 //      $CFG->disableupdatenotifications = true;
 //
+// As of version 2.4 Moodle serves icons as SVG images if the users browser appears
+// to support SVG.
+// For those wanting to control the serving of SVG images the following setting can
+// be defined in your config.php.
+// If it is not defined then the default (browser detection) will occur.
+//
+// To ensure they are always used when available:
+//      $CFG->svgicons = true;
+//
+// To ensure they are never used even when available:
+//      $CFG->svgicons = false;
+//
 //=========================================================================
 // 8. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
index 7bfef1d..2dcaa13 100644 (file)
@@ -445,6 +445,7 @@ M.course_dndupload = {
             a: document.createElement('a'),
             icon: document.createElement('img'),
             namespan: document.createElement('span'),
+            groupingspan: document.createElement('span'),
             progressouter: document.createElement('span'),
             progress: document.createElement('span')
         };
@@ -469,6 +470,9 @@ M.course_dndupload = {
 
         resel.div.appendChild(document.createTextNode(' '));
 
+        resel.groupingspan.className = 'groupinglabel';
+        resel.div.appendChild(resel.groupingspan);
+
         resel.progressouter.className = 'dndupload-progress-outer';
         resel.progress.className = 'dndupload-progress-inner';
         resel.progress.innerHTML = '&nbsp;';
@@ -724,6 +728,13 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+
+                            if (result.groupingname) {
+                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            } else {
+                                resel.div.removeChild(resel.groupingspan);
+                            }
+
                             resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
                             resel.div.innerHTML += result.commands;
@@ -904,6 +915,13 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+
+                            if (result.groupingname) {
+                                resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
+                            } else {
+                                resel.div.removeChild(resel.groupingspan);
+                            }
+
                             resel.div.removeChild(resel.progressouter);
                             resel.li.id = result.elementid;
                             resel.div.innerHTML += result.commands;
index 816924b..038c09b 100644 (file)
@@ -667,6 +667,12 @@ class dndupload_ajax_processor {
         $resp->commands = make_editing_buttons($mod, true, true, 0, $mod->sectionnum);
         $resp->onclick = $mod->get_on_click();
 
+        // if using groupings, then display grouping name
+        if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
+            $groupings = groups_get_all_groupings($this->course->id);
+            $resp->groupingname = format_string($groupings[$mod->groupingid]->name);
+        }
+
         echo $OUTPUT->header();
         echo json_encode($resp);
         die();
index 3f69a58..72d94e9 100644 (file)
@@ -603,7 +603,8 @@ function print_log_csv($course, $user, $date, $order='l.time DESC', $modname,
         $coursecontext = context_course::instance($course->id);
         $firstField = format_string($courses[$log->course], true, array('context' => $coursecontext));
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
-        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action, $log->info);
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);\r
+        $row = array($firstField, userdate($log->time, $strftimedatetime), $log->ip, $fullname, $log->module.' '.$log->action.' ('.$actionurl.')', $log->info);
         $csvexporter->add_data($row);
     }
     $csvexporter->download_file();
@@ -714,7 +715,8 @@ function print_log_xls($course, $user, $date, $order='l.time DESC', $modname,
         $myxls->write($row, 2, $log->ip, '');
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
         $myxls->write($row, 3, $fullname, '');
-        $myxls->write($row, 4, $log->module.' '.$log->action, '');
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')', '');
         $myxls->write($row, 5, $log->info, '');
 
         $row++;
@@ -827,7 +829,8 @@ function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
         $myxls->write_string($row, 2, $log->ip);
         $fullname = fullname($log, has_capability('moodle/site:viewfullnames', $coursecontext));
         $myxls->write_string($row, 3, $fullname);
-        $myxls->write_string($row, 4, $log->module.' '.$log->action);
+        $actionurl = $CFG->wwwroot. make_log_url($log->module,$log->url);
+        $myxls->write_string($row, 4, $log->module.' '.$log->action.' ('.$actionurl.')');
         $myxls->write_string($row, 5, $log->info);
 
         $row++;
@@ -1577,7 +1580,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     // Display link itself
                     echo '<a ' . $linkcss . $mod->extra . $onclick .
                             ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
-                            '" class="activityicon" alt="" /> ' .
+                            '" class="activityicon" alt="' . $modulename . '" /> ' .
                             $accesstext . '<span class="instancename">' .
                             $instancename . $altname . '</span></a>';
 
index 0004f7d..87fa03d 100644 (file)
@@ -70,7 +70,7 @@ class courselib_testcase extends advanced_testcase {
         $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
         $oldsections = array();
         $sections = array();
-        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
+        foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
             $oldsections[$section->section] = $section->id;
             $sections[$section->id] = $section->section;
         }
index 605c5d8..984b9ed 100644 (file)
@@ -580,6 +580,5 @@ class core_course_external_testcase extends externallib_advanced_testcase {
 
         // Check that the course has been duplicated.
         $this->assertEquals($newcourse['shortname'], $duplicate['shortname']);
-        gc_collect_cycles();
     }
 }
index fc93fbe..069dbcb 100644 (file)
@@ -294,13 +294,13 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                     var resources = Y.Node.create('<ul></ul>');
                     resources.addClass(CSS.SECTION);
                     sectionnode.one('.'+CSS.CONTENT+' div.'+CSS.SUMMARY).insert(resources, 'after');
-                    // Define empty ul as droptarget, so that item could be moved to empty list
-                    var tar = new Y.DD.Drop({
-                        node: resources,
-                        groups: this.groups,
-                        padding: '20 0 20 0'
-                    });
                 }
+                // Define empty ul as droptarget, so that item could be moved to empty list
+                var tar = new Y.DD.Drop({
+                    node: resources,
+                    groups: this.groups,
+                    padding: '20 0 20 0'
+                });
 
                 // Initialise each resource/activity in this section
                 this.setup_for_resource('#'+sectionnode.get('id')+' li.'+CSS.ACTIVITY);
index ede3c5a..0b0ee31 100644 (file)
@@ -54,11 +54,13 @@ echo $OUTPUT->header(); // send headers
 
 $manager = new course_enrolment_manager($PAGE, $course);
 
-$outcome = new stdClass;
+$outcome = new stdClass();
 $outcome->success = true;
-$outcome->response = new stdClass;
+$outcome->response = new stdClass();
 $outcome->error = '';
 
+$searchanywhere = get_user_preferences('userselector_searchanywhere', false);
+
 switch ($action) {
     case 'unenrol':
         $ue = $DB->get_record('user_enrolments', array('id'=>required_param('ue', PARAM_INT)), '*', MUST_EXIST);
@@ -90,15 +92,26 @@ switch ($action) {
         $outcome->response = array_reverse($manager->get_assignable_roles($otheruserroles), true);
         break;
     case 'searchotherusers':
-        $search  = optional_param('search', '', PARAM_RAW);
+        $search = optional_param('search', '', PARAM_RAW);
         $page = optional_param('page', 0, PARAM_INT);
-        $outcome->response = $manager->search_other_users($search, false, $page);
+        $outcome->response = $manager->search_other_users($search, $searchanywhere, $page);
+        $extrafields = get_extra_user_fields($context);
         foreach ($outcome->response['users'] as &$user) {
             $user->userId = $user->id;
             $user->picture = $OUTPUT->user_picture($user);
             $user->fullname = fullname($user);
+            $fieldvalues = array();
+            foreach ($extrafields as $field) {
+                $fieldvalues[] = s($user->{$field});
+                unset($user->{$field});
+            }
+            $user->extrafields = implode(', ', $fieldvalues);
             unset($user->id);
         }
+        // Chrome will display users in the order of the array keys, so we need
+        // to ensure that the results ordered array keys. Fortunately, the JavaScript
+        // does not care what the array keys are. It uses user.id where necessary.
+        $outcome->response['users'] = array_values($outcome->response['users']);
         $outcome->success = true;
         break;
     default:
index 9656e21..75fc773 100644 (file)
@@ -233,6 +233,27 @@ class enrol_cohort_testcase extends advanced_testcase {
 
         cohort_remove_member($cohort1->id, $user1->id);
         $this->assertTrue(groups_is_member($group1->id, $user1->id));
+
+
+        // Test deleting of instances.
+
+        cohort_add_member($cohort1->id, $user1->id);
+        cohort_add_member($cohort1->id, $user2->id);
+        cohort_add_member($cohort1->id, $user3->id);
+
+        $this->assertEquals(7, $DB->count_records('user_enrolments', array()));
+        $this->assertEquals(5, $DB->count_records('role_assignments', array()));
+        $this->assertEquals(3, $DB->count_records('role_assignments', array('component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id)));
+        $this->assertEquals(5, $DB->count_records('groups_members', array()));
+        $this->assertEquals(3, $DB->count_records('groups_members', array('component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id)));
+
+        $cohortplugin->delete_instance($cohortinstance1);
+
+        $this->assertEquals(4, $DB->count_records('user_enrolments', array()));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array()));
+        $this->assertEquals(0, $DB->count_records('role_assignments', array('component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id)));
+        $this->assertEquals(2, $DB->count_records('groups_members', array()));
+        $this->assertEquals(0, $DB->count_records('groups_members', array('component'=>'enrol_cohort', 'itemid'=>$cohortinstance1->id)));
     }
 
     public function test_sync_course() {
index 8b8d558..ac4db48 100644 (file)
@@ -105,7 +105,11 @@ if ($canconfig and $action and confirm_sesskey()) {
             $yesurl = new moodle_url('/enrol/instances.php', array('id'=>$course->id, 'action'=>'delete', 'instance'=>$instance->id, 'confirm'=>1,'sesskey'=>sesskey()));
             $displayname = $plugin->get_instance_name($instance);
             $users = $DB->count_records('user_enrolments', array('enrolid'=>$instance->id));
-            $message = get_string('deleteinstanceconfirm', 'enrol', array('name'=>$displayname, 'users'=>$users));
+            if ($users) {
+                $message = markdown_to_html(get_string('deleteinstanceconfirm', 'enrol', array('name'=>$displayname, 'users'=>$users)));
+            } else {
+                $message = markdown_to_html(get_string('deleteinstancenousersconfirm', 'enrol', array('name'=>$displayname)));
+            }
             echo $OUTPUT->confirm($message, $yesurl, $PAGE->url);
             echo $OUTPUT->footer();
             die();
index 66a2381..2ee9a7b 100644 (file)
@@ -265,17 +265,16 @@ class course_enrolment_manager {
     }
 
     /**
-     * Gets an array of the users that can be enrolled in this course.
+     * Helper method used by {@link get_potential_users()} and {@link search_other_users()}.
      *
-     * @global moodle_database $DB
-     * @param int $enrolid
-     * @param string $search
-     * @param bool $searchanywhere
-     * @param int $page Defaults to 0
-     * @param int $perpage Defaults to 25
-     * @return array Array(totalusers => int, users => array)
-     */
-    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25) {
+     * @param string $search the search term, if any.
+     * @param bool $searchanywhere Can the search term be anywhere, or must it be at the start.
+     * @return array with three elements:
+     *     string list of fields to SELECT,
+     *     string contents of SQL WHERE clause,
+     *     array query params. Note that the SQL snippets use named parameters.
+     */
+    protected function get_basic_search_conditions($search, $searchanywhere) {
         global $DB, $CFG;
 
         // Add some additional sensible conditions
@@ -283,15 +282,17 @@ class course_enrolment_manager {
         $params = array('guestid' => $CFG->siteguest);
         if (!empty($search)) {
             $conditions = get_extra_user_fields($this->get_context());
-            $conditions[] = $DB->sql_concat('u.firstname', "' '", 'u.lastname');
+            $conditions[] = 'u.firstname';
+            $conditions[] = 'u.lastname';
+            $conditions[] = $DB->sql_fullname('u.firstname', 'u.lastname');
             if ($searchanywhere) {
                 $searchparam = '%' . $search . '%';
             } else {
                 $searchparam = $search . '%';
             }
             $i = 0;
-            foreach ($conditions as $key=>$condition) {
-                $conditions[$key] = $DB->sql_like($condition,":con{$i}00", false);
+            foreach ($conditions as $key => $condition) {
+                $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false);
                 $params["con{$i}00"] = $searchparam;
                 $i++;
             }
@@ -304,6 +305,52 @@ class course_enrolment_manager {
         $extrafields[] = 'lastaccess';
         $ufields = user_picture::fields('u', $extrafields);
 
+        return array($ufields, $params, $wherecondition);
+    }
+
+    /**
+     * Helper method used by {@link get_potential_users()} and {@link search_other_users()}.
+     *
+     * @param string $search the search string, if any.
+     * @param string $fields the first bit of the SQL when returning some users.
+     * @param string $countfields fhe first bit of the SQL when counting the users.
+     * @param string $sql the bulk of the SQL statement.
+     * @param array $params query parameters.
+     * @param int $page which page number of the results to show.
+     * @param int $perpage number of users per page.
+     * @return array with two elememts:
+     *      int total number of users matching the search.
+     *      array of user objects returned by the query.
+     */
+    protected function execute_search_queries($search, $fields, $countfields, $sql, array $params, $page, $perpage) {
+        global $DB, $CFG;
+
+        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context());
+        $order = ' ORDER BY ' . $sort;
+
+        $totalusers = $DB->count_records_sql($countfields . $sql, $params);
+        $availableusers = $DB->get_records_sql($fields . $sql . $order,
+                array_merge($params, $sortparams), $page*$perpage, $perpage);
+
+        return array('totalusers' => $totalusers, 'users' => $availableusers);
+    }
+
+    /**
+     * Gets an array of the users that can be enrolled in this course.
+     *
+     * @global moodle_database $DB
+     * @param int $enrolid
+     * @param string $search
+     * @param bool $searchanywhere
+     * @param int $page Defaults to 0
+     * @param int $perpage Defaults to 25
+     * @return array Array(totalusers => int, users => array)
+     */
+    public function get_potential_users($enrolid, $search='', $searchanywhere=false, $page=0, $perpage=25) {
+        global $DB;
+
+        list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere);
+
         $fields      = 'SELECT '.$ufields;
         $countfields = 'SELECT COUNT(1)';
         $sql = " FROM {user} u
@@ -312,13 +359,7 @@ class course_enrolment_manager {
                       AND ue.id IS NULL";
         $params['enrolid'] = $enrolid;
 
-        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->get_context());
-        $order = ' ORDER BY ' . $sort;
-        $params += $sortparams;
-
-        $totalusers = $DB->count_records_sql($countfields . $sql, $params);
-        $availableusers = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage);
-        return array('totalusers' => $totalusers, 'users' => $availableusers);
+        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage);
     }
 
     /**
@@ -334,27 +375,9 @@ class course_enrolment_manager {
     public function search_other_users($search='', $searchanywhere=false, $page=0, $perpage=25) {
         global $DB, $CFG;
 
-        // Add some additional sensible conditions
-        $tests = array("u.id <> :guestid", 'u.deleted = 0', 'u.confirmed = 1');
-        $params = array('guestid'=>$CFG->siteguest);
-        if (!empty($search)) {
-            $conditions = array('u.firstname', 'u.lastname');
-            if ($searchanywhere) {
-                $searchparam = '%' . $search . '%';
-            } else {
-                $searchparam = $search . '%';
-            }
-            $i = 0;
-            foreach ($conditions as $key=>$condition) {
-                $conditions[$key] = $DB->sql_like($condition, ":con{$i}00", false);
-                $params["con{$i}00"] = $searchparam;
-                $i++;
-            }
-            $tests[] = '(' . implode(' OR ', $conditions) . ')';
-        }
-        $wherecondition = implode(' AND ', $tests);
+        list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere);
 
-        $fields      = 'SELECT '.user_picture::fields('u', array('username','lastaccess'));
+        $fields      = 'SELECT ' . $ufields;
         $countfields = 'SELECT COUNT(u.id)';
         $sql   = " FROM {user} u
               LEFT JOIN {role_assignments} ra ON (ra.userid = u.id AND ra.contextid = :contextid)
@@ -362,13 +385,7 @@ class course_enrolment_manager {
                     AND ra.id IS NULL";
         $params['contextid'] = $this->context->id;
 
-        list($sort, $sortparams) = users_order_by_sql('u', $search, $this->context);
-        $order = ' ORDER BY ' . $sort;
-
-        $totalusers = $DB->count_records_sql($countfields . $sql, $params);
-        $availableusers = $DB->get_records_sql($fields . $sql . $order,
-                array_merge($params, $sortparams), $page*$perpage, $perpage);
-        return array('totalusers'=>$totalusers, 'users'=>$availableusers);
+        return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage);
     }
 
     /**
index faf8ac0..093ee05 100644 (file)
@@ -51,11 +51,13 @@ echo $OUTPUT->header(); // Send headers.
 
 $manager = new course_enrolment_manager($PAGE, $course);
 
-$outcome = new stdClass;
+$outcome = new stdClass();
 $outcome->success = true;
-$outcome->response = new stdClass;
+$outcome->response = new stdClass();
 $outcome->error = '';
 
+$searchanywhere = get_user_preferences('userselector_searchanywhere', false);
+
 switch ($action) {
     case 'getassignable':
         $otheruserroles = optional_param('otherusers', false, PARAM_BOOL);
@@ -63,9 +65,9 @@ switch ($action) {
         break;
     case 'searchusers':
         $enrolid = required_param('enrolid', PARAM_INT);
-        $search  = optional_param('search', '', PARAM_RAW);
+        $search = optional_param('search', '', PARAM_RAW);
         $page = optional_param('page', 0, PARAM_INT);
-        $outcome->response = $manager->get_potential_users($enrolid, $search, true, $page);
+        $outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page);
         $extrafields = get_extra_user_fields($context);
         foreach ($outcome->response['users'] as &$user) {
             $user->picture = $OUTPUT->user_picture($user);
@@ -77,6 +79,10 @@ switch ($action) {
             }
             $user->extrafields = implode(', ', $fieldvalues);
         }
+        // Chrome will display users in the order of the array keys, so we need
+        // to ensure that the results ordered array keys. Fortunately, the JavaScript
+        // does not care what the array keys are. It uses user.id where necessary.
+        $outcome->response['users'] = array_values($outcome->response['users']);
         $outcome->success = true;
         break;
     case 'enrol':
index c18488e..acdb2eb 100644 (file)
@@ -42,7 +42,7 @@ if ($unrecognized) {
 
 if ($options['help']) {
     $help =
-        "Execute manual enrolments expiration sync.
+        "Execute manual enrolments expiration sync and send notifications.
 
 Options:
 -v, --verbose         Print verbose progress information
@@ -62,4 +62,6 @@ $plugin = enrol_get_plugin('manual');
 
 $result = $plugin->sync(null, $verbose);
 
+$plugin->send_notifications($verbose);
+
 exit($result);
diff --git a/enrol/manual/db/messages.php b/enrol/manual/db/messages.php
new file mode 100644 (file)
index 0000000..a576217
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+/**
+ * Defines message providers for manual enrolments.
+ *
+ * @package    enrol_manual
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$messageproviders = array (
+
+    'expiry_notification' => array(),
+
+);
diff --git a/enrol/manual/db/upgrade.php b/enrol/manual/db/upgrade.php
new file mode 100644 (file)
index 0000000..6bc2602
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file keeps track of upgrades to the manual enrolment plugin
+ *
+ * @package    enrol_manual
+ * @copyright  2012 Petr Skoda {@link http://skodak.org
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+function xmldb_enrol_manual_upgrade($oldversion) {
+    global $CFG, $DB, $OUTPUT;
+
+    $dbman = $DB->get_manager();
+
+    // Moodle v2.3.0 release upgrade line
+    // Put any upgrade step following this
+
+    if ($oldversion < 2012100702) {
+        // Set default expiry threshold to 1 day.
+        $DB->execute("UPDATE {enrol} SET expirythreshold = 86400 WHERE enrol = 'manual' AND expirythreshold = 0");
+        upgrade_plugin_savepoint(true, 2012100702, 'enrol', 'manual');
+    }
+
+    return true;
+}
+
+
index c4ff53d..d6d01e2 100644 (file)
@@ -52,36 +52,64 @@ if ($instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'enrol
             $plugin->delete_instance($del);
         }
     }
+    // Merge these two settings to one value for the single selection element.
+    if ($instance->notifyall and $instance->expirynotify) {
+        $instance->expirynotify = 2;
+    }
+    unset($instance->notifyall);
+
 } else {
     require_capability('moodle/course:enrolconfig', $context);
     // No instance yet, we have to add new instance.
     navigation_node::override_active_url(new moodle_url('/enrol/instances.php', array('id'=>$course->id)));
     $instance = new stdClass();
-    $instance->id       = null;
-    $instance->courseid = $course->id;
+    $instance->id              = null;
+    $instance->courseid        = $course->id;
+    $instance->expirynotify    = $plugin->get_config('expirynotify');
+    $instance->expirythreshold = $plugin->get_config('expirythreshold');
 }
 
-$mform = new enrol_manual_edit_form(NULL, array($instance, $plugin, $context));
+$mform = new enrol_manual_edit_form(null, array($instance, $plugin, $context));
 
 if ($mform->is_cancelled()) {
     redirect($return);
 
 } else if ($data = $mform->get_data()) {
+    if ($data->expirynotify == 2) {
+        $data->expirynotify = 1;
+        $data->notifyall = 1;
+    } else {
+        $data->notifyall = 0;
+    }
+    if (!$data->expirynotify) {
+        // Keep previous/default value of disabled expirythreshold option.
+        $data->expirythreshold = $instance->expirythreshold;
+    }
     if ($instance->id) {
-        $reset = ($instance->status != $data->status);
+        $instance->roleid          = $data->roleid;
+        $instance->enrolperiod     = $data->enrolperiod;
+        $instance->expirynotify    = $data->expirynotify;
+        $instance->notifyall       = $data->notifyall;
+        $instance->expirythreshold = $data->expirythreshold;
+        $instance->timemodified    = time();
 
-        $instance->status       = $data->status;
-        $instance->enrolperiod  = $data->enrolperiod;
-        $instance->roleid       = $data->roleid;
-        $instance->timemodified = time();
         $DB->update_record('enrol', $instance);
 
-        if ($reset) {
+        // Use standard API to update instance status.
+        if ($instance->status != $data->status) {
+            $instance = $DB->get_record('enrol', array('id'=>$instance->id));
+            $plugin->update_status($instance, $data->status);
             $context->mark_dirty();
         }
 
     } else {
-        $fields = array('status'=>$data->status, 'enrolperiod'=>$data->enrolperiod, 'roleid'=>$data->roleid);
+        $fields = array(
+            'status'          => $data->status,
+            'roleid'          => $data->roleid,
+            'enrolperiod'     => $data->enrolperiod,
+            'expirynotify'    => $data->expirynotify,
+            'notifyall'       => $data->notifyall,
+            'expirythreshold' => $data->expirythreshold);
         $plugin->add_instance($course, $fields);
     }
 
index eb21099..23b7f4e 100644 (file)
@@ -42,10 +42,6 @@ class enrol_manual_edit_form extends moodleform {
         $mform->addHelpButton('status', 'status', 'enrol_manual');
         $mform->setDefault('status', $plugin->get_config('status'));
 
-        $mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), array('optional' => true, 'defaultunit' => 86400));
-        $mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
-        $mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
-
         if ($instance->id) {
             $roles = get_default_enrol_roles($context, $instance->roleid);
         } else {
@@ -54,10 +50,34 @@ class enrol_manual_edit_form extends moodleform {
         $mform->addElement('select', 'roleid', get_string('defaultrole', 'role'), $roles);
         $mform->setDefault('roleid', $plugin->get_config('roleid'));
 
+        $mform->addElement('duration', 'enrolperiod', get_string('defaultperiod', 'enrol_manual'), array('optional' => true, 'defaultunit' => 86400));
+        $mform->setDefault('enrolperiod', $plugin->get_config('enrolperiod'));
+        $mform->addHelpButton('enrolperiod', 'defaultperiod', 'enrol_manual');
+
+        $options = array(0 => get_string('no'), 1 => get_string('expirynotifyteacher', 'enrol_manual'), 2 => get_string('expirynotifyall', 'enrol_manual'));
+        $mform->addElement('select', 'expirynotify', get_string('expirynotify', 'enrol_manual'), $options);
+        $mform->addHelpButton('expirynotify', 'expirynotify', 'enrol_manual');
+
+        $mform->addElement('duration', 'expirythreshold', get_string('expirythreshold', 'enrol_manual'), array('optional' => false, 'defaultunit' => 86400));
+        $mform->addHelpButton('expirythreshold', 'expirythreshold', 'enrol_manual');
+        $mform->disabledIf('expirythreshold', 'expirynotify', 'eq', 0);
+
         $mform->addElement('hidden', 'courseid');
 
         $this->add_action_buttons(true, ($instance->id ? null : get_string('addinstance', 'enrol')));
 
         $this->set_data($instance);
     }
+
+    function validation($data, $files) {
+        global $DB;
+
+        $errors = parent::validation($data, $files);
+
+        if ($data['expirynotify'] > 0 and $data['expirythreshold'] < 86400) {
+            $errors['expirythreshold'] = get_string('errorthresholdlow', 'enrol_manual');
+        }
+
+        return $errors;
+    }
 }
index 7abb510..b4494f2 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
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Strings for component 'enrol_manual', language 'en', branch 'MOODLE_20_STABLE'
+ * Strings for component 'enrol_manual', language 'en'.
  *
- * @package    enrol
- * @subpackage manual
+ * @package    enrol_manual
  * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -37,13 +35,35 @@ $string['editenrolment'] = 'Edit enrolment';
 $string['editselectedusers'] = 'Edit selected user enrolments';
 $string['enrolledincourserole'] = 'Enrolled in "{$a->course}" as "{$a->role}"';
 $string['enrolusers'] = 'Enrol users';
+$string['errorthresholdlow'] = 'Notification threshold must be at least 1 day.';
 $string['expiredaction'] = 'Enrolment expiration action';
 $string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
+$string['expirymessageenrollersubject'] = 'Enrolment expiry notification';
+$string['expirymessageenrollerbody'] = 'Enrolment in the course \'{$a->course}\' will expire within the next {$a->threshold} for the following users:
+
+{$a->users}
+
+To extend their enrolment, go to {$a->extendurl}';
+$string['expirymessageenrolledsubject'] = 'Enrolment expiry notification';
+$string['expirymessageenrolledbody'] = 'Dear {$a->user},
+
+This is a notification that your enrolment in the course \'{$a->course}\' is due to expire on {$a->timeend}.
+
+If you need help, please contact {$a->enroller}.';
+$string['expirynotify'] = 'Notify before enrolment expires';
+$string['expirynotify_help'] = 'This setting determines whether enrolment expiry notification messages are sent.';
+$string['expirynotifyall'] = 'Enroller and enrolled user';
+$string['expirynotifyteacher'] = 'Enroller only';
+$string['expirythreshold'] = 'Notification threshold';
+$string['expirythreshold_help'] = 'This setting specifies the number of days before enrolment expiry that a notification message is sent.';
+$string['expirythreshold_help'] = 'How long before expiration should be users notified?';
 $string['manual:config'] = 'Configure manual enrol instances';
 $string['manual:enrol'] = 'Enrol users';
 $string['manual:manage'] = 'Manage user enrolments';
 $string['manual:unenrol'] = 'Unenrol users from the course';
 $string['manual:unenrolself'] = 'Unenrol self from the course';
+$string['messageprovider:expiry_notification'] = 'Manual enrolment expiry notifications';
+$string['notifyhour'] = 'Hour to send enrolment expiry notifications';
 $string['pluginname'] = 'Manual enrolments';
 $string['pluginname_desc'] = 'The manual enrolments plugin allows users to be enrolled manually via a link in the course administration settings, by a user with appropriate permissions such as a teacher. The plugin should normally be enabled, since certain other enrolment plugins, such as self enrolment, require it.';
 $string['status'] = 'Enable manual enrolments';
index 7fe0f56..3f7d8ed 100644 (file)
@@ -26,6 +26,9 @@ defined('MOODLE_INTERNAL') || die();
 
 class enrol_manual_plugin extends enrol_plugin {
 
+    protected $lasternoller = null;
+    protected $lasternollercourseid = 0;
+
     public function roles_protected() {
         // Users may tweak the roles later.
         return false;
@@ -147,7 +150,21 @@ class enrol_manual_plugin extends enrol_plugin {
      * @return int id of new instance, null if can not be created
      */
     public function add_default_instance($course) {
-        $fields = array('status'=>$this->get_config('status'), 'enrolperiod'=>$this->get_config('enrolperiod', 0), 'roleid'=>$this->get_config('roleid', 0));
+        $expirynotify = $this->get_config('expirynotify', 0);
+        if ($expirynotify == 2) {
+            $expirynotify = 1;
+            $notifyall = 1;
+        } else {
+            $notifyall = 0;
+        }
+        $fields = array(
+            'status'          => $this->get_config('status'),
+            'roleid'          => $this->get_config('roleid', 0),
+            'enrolperiod'     => $this->get_config('enrolperiod', 0),
+            'expirynotify'    => $expirynotify,
+            'notifyall'       => $notifyall,
+            'expirythreshold' => $this->get_config('expirythreshold', 86400),
+        );
         return $this->add_instance($course, $fields);
     }
 
@@ -263,6 +280,7 @@ class enrol_manual_plugin extends enrol_plugin {
      */
     public function cron() {
         $this->sync(null, true);
+        $this->send_notifications(true);
     }
 
     /**
@@ -357,6 +375,255 @@ class enrol_manual_plugin extends enrol_plugin {
         return 0;
     }
 
+    /**
+     * Send notifications.
+     *
+     * @param bool $verbose verbose CLI output
+     */
+    public function send_notifications($verbose = false) {
+        global $DB, $CFG;
+
+        // Unfortunately this may take a long time, it should not be interrupted,
+        // otherwise users get duplicate notification.
+
+        @set_time_limit(0);
+        raise_memory_limit(MEMORY_HUGE);
+
+        $notifylast = $this->get_config('notifylast', 0);
+        $notifyhour = $this->get_config('notifyhour', 6);
+        $timenow    = time();
+
+        $notifytime = usergetmidnight($timenow, $CFG->timezone) + ($notifyhour * 3600);
+
+        if ($notifylast > $notifytime) {
+            if ($verbose) {
+                mtrace('Manual enrolment notifications were already sent today at '.userdate($notifylast, '', $CFG->timezone).'.');
+            }
+            return;
+        } else if ($timenow < $notifytime) {
+            if ($verbose) {
+                mtrace('Manual enrolment notifications will be sent at '.userdate($notifytime, '', $CFG->timezone).'.');
+            }
+            return;
+        }
+
+        if ($verbose) {
+            mtrace('Processing manual enrolment notifications...');
+        }
+
+        // Notify users responsible for enrolment once every day.
+        $sql = "SELECT ue.*, e.expirynotify, e.notifyall, e.expirythreshold, e.courseid, c.fullname
+                  FROM {user_enrolments} ue
+                  JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'manual' AND e.expirynotify > 0 AND e.status = :enabled)
+                  JOIN {course} c ON (c.id = e.courseid)
+                  JOIN {user} u ON (u.id = ue.userid AND u.deleted = 0 AND u.suspended = 0)
+                 WHERE ue.status = :active AND ue.timeend > 0 AND ue.timeend > :now1 AND ue.timeend < (e.expirythreshold + :now2)
+              ORDER BY ue.enrolid ASC, u.lastname ASC, u.firstname ASC, u.id ASC";
+        $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'now1'=>$timenow, 'now2'=>$timenow);
+
+        $rs = $DB->get_recordset_sql($sql, $params);
+
+        $lastenrollid = 0;
+        $users = array();
+
+        foreach($rs as $ue) {
+            if ($lastenrollid and $lastenrollid != $ue->enrolid) {
+                $this->notify_expiry_enroller($lastenrollid, $users, $verbose);
+                $users = array();
+            }
+            $lastenrollid = $ue->enrolid;
+
+            $enroller = $this->get_enroller($ue->courseid);
+            $context = context_course::instance($ue->courseid);
+
+            $user = $DB->get_record('user', array('id'=>$ue->userid));
+
+            $users[] = array('fullname'=>fullname($user, has_capability('moodle/site:viewfullnames', $context, $enroller)), 'timeend'=>$ue->timeend);
+
+            if (!$ue->notifyall) {
+                continue;
+            }
+
+            if ($ue->timeend - $ue->expirythreshold + 86400 < $timenow) {
+                // Notify enrolled users only once at the start of the threshold.
+                continue;
+            }
+
+            $this->notify_expiry_enrolled($user, $ue, $verbose);
+        }
+        $rs->close();
+
+        if ($lastenrollid and $users) {
+            $this->notify_expiry_enroller($lastenrollid, $users, $verbose);
+        }
+
+        if ($verbose) {
+            mtrace('...notification processing finished.');
+        }
+        $this->set_config('notifylast', $timenow);
+    }
+
+    /**
+     * Returns the user who is responsible for manual enrolments in given course.
+     *
+     * Usually it is the first editing teacher - the person with "highest authority"
+     * as defined by sort_by_roleassignment_authority() having 'enrol/manual:manage'
+     * capability.
+     *
+     * @param int $courseid
+     * @return stdClass user record
+     */
+    protected function get_enroller($courseid) {
+        if ($this->lasternollercourseid == $courseid and $this->lasternoller) {
+            return $this->lasternoller;
+        }
+
+        $context = context_course::instance($courseid);
+        if ($users = get_enrolled_users($context, 'enrol/manual:manage')) {
+            $users = sort_by_roleassignment_authority($users, $context);
+            $this->lasternoller = reset($users);
+            unset($users);
+        } else {
+            $this->lasternoller = get_admin();
+        }
+
+        $this->lasternollercourseid = $courseid;
+
+        return $this->lasternoller;
+    }
+
+    /**
+     * Notify user about incoming expiration of their enrolment,
+     * it is called only if notification of enrolled users (aka students) is enabled in course.
+     *
+     * This is executed only once for each expiring enrolment right
+     * at the start of the expiration threshold.
+     *
+     * @param stdClass $user
+     * @param stdClass $ue
+     * @param bool $verbose
+     */
+    protected function notify_expiry_enrolled($user, $ue, $verbose) {
+        global $CFG, $SESSION;
+
+        // Some nasty hackery to get strings and dates localised for target user.
+        $sessionlang = isset($SESSION->lang) ? $SESSION->lang : null;
+        if (get_string_manager()->translation_exists($user->lang, false)) {
+            $SESSION->lang = $user->lang;
+            moodle_setlocale();
+        }
+
+        $enroller = $this->get_enroller($ue->courseid);
+        $context = context_course::instance($ue->courseid);
+
+        $subject = get_string('expirymessageenrolledsubject', 'enrol_manual', null);
+        $a = new stdClass();
+        $a->course   = format_string($ue->fullname, true, array('context'=>$context));
+        $a->user     = fullname($user, true);
+        $a->timeend  = userdate($ue->timeend, '', $user->timezone);
+        $a->enroller = fullname($enroller, has_capability('moodle/site:viewfullnames', $context, $user));
+        $body = get_string('expirymessageenrolledbody', 'enrol_manual', $a);
+
+        $message = new stdClass();
+        $message->notification      = 1;
+        $message->component         = 'enrol_manual';
+        $message->name              = 'expiry_notification';
+        $message->userfrom          = $enroller;
+        $message->userto            = $user;
+        $message->subject           = $subject;
+        $message->fullmessage       = $body;
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml   = markdown_to_html($body);
+        $message->smallmessage      = $subject;
+        $message->contexturlname    = $a->course;
+        $message->contexturl        = (string)new moodle_url('/course/view.php', array('id'=>$ue->courseid));
+
+        if (message_send($message)) {
+            if ($verbose) {
+                mtrace("  notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
+            }
+        } else {
+            if ($verbose) {
+                mtrace("  error notifying user $ue->userid that enrolment in course $ue->courseid expires on ".userdate($ue->timeend, '', $CFG->timezone));
+            }
+        }
+
+        if ($SESSION->lang !== $sessionlang) {
+            $SESSION->lang = $sessionlang;
+            moodle_setlocale();
+        }
+    }
+
+    /**
+     * Notify person responsible for enrolments that some user enrolments will be expired soon,
+     * it is called only if notification of enrollers (aka teachers) is enabled in course.
+     *
+     * This is called repeatedly every day for each course if there are any pending expiration
+     * in the expiration threshold.
+     *
+     * @param int $eid
+     * @param array $users
+     * @param bool $verbose
+     */
+    protected function notify_expiry_enroller($eid, $users, $verbose) {
+        global $DB, $SESSION;
+
+        $instance = $DB->get_record('enrol', array('id'=>$eid, 'enrol'=>'manual'));
+        $context = context_course::instance($instance->courseid);
+        $course = $DB->get_record('course', array('id'=>$instance->courseid));
+
+        $enroller = $this->get_enroller($instance->courseid);
+        $admin = get_admin();
+
+        // Some nasty hackery to get strings and dates localised for target user.
+        $sessionlang = isset($SESSION->lang) ? $SESSION->lang : null;
+        if (get_string_manager()->translation_exists($enroller->lang, false)) {
+            $SESSION->lang = $enroller->lang;
+            moodle_setlocale();
+        }
+
+        foreach($users as $key=>$info) {
+            $users[$key] = '* '.$info['fullname'].' - '.userdate($info['timeend'], '', $enroller->timezone);
+        }
+
+        $subject = get_string('expirymessageenrollersubject', 'enrol_manual', null);
+        $a = new stdClass();
+        $a->course    = format_string($course->fullname, true, array('context'=>$context));
+        $a->threshold = get_string('numdays', '', $instance->expirythreshold / (60*60*24));
+        $a->users     = implode("\n", $users);
+        $a->extendurl = (string)new moodle_url('/enrol/users.php', array('id'=>$instance->courseid));
+        $body = get_string('expirymessageenrollerbody', 'enrol_manual', $a);
+
+        $message = new stdClass();
+        $message->notification      = 1;
+        $message->component         = 'enrol_manual';
+        $message->name              = 'expiry_notification';
+        $message->userfrom          = $admin;
+        $message->userto            = $enroller;
+        $message->subject           = $subject;
+        $message->fullmessage       = $body;
+        $message->fullmessageformat = FORMAT_MARKDOWN;
+        $message->fullmessagehtml   = markdown_to_html($body);
+        $message->smallmessage      = $subject;
+        $message->contexturlname    = $a->course;
+        $message->contexturl        = $a->extendurl;
+
+        if (message_send($message)) {
+            if ($verbose) {
+                mtrace("  notifying user $enroller->id about all expiring manual enrolments in course $instance->courseid");
+            }
+        } else {
+            if ($verbose) {
+                mtrace("  error notifying user $enroller->id about all expiring manual enrolments in course $instance->courseid");
+            }
+        }
+
+        if ($SESSION->lang !== $sessionlang) {
+            $SESSION->lang = $sessionlang;
+            moodle_setlocale();
+        }
+    }
+
     /**
      * Gets an array of the user enrolment actions.
      *
index 46d1b77..f7eab04 100644 (file)
@@ -63,7 +63,7 @@ $PAGE->set_heading($course->fullname);
 navigation_node::override_active_url(new moodle_url('/enrol/users.php', array('id'=>$course->id)));
 
 // Create the user selector objects.
-$options = array('enrolid' => $enrolid);
+$options = array('enrolid' => $enrolid, 'accesscontext' => $context);
 
 $potentialuserselector = new enrol_manual_potential_participant('addselect', $options);
 $currentuserselector = new enrol_manual_current_participant('removeselect', $options);
index f793485..d27d265 100644 (file)
@@ -38,6 +38,12 @@ if ($ADMIN->fulltree) {
     );
     $settings->add(new admin_setting_configselect('enrol_manual/expiredaction', get_string('expiredaction', 'enrol_manual'), get_string('expiredaction_help', 'enrol_manual'), ENROL_EXT_REMOVED_KEEP, $options));
 
+    $options = array();
+    for ($i=0; $i<24; $i++) {
+        $options[$i] = $i;
+    }
+    $settings->add(new admin_setting_configselect('enrol_manual/notifyhour', get_string('notifyhour', 'enrol_manual'), '', 6, $options));
+
 
     //--- enrol instance defaults ----------------------------------------------------------------------------
     $settings->add(new admin_setting_heading('enrol_manual_defaults',
@@ -51,9 +57,6 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_configselect('enrol_manual/status',
         get_string('status', 'enrol_manual'), get_string('status_desc', 'enrol_manual'), ENROL_INSTANCE_ENABLED, $options));
 
-    $settings->add(new admin_setting_configduration('enrol_manual/enrolperiod',
-        get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0));
-
     if (!during_initial_install()) {
         $options = get_default_enrol_roles(context_system::instance());
         $student = get_archetype_roles('student');
@@ -61,4 +64,15 @@ if ($ADMIN->fulltree) {
         $settings->add(new admin_setting_configselect('enrol_manual/roleid',
             get_string('defaultrole', 'role'), '', $student->id, $options));
     }
+
+    $settings->add(new admin_setting_configduration('enrol_manual/enrolperiod',
+        get_string('defaultperiod', 'enrol_manual'), get_string('defaultperiod_desc', 'enrol_manual'), 0));
+
+    $options = array(0 => get_string('no'), 1 => get_string('expirynotifyteacher', 'enrol_manual'), 2 => get_string('expirynotifyall', 'enrol_manual'));
+    $settings->add(new admin_setting_configselect('enrol_manual/expirynotify',
+        get_string('expirynotify', 'enrol_manual'), get_string('expirynotify_help', 'enrol_manual'), 0, $options));
+
+    $settings->add(new admin_setting_configduration('enrol_manual/expirythreshold',
+        get_string('expirythreshold', 'enrol_manual'), get_string('expirythreshold_help', 'enrol_manual'), 86400, 86400));
+
 }
index a878cb2..6d04133 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012091500;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012091400;        // Requires this Moodle version
+$plugin->version   = 2012100705;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012100500;        // Requires this Moodle version
 $plugin->component = 'enrol_manual';    // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 600;
index f9c1ac4..ff3543f 100644 (file)
@@ -15,7 +15,7 @@ Structure of the user enroller panel
                             .picture
                             .details
                                 .fullname
-                                .email
+                                .extrafields
                             .options
                                 .enrol
                     .uep-more-results
index b19cb3e..db39b13 100644 (file)
@@ -16,7 +16,7 @@ Structure of the other user role assignment panel
                                 .oump-user-picture
                                 .oump-user-specifics
                                     .oump-user-fullname
-                                    .oump-user-email
+                                    .oump-user-extrafields
                                 .oump-role-options
                                     .label
                                     .oump-assignable-role
index 32b1ad8..1df94ce 100644 (file)
@@ -11,7 +11,7 @@ YUI.add('moodle-enrol-otherusersmanager', function(Y) {
         USERCOUNT = 'userCount',
         PICTURE = 'picture',
         FULLNAME = 'fullname',
-        EMAIL = 'email',
+        EXTRAFIELDS = 'extrafields',
         ASSIGNABLEROLES = 'assignableRoles',
         USERS = 'users',
         URL = 'url',
@@ -36,7 +36,7 @@ YUI.add('moodle-enrol-otherusersmanager', function(Y) {
         PICTURE : 'oump-user-picture',
         DETAILS : 'oump-user-specifics',
         FULLNAME : 'oump-user-fullname',
-        EMAIL : 'oump-user-email',
+        EXTRAFIELDS : 'oump-user-extrafields',
         OPTIONS : 'oump-role-options',
         ROLEOPTION : 'oump-assignable-role',
         ODD  : 'odd',
@@ -310,7 +310,7 @@ YUI.add('moodle-enrol-otherusersmanager', function(Y) {
                     )
                     .append(Y.Node.create('<div class="'+CSS.DETAILS+'"></div>')
                         .append(Y.Node.create('<div class="'+CSS.FULLNAME+'">'+this.get(FULLNAME)+'</div>'))
-                        .append(Y.Node.create('<div class="'+CSS.EMAIL+'">'+this.get(EMAIL)+'</div>'))
+                        .append(Y.Node.create('<div class="'+CSS.EXTRAFIELDS+'">'+this.get(EXTRAFIELDS)+'</div>'))
                     )
                     .append(Y.Node.create('<div class="'+CSS.OPTIONS+'"><span class="label">'+M.str.role.assignrole+': </span></div>'))
                 );
@@ -374,7 +374,7 @@ YUI.add('moodle-enrol-otherusersmanager', function(Y) {
             fullname : {
                 validator : Y.Lang.isString
             },
-            email : {
+            extrafields : {
                 validator : Y.Lang.isString
             },
             picture : {
index 48baef7..9c1621c 100644 (file)
@@ -35,7 +35,10 @@ $string['configenrolplugins'] = 'Please select all required plugins and arrange
 $string['custominstancename'] = 'Custom instance name';
 $string['defaultenrol'] = 'Add instance to new courses';
 $string['defaultenrol_desc'] = 'It is possible to add this plugin to all new courses by default.';
-$string['deleteinstanceconfirm'] = 'Do you really want to delete enrol plugin instance "{$a->name}" with {$a->users} enrolled users?';
+$string['deleteinstanceconfirm'] = 'You are about to delete the enrolment method "{$a->name}". All {$a->users} users currently enrolled using this method will be unenrolled and any course-related data such as users\' grades, group membership or forum subscriptions will be deleted.
+
+Are you sure you want to continue?';
+$string['deleteinstancenousersconfirm'] = 'You are about to delete the enrolment method "{$a->name}". Are you sure you want to continue?';
 $string['durationdays'] = '{$a} days';
 $string['enrol'] = 'Enrol';
 $string['enrolcandidates'] = 'Not enrolled users';
index 9cb966d..64624fb 100644 (file)
@@ -46,6 +46,7 @@ $string['application/vnd.openxmlformats-officedocument.presentationml.slideshow'
 $string['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'] = 'Excel spreadsheet';
 $string['application/vnd.openxmlformats-officedocument.spreadsheetml.template'] = 'Excel template';
 $string['application/vnd.openxmlformats-officedocument.wordprocessingml.document'] = 'Word document';
+$string['application/epub_zip'] = 'EPUB ebook';
 $string['archive'] = 'Archive ({$a->EXT})';
 $string['audio'] = 'Audio file ({$a->EXT})';
 $string['default'] = '{$a->mimetype}';
index fba92b2..3617653 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20120926" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20121002" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="course"/>
         <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="criteriatype"/>
-        <FIELD NAME="criteriatype" TYPE="int" LENGTH="20" NOTNULL="false" SEQUENCE="false" COMMENT="The criteria type we are aggregating, or NULL if complete course aggregation" PREVIOUS="course" NEXT="method"/>
+        <FIELD NAME="criteriatype" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The criteria type we are aggregating, or NULL if complete course aggregation" PREVIOUS="course" NEXT="method"/>
         <FIELD NAME="method" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="1 = all, 2 = any, 3 = fraction, 4 = unit" PREVIOUS="criteriatype" NEXT="value"/>
         <FIELD NAME="value" TYPE="number" LENGTH="10" NOTNULL="false" SEQUENCE="false" DECIMALS="5" COMMENT="NULL = all/any, 0..1 for method 'fraction', &gt; 0 for method 'unit'" PREVIOUS="method"/>
       </FIELDS>
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="course"/>
         <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="criteriatype"/>
-        <FIELD NAME="criteriatype" TYPE="int" LENGTH="20" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Type of criteria" PREVIOUS="course" NEXT="module"/>
+        <FIELD NAME="criteriatype" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Type of criteria" PREVIOUS="course" NEXT="module"/>
         <FIELD NAME="module" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false" COMMENT="Type of module (if using module criteria type)" PREVIOUS="criteriatype" NEXT="moduleinstance"/>
         <FIELD NAME="moduleinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Module instance id (if using module criteria type)" PREVIOUS="module" NEXT="courseinstance"/>
         <FIELD NAME="courseinstance" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Course instance id (if using course criteria type)" PREVIOUS="moduleinstance" NEXT="enrolperiod"/>
         <FIELD NAME="externalserviceid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" PREVIOUS="userid" NEXT="sid"/>
         <FIELD NAME="sid" TYPE="char" LENGTH="128" NOTNULL="false" SEQUENCE="false" COMMENT="link to browser or emulated session" PREVIOUS="externalserviceid" NEXT="contextid"/>
         <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="context id where in token valid" PREVIOUS="sid" NEXT="creatorid"/>
-        <FIELD NAME="creatorid" TYPE="int" LENGTH="20" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="user id of the token creator (useful to know when the administrator created a token and so display the token to a specific administrator)" PREVIOUS="contextid" NEXT="iprestriction"/>
+        <FIELD NAME="creatorid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="1" SEQUENCE="false" COMMENT="user id of the token creator (useful to know when the administrator created a token and so display the token to a specific administrator)" PREVIOUS="contextid" NEXT="iprestriction"/>
         <FIELD NAME="iprestriction" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false" COMMENT="ip restriction" PREVIOUS="creatorid" NEXT="validuntil"/>
         <FIELD NAME="validuntil" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="timestampt - valid until data" PREVIOUS="iprestriction" NEXT="timecreated"/>
         <FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="created timestamp" PREVIOUS="validuntil" NEXT="lastaccess"/>
index 5a91e9c..c7f83fc 100644 (file)
@@ -570,7 +570,27 @@ class mysqli_native_moodle_database extends moodle_database {
 
                 } else if (preg_match('/([a-z]*int[a-z]*)\((\d+)\)/i', $rawcolumn->column_type, $matches)) {
                     $rawcolumn->data_type = $matches[1];
-                    $rawcolumn->character_maximum_length = $matches[2];
+                    $rawcolumn->numeric_precision = $matches[2];
+                    $rawcolumn->max_length = $rawcolumn->numeric_precision;
+
+                    $type = strtoupper($matches[1]);
+                    if ($type === 'BIGINT') {
+                        $maxlength = 18;
+                    } else if ($type === 'INT' or $type === 'INTEGER') {
+                        $maxlength = 9;
+                    } else if ($type === 'MEDIUMINT') {
+                        $maxlength = 6;
+                    } else if ($type === 'SMALLINT') {
+                        $maxlength = 4;
+                    } else if ($type === 'TINYINT') {
+                        $maxlength = 2;
+                    } else {
+                        // This should not happen.
+                        $maxlength = 0;
+                    }
+                    if ($maxlength < $rawcolumn->max_length) {
+                        $rawcolumn->max_length = $maxlength;
+                    }
 
                 } else if (preg_match('/(decimal)\((\d+),(\d+)\)/i', $rawcolumn->column_type, $matches)) {
                     $rawcolumn->data_type = $matches[1];
@@ -631,7 +651,30 @@ class mysqli_native_moodle_database extends moodle_database {
                 $info->meta_type = 'R';
                 $info->unique    = true;
             }
+            // Return number of decimals, not bytes here.
             $info->max_length    = $rawcolumn->numeric_precision;
+            if (preg_match('/([a-z]*int[a-z]*)\((\d+)\)/i', $rawcolumn->column_type, $matches)) {
+                $type = strtoupper($matches[1]);
+                if ($type === 'BIGINT') {
+                    $maxlength = 18;
+                } else if ($type === 'INT' or $type === 'INTEGER') {
+                    $maxlength = 9;
+                } else if ($type === 'MEDIUMINT') {
+                    $maxlength = 6;
+                } else if ($type === 'SMALLINT') {
+                    $maxlength = 4;
+                } else if ($type === 'TINYINT') {
+                    $maxlength = 2;
+                } else {
+                    // This should not happen.
+                    $maxlength = 0;
+                }
+                // It is possible that display precision is different from storage type length,
+                // always use the smaller value to make sure our data fits.
+                if ($maxlength < $info->max_length) {
+                    $info->max_length = $maxlength;
+                }
+            }
             $info->unsigned      = (stripos($rawcolumn->column_type, 'unsigned') !== false);
             $info->auto_increment= (strpos($rawcolumn->extra, 'auto_increment') !== false);
 
@@ -676,6 +719,7 @@ class mysqli_native_moodle_database extends moodle_database {
             case 'SMALLINT':
             case 'MEDIUMINT':
             case 'INT':
+            case 'INTEGER':
             case 'BIGINT':
                 $type = 'I';
                 break;
index fd41e5c..62d7548 100644 (file)
@@ -463,7 +463,18 @@ class pgsql_native_moodle_database extends moodle_database {
                     $info->auto_increment= false;
                     $info->has_default   = ($rawcolumn->atthasdef === 't');
                 }
-                $info->max_length    = $matches[1];
+                // Return number of decimals, not bytes here.
+                if ($matches[1] >= 8) {
+                    $info->max_length = 18;
+                } else if ($matches[1] >= 4) {
+                    $info->max_length = 9;
+                } else if ($matches[1] >= 2) {
+                    $info->max_length = 4;
+                } else if ($matches[1] >= 1) {
+                    $info->max_length = 2;
+                } else {
+                    $info->max_length = 0;
+                }
                 $info->scale         = null;
                 $info->not_null      = ($rawcolumn->attnotnull === 't');
                 if ($info->has_default) {
index 418d518..c976b37 100644 (file)
@@ -677,6 +677,17 @@ class dml_testcase extends database_driver_testcase {
         $table->add_field('negativedfltint', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '-1');
         $table->add_field('negativedfltnumber', XMLDB_TYPE_NUMBER, '10', null, XMLDB_NOTNULL, null, '-2');
         $table->add_field('negativedfltfloat', XMLDB_TYPE_FLOAT, '10', null, XMLDB_NOTNULL, null, '-3');
+        $table->add_field('someint1', XMLDB_TYPE_INTEGER, '1', null, null, null, '0');
+        $table->add_field('someint2', XMLDB_TYPE_INTEGER, '2', null, null, null, '0');
+        $table->add_field('someint3', XMLDB_TYPE_INTEGER, '3', null, null, null, '0');
+        $table->add_field('someint4', XMLDB_TYPE_INTEGER, '4', null, null, null, '0');
+        $table->add_field('someint5', XMLDB_TYPE_INTEGER, '5', null, null, null, '0');
+        $table->add_field('someint6', XMLDB_TYPE_INTEGER, '6', null, null, null, '0');
+        $table->add_field('someint7', XMLDB_TYPE_INTEGER, '7', null, null, null, '0');
+        $table->add_field('someint8', XMLDB_TYPE_INTEGER, '8', null, null, null, '0');
+        $table->add_field('someint9', XMLDB_TYPE_INTEGER, '9', null, null, null, '0');
+        $table->add_field('someint10', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
+        $table->add_field('someint18', XMLDB_TYPE_INTEGER, '18', null, null, null, '0');
         $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
         $dbman->create_table($table);
 
@@ -698,6 +709,15 @@ class dml_testcase extends database_driver_testcase {
         $this->assertEquals(0, $field->default_value);
         $this->assertTrue($field->not_null);
 
+        for($i=1;$i<=10;$i++) {
+            $field = $columns['someint'.$i];
+            $this->assertEquals('I', $field->meta_type);
+            $this->assertGreaterThanOrEqual($i, $field->max_length);
+        }
+        $field = $columns['someint18'];
+        $this->assertEquals('I', $field->meta_type);
+        $this->assertGreaterThanOrEqual(18, $field->max_length);
+
         $field = $columns['name'];
         $this->assertEquals('C', $field->meta_type);
         $this->assertFalse($field->auto_increment);
index 2aa9caa..5c15e1b 100644 (file)
@@ -198,7 +198,7 @@ class tiynce_subplugins_settings extends admin_setting {
                 $displayname = html_writer::tag('span', $namestr, array('class'=>'dimmed_text'));
             }
 
-            if ($PAGE->theme->resolve_image_location('icon', 'tinymce_' . $name)) {
+            if ($PAGE->theme->resolve_image_location('icon', 'tinymce_' . $name, false)) {
                 $icon = $OUTPUT->pix_icon('icon', '', 'tinymce_' . $name, array('class' => 'smallicon pluginicon'));
             } else {
                 $icon = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'smallicon pluginicon noicon'));
index b80f35f..dd5cffb 100644 (file)
@@ -24,6 +24,8 @@
 
 $string['javaneeded'] = 'To use this page you need a Java-enabled browser. Download the latest Java plug-in from {$a}.';
 $string['pluginname'] = 'Insert equation';
+$string['requiretex'] = 'Require TeX filter';
+$string['requiretex_desc'] = 'If enabled the Dragmath button is visible only when the TeX filter is enabled in the editor context. Disable if you have a custom TeX filter that is enabled in global scope.';
 $string['title'] = 'DragMath Equation Editor';
 
 /* All lang strings used from TinyMCE JavaScript code must be named 'pluginname:stringname', no need to create langs/en_dlg.js */
index 1cacacf..6c2eac2 100644 (file)
@@ -30,10 +30,12 @@ class tinymce_dragmath extends editor_tinymce_plugin {
     protected function update_init_params(array &$params, context $context,
             array $options = null) {
 
-        // If TeX filter is disabled, do not add button.
-        $filters = filter_get_active_in_context($context);
-        if (!array_key_exists('filter/tex', $filters)) {
-            return;
+        if ($this->get_config('requiretex', 1)) {
+            // If TeX filter is disabled, do not add button.
+            $filters = filter_get_active_in_context($context);
+            if (!array_key_exists('filter/tex', $filters)) {
+                return;
+            }
         }
 
         // Add button before 'nonbreaking' in advancedbuttons3.
diff --git a/lib/editor/tinymce/plugins/dragmath/settings.php b/lib/editor/tinymce/plugins/dragmath/settings.php
new file mode 100644 (file)
index 0000000..2b5d290
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * DragMath integration settings.
+ *
+ * @package   tinymce_dragmath
+ * @copyright 2012 Petr Skoda {@link http://skodak.org}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+    $settings->add(new admin_setting_configcheckbox('tinymce_dragmath/requiretex',
+        get_string('requiretex', 'tinymce_dragmath'), get_string('requiretex_desc', 'tinymce_dragmath'), 1));
+}
index 287f1b5..3af3438 100644 (file)
@@ -25,8 +25,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 // The current plugin version (Date: YYYYMMDDXX).
-$plugin->version   = 2012051700;
+$plugin->version   = 2012100200;
 // Required Moodle version.
-$plugin->requires  = 2011112900;
+$plugin->requires  = 2012092700;
 // Full name of the plugin (used for diagnostics).
 $plugin->component = 'tinymce_dragmath';
index 55100cf..c8fc2d0 100644 (file)
@@ -23,6 +23,8 @@
  */
 
 $string['pluginname'] = 'Insert emoticon';
+$string['requireemoticon'] = 'Require emoticon filter';
+$string['requireemoticon_desc'] = 'If enabled the emoticon button is visible only when the emoticon filter is enabled in the editor context.';
 
 /* All lang strings used from TinyMCE JavaScript code must be named 'pluginname:stringname', no need to create langs/en_dlg.js */
 $string['moodleemoticon:desc'] = 'Insert emoticon';
index 910cd56..f61c9ea 100644 (file)
@@ -31,10 +31,12 @@ class tinymce_moodleemoticon extends editor_tinymce_plugin {
             array $options = null) {
         global $OUTPUT;
 
-        // If emoticon filter is disabled, do not add button.
-        $filters = filter_get_active_in_context($context);
-        if (!array_key_exists('filter/emoticon', $filters)) {
-            return;
+        if ($this->get_config('requireemoticon', 1)) {
+            // If emoticon filter is disabled, do not add button.
+            $filters = filter_get_active_in_context($context);
+            if (!array_key_exists('filter/emoticon', $filters)) {
+                return;
+            }
         }
 
         // Add button after 'image' in advancedbuttons3.
diff --git a/lib/editor/tinymce/plugins/moodleemoticon/settings.php b/lib/editor/tinymce/plugins/moodleemoticon/settings.php
new file mode 100644 (file)
index 0000000..374a9ef
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Emoticon integration settings.
+ *
+ * @package   tinymce_moodleemoticon
+ * @copyright 2012 Petr Skoda {@link http://skodak.org}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+if ($ADMIN->fulltree) {
+    $settings->add(new admin_setting_configcheckbox('tinymce_moodleemoticon/requireemoticon',
+        get_string('requireemoticon', 'tinymce_moodleemoticon'), get_string('requireemoticon_desc', 'tinymce_moodleemoticon'), 1));
+}
index 7252acb..c7f46a7 100644 (file)
@@ -25,8 +25,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 // The current plugin version (Date: YYYYMMDDXX).
-$plugin->version   = 2012051700;
+$plugin->version   = 2012100200;
 // Required Moodle version.
-$plugin->requires  = 2011112900;
+$plugin->requires  = 2012092700;
 // Full name of the plugin (used for diagnostics).
 $plugin->component = 'tinymce_moodleemoticon';
index ee7afbd..c731f5d 100644 (file)
@@ -1627,8 +1627,8 @@ abstract class enrol_plugin {
         $participants->close();
 
         // now clean up all remainders that were not removed correctly
-        $DB->delete_records('groups_members', array('itemid'=>$instance->id, 'component'=>$name));
-        $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>$name));
+        $DB->delete_records('groups_members', array('itemid'=>$instance->id, 'component'=>'enrol_'.$name));
+        $DB->delete_records('role_assignments', array('itemid'=>$instance->id, 'component'=>'enrol_'.$name));
         $DB->delete_records('user_enrolments', array('enrolid'=>$instance->id));
 
         // finally drop the enrol row
index 401d781..a9a49d9 100644 (file)
@@ -1404,6 +1404,7 @@ function &get_mimetypes_array() {
         'dir'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
         'dxr'  => array ('type'=>'application/x-director', 'icon'=>'flash'),
         'eps'  => array ('type'=>'application/postscript', 'icon'=>'eps'),
+        'epub' => array ('type'=>'application/epub+zip', 'icon'=>'epub', 'groups'=>array('document')),
         'fdf'  => array ('type'=>'application/pdf', 'icon'=>'pdf'),
         'flv'  => array ('type'=>'video/x-flv', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
         'f4v'  => array ('type'=>'video/mp4', 'icon'=>'flash', 'groups'=>array('video','web_video'), 'string'=>'video'),
@@ -1817,10 +1818,14 @@ function get_mimetype_description($obj, $capitalise=false) {
         $a[strtoupper($key)] = strtoupper($value);
         $a[ucfirst($key)] = ucfirst($value);
     }
-    if (get_string_manager()->string_exists($mimetype, 'mimetypes')) {
-        $result = get_string($mimetype, 'mimetypes', (object)$a);
-    } else if (get_string_manager()->string_exists($mimetypestr, 'mimetypes')) {
-        $result = get_string($mimetypestr, 'mimetypes', (object)$a);
+
+    // MIME types may include + symbol but this is not permitted in string ids.
+    $safemimetype = str_replace('+', '_', $mimetype);
+    $safemimetypestr = str_replace('+', '_', $mimetypestr);
+    if (get_string_manager()->string_exists($safemimetype, 'mimetypes')) {
+        $result = get_string($safemimetype, 'mimetypes', (object)$a);
+    } else if (get_string_manager()->string_exists($safemimetypestr, 'mimetypes')) {
+        $result = get_string($safemimetypestr, 'mimetypes', (object)$a);
     } else if (get_string_manager()->string_exists('default', 'mimetypes')) {
         $result = get_string('default', 'mimetypes', (object)$a);
     } else {
@@ -3779,7 +3784,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
                 }
                 // no redirect here because it is not cached
                 $theme = theme_config::load($themename);
-                $imagefile = $theme->resolve_image_location('u/'.$filename, 'moodle');
+                $imagefile = $theme->resolve_image_location('u/'.$filename, 'moodle', null);
                 send_file($imagefile, basename($imagefile), 60*60*24*14);
             }
 
index 1f3f8fb..f22a28c 100644 (file)
@@ -541,6 +541,7 @@ class zip_archive extends file_archive {
                             case 'ISO-8859-5': $encoding = 'CP866'; break;
                             case 'ISO-8859-6': $encoding = 'CP720'; break;
                             case 'ISO-8859-7': $encoding = 'CP737'; break;
+                            case 'ISO-8859-8': $encoding = 'CP862'; break;
                         }
                     }
                     $newname = @textlib::convert($name, $encoding, 'utf-8');
index 03bb5f0..00044d6 100644 (file)
@@ -1263,6 +1263,9 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
     /** @var bool Whether to display advanced elements (on page load) */
     var $_showAdvanced = null;
 
+    /** @var bool whether to automatically initialise M.formchangechecker for this form. */
+    protected $_use_form_change_checker = true;
+
     /**
      * The form name is derived from the class name of the wrapper minus the trailing form
      * It is a name with words joined by underscores whereas the id attribute is words joined by underscores.
@@ -1392,8 +1395,32 @@ class MoodleQuickForm extends HTML_QuickForm_DHTMLRulesTableless {
         return $this->_showAdvanced;
     }
 
+    /**
+     * Call this method if you don't want the formchangechecker JavaScript to be
+     * automatically initialised for this form.
+     */
+    public function disable_form_change_checker() {
+        $this->_use_form_change_checker = false;
+    }
 
-   /**
+    /**
+     * If you have called {@link disable_form_change_checker()} then you can use
+     * this method to re-enable it. It is enabled by default, so normally you don't
+     * need to call this.
+     */
+    public function enable_form_change_checker() {
+        $this->_use_form_change_checker = true;
+    }
+
+    /**
+     * @return bool whether this form should automatically initialise
+     *      formchangechecker for itself.
+     */
+    public function is_form_change_checker_enabled() {
+        return $this->_use_form_change_checker;
+    }
+
+    /**
     * Accepts a renderer
     *
     * @param HTML_QuickForm_Renderer $renderer An HTML_QuickForm_Renderer object
@@ -2243,13 +2270,15 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
             $this->_hiddenHtml .= $form->_pageparams;
         }
 
-        $PAGE->requires->yui_module('moodle-core-formchangechecker',
-                'M.core_formchangechecker.init',
-                array(array(
-                    'formid' => $form->getAttribute('id')
-                ))
-        );
-        $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
+        if ($form->is_form_change_checker_enabled()) {
+            $PAGE->requires->yui_module('moodle-core-formchangechecker',
+                    'M.core_formchangechecker.init',
+                    array(array(
+                        'formid' => $form->getAttribute('id')
+                    ))
+            );
+            $PAGE->requires->string_for_js('changesmadereallygoaway', 'moodle');
+        }
     }
 
     /**
index 10656f9..5cd052d 100644 (file)
@@ -913,7 +913,10 @@ class grade_category extends grade_object {
                 // We're looking for other grade items with the same grade value but a higher grademax
                 $i = 1;
                 while ($originalindex+$i < count($grade_keys)) {
+
                     $possibleitemid = $grade_keys[$originalindex+$i];
+                    $i++;
+
                     if ($grade_values[$founditemid] != $grade_values[$possibleitemid]) {
                         // The next grade item has a different grade value. Stop looking.
                         break;
@@ -930,8 +933,6 @@ class grade_category extends grade_object {
                         $founditemid = $possibleitemid;
                         // Continue searching to see if there is an even higher grademax
                     }
-
-                    $i++;
                 }
 
                 // Now drop whatever grade item we have found
index 727e698..e4281a0 100644 (file)
@@ -516,6 +516,21 @@ class grade_category_testcase extends grade_base_testcase {
         $this->assertEquals(count($grades), 1);
         $this->assertEquals($grades[$this->grade_items[2]->id], 6);
 
+        // MDL-35667 - There was an infinite loop if several items had the same grade and at least one was extra credit
+        $category = new grade_category();
+        $category->droplow     = 1;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $items[$this->grade_items[1]->id]->aggregationcoef = 1; // Mark grade item 1 as "extra credit"
+        $grades = array($this->grade_items[0]->id=>1, // 1 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>1, // 1 out of 100. Extra credit. Should be retained.
+                        $this->grade_items[2]->id=>1, // 1 out of 6. Should be retained.
+                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be retained.
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 3);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 1);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 1);
+        $this->assertEquals($grades[$this->grade_items[4]->id], 1);
+
     }
 
     /**
index 84bf7bb..b9ed527 100644 (file)
@@ -38,10 +38,17 @@ M.util.image_url = function(imagename, component) {
         component = 'core';
     }
 
+    var url = M.cfg.wwwroot + '/theme/image.php';
     if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
-        var url = M.cfg.wwwroot + '/theme/image.php/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
+        if (!M.cfg.svgicons) {
+            url += '/_s';
+        }
+        url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
     } else {
-        var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
+        url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
+        if (!M.cfg.svgicons) {
+            url += '&svg=0';
+        }
     }
 
     return url;
index 9b35114..e5dc06f 100644 (file)
@@ -1197,7 +1197,7 @@ class global_navigation extends navigation_node {
                 break;
             case CONTEXT_COURSECAT :
                 // This has already been loaded we just need to map the variable
-                if ($showcategories) {
+                if ($this->show_categories()) {
                     $this->load_all_categories($this->page->context->instanceid, true);
                 }
                 break;
index 69c1393..3223ee4 100644 (file)
@@ -334,6 +334,12 @@ class theme_config {
      */
     public $supportscssoptimisation = true;
 
+    /**
+     * Used to determine whether we can serve SVG images or not.
+     * @var bool
+     */
+    private $usesvg = null;
+
     /**
      * Load the config.php file for a particular theme, and return an instance
      * of this class. (That is, this is a factory method.)
@@ -942,6 +948,11 @@ class theme_config {
     public function post_process($css) {
         // now resolve all image locations
         if (preg_match_all('/\[\[pix:([a-z_]+\|)?([^\]]+)\]\]/', $css, $matches, PREG_SET_ORDER)) {
+            // We are going to disable the use of SVG images when available in CSS background-image properties
+            // as support for it in browsers is at best quirky.
+            // When we choose to support SVG in background css we will need to remove this code and implement a solution that is
+            // either consistent or varies the URL for serving CSS depending upon SVG being used if available, or not.
+            $this->force_svg_use(false);
             $replaced = array();
             foreach ($matches as $match) {
                 if (isset($replaced[$match[0]])) {
@@ -977,6 +988,7 @@ class theme_config {
         global $CFG;
 
         $params = array('theme'=>$this->name);
+        $svg = $this->use_svg_icons();
 
         if (empty($component) or $component === 'moodle' or $component === 'core') {
             $params['component'] = 'core';
@@ -991,11 +1003,22 @@ class theme_config {
 
         $params['image'] = $imagename;
 
+        $url = new moodle_url("$CFG->httpswwwroot/theme/image.php");
         if (!empty($CFG->slasharguments) and $rev > 0) {
-            $url = new moodle_url("$CFG->httpswwwroot/theme/image.php");
-            $url->set_slashargument('/'.$params['theme'].'/'.$params['component'].'/'.$params['rev'].'/'.$params['image'], 'noparam', true);
+            $path = '/'.$params['theme'].'/'.$params['component'].'/'.$params['rev'].'/'.$params['image'];
+            if (!$svg) {
+                // We add a simple /_s to the start of the path.
+                // The underscore is used to ensure that it isn't a valid theme name.
+                $path = '/_s'.$path;
+            }
+            $url->set_slashargument($path, 'noparam', true);
         } else {
-            $url = new moodle_url("$CFG->httpswwwroot/theme/image.php", $params);
+            if (!$svg) {
+                // We add an SVG param so that we know not to serve SVG images.
+                // We do this because all modern browsers support SVG and this param will one day be removed.
+                $params['svg'] = '0';
+            }
+            $url->params($params);
         }
 
         return $url;
@@ -1003,26 +1026,41 @@ class theme_config {
 
     /**
      * Resolves the real image location.
+     *
+     * $svg was introduced as an arg in 2.4. It is important because not all supported browsers support the use of SVG
+     * and we need a way in which to turn it off.
+     * By default SVG won't be used unless asked for. This is done for two reasons:
+     *   1. It ensures that we don't serve svg images unless we really want to. The admin has selected to force them, of the users
+     *      browser supports SVG.
+     *   2. We only serve SVG images from locations we trust. This must NOT include any areas where the image may have been uploaded
+     *      by the user due to security concerns.
+     *
      * @param string $image name of image, may contain relative path
      * @param string $component
+     * @param bool $svg If set to true SVG images will also be looked for.
      * @return string full file path
      */
-    public function resolve_image_location($image, $component) {
+    public function resolve_image_location($image, $component, $svg = false) {
         global $CFG;
 
+        if (!is_bool($svg)) {
+            // If $svg isn't a bool then we need to decide for ourselves.
+            $svg = $this->use_svg_icons();
+        }
+
         if ($component === 'moodle' or $component === 'core' or empty($component)) {
-            if ($imagefile = $this->image_exists("$this->dir/pix_core/$image")) {
+            if ($imagefile = $this->image_exists("$this->dir/pix_core/$image", $svg)) {
                 return $imagefile;
             }
             foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last
-                if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image")) {
+                if ($imagefile = $this->image_exists("$parent_config->dir/pix_core/$image", $svg)) {
                     return $imagefile;
                 }
             }
-            if ($imagefile = $this->image_exists("$CFG->dataroot/pix/$image")) {
+            if ($imagefile = $this->image_exists("$CFG->dataroot/pix/$image", $svg)) {
                 return $imagefile;
             }
-            if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image")) {
+            if ($imagefile = $this->image_exists("$CFG->dirroot/pix/$image", $svg)) {
                 return $imagefile;
             }
             return null;
@@ -1031,11 +1069,11 @@ class theme_config {
             if ($image === 'favicon') {
                 return "$this->dir/pix/favicon.ico";
             }
-            if ($imagefile = $this->image_exists("$this->dir/pix/$image")) {
+            if ($imagefile = $this->image_exists("$this->dir/pix/$image", $svg)) {
                 return $imagefile;
             }
             foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last
-                if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image")) {
+                if ($imagefile = $this->image_exists("$parent_config->dir/pix/$image", $svg)) {
                     return $imagefile;
                 }
             }
@@ -1047,36 +1085,92 @@ class theme_config {
             }
             list($type, $plugin) = explode('_', $component, 2);
 
-            if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$image")) {
+            if ($imagefile = $this->image_exists("$this->dir/pix_plugins/$type/$plugin/$image", $svg)) {
                 return $imagefile;
             }
             foreach (array_reverse($this->parent_configs) as $parent_config) { // base first, the immediate parent last
-                if ($imagefile = $this->image_exists("$parent_config->dir/pix_plugins/$type/$plugin/$image")) {
+                if ($imagefile = $this->image_exists("$parent_config->dir/pix_plugins/$type/$plugin/$image", $svg)) {
                     return $imagefile;
                 }
             }
-            if ($imagefile = $this->image_exists("$CFG->dataroot/pix_plugins/$type/$plugin/$image")) {
+            if ($imagefile = $this->image_exists("$CFG->dataroot/pix_plugins/$type/$plugin/$image", $svg)) {
                 return $imagefile;
             }
             $dir = get_plugin_directory($type, $plugin);
-            if ($imagefile = $this->image_exists("$dir/pix/$image")) {
+            if ($imagefile = $this->image_exists("$dir/pix/$image", $svg)) {
                 return $imagefile;
             }
             return null;
         }
     }
 
+    /**
+     * Return true if we should look for SVG images as well.
+     *
+     * @staticvar bool $svg
+     * @return bool
+     */
+    public function use_svg_icons() {
+        global $CFG;
+        if ($this->usesvg === null) {
+            if (!isset($CFG->svgicons) || !is_bool($CFG->svgicons)) {
+                // IE 5 - 8 don't support SVG at all.
+                if (empty($_SERVER['HTTP_USER_AGENT'])) {
+                    // Can't be sure, just say no.
+                    $this->usesvg = false;
+                } else if (preg_match('#MSIE +[5-8]\.#', $_SERVER['HTTP_USER_AGENT'])) {
+                    // IE < 9 doesn't support SVG. Say no.
+                    $this->usesvg = false;
+                } else if (preg_match('#Android +[0-2]\.#', $_SERVER['HTTP_USER_AGENT'])) {
+                    // Android < 3 doesn't support SVG. Say no.
+                    $this->usesvg = false;
+                } else {
+                    // Presumed fine.
+                    $this->usesvg = true;
+                }
+            } else {
+                // Force them on/off depending upon the setting.
+                $this->usesvg = $CFG->svgicons;
+            }
+        }
+        return $this->usesvg;
+    }
+
+    /**
+     * Forces the usesvg setting to either true or false, avoiding any decision making.
+     *
+     * This function should only ever be used when absolutely required, and before any generation of image URL's has occurred.
+     *
+     * @param bool $setting True to force the use of svg when available, null otherwise.
+     */
+    private function force_svg_use($setting) {
+        $this->usesvg = (bool)$setting;
+    }
+
     /**
      * Checks if file with any image extension exists.
      *
+     * The order to these images was adjusted prior to the release of 2.4
+     * At that point the were the following image counts in Moodle core:
+     *
+     *     - png = 667 in pix dirs (1499 total)
+     *     - gif = 385 in pix dirs (606 total)
+     *     - jpg = 62  in pix dirs (74 total)
+     *     - jpeg = 0  in pix dirs (1 total)
+     *
+     * There is work in progress to move towards SVG presently hence that has been prioritiesed.
+     *
      * @param string $filepath
+     * @param bool $svg If set to true SVG images will also be looked for.
      * @return string image name with extension
      */
-    private static function image_exists($filepath) {
-        if (file_exists("$filepath.gif")) {
-            return "$filepath.gif";
+    private static function image_exists($filepath, $svg = false) {
+        if ($svg && file_exists("$filepath.svg")) {
+            return "$filepath.svg";
         } else  if (file_exists("$filepath.png")) {
             return "$filepath.png";
+        } else if (file_exists("$filepath.gif")) {
+            return "$filepath.gif";
         } else  if (file_exists("$filepath.jpg")) {
             return "$filepath.jpg";
         } else  if (file_exists("$filepath.jpeg")) {
index 98b5209..1dc66c7 100644 (file)
@@ -264,6 +264,7 @@ class page_requirements_manager {
             'slasharguments'      => (int)(!empty($CFG->slasharguments)),
             'theme'               => $page->theme->name,
             'jsrev'               => ((empty($CFG->cachejs) or empty($CFG->jsrev)) ? -1 : $CFG->jsrev),
+            'svgicons'            => $page->theme->use_svg_icons()
         );
         if (debugging('', DEBUG_DEVELOPER)) {
             $this->M_cfg['developerdebug'] = true;
index a4b3d9f..0f95b2f 100644 (file)
@@ -553,6 +553,9 @@ class phpunit_util {
     public static function reset_all_data($logchanges = false) {
         global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION, $GROUPLIB_CACHE;
 
+        // Release memory and indirectly call destroy() methods to release resource handles, etc.
+        gc_collect_cycles();
+
         // Show any unhandled debugging messages, the runbare() could already reset it.
         self::display_debugging_messages();
         self::reset_debugging();
@@ -632,6 +635,14 @@ class phpunit_util {
         $GROUPLIB_CACHE = null;
         //TODO MDL-25290: add more resets here and probably refactor them to new core function
 
+        // Reset course and module caches.
+        if (class_exists('format_base')) {
+            // If file containing class is not loaded, there is no cache there anyway.
+            format_base::reset_course_cache(0);
+        }
+        $reset = 'reset';
+        get_fast_modinfo($reset);
+
         // purge dataroot directory
         self::reset_dataroot();
 
index d5d50bf..c3b796b 100644 (file)
@@ -118,6 +118,9 @@ class user_picture_testcase extends advanced_testcase {
 
         $this->resetAfterTest();
 
+        // Force SVG on so that we have predictable URL's.
+        $CFG->svgicons = true;
+
         // verify new install contains expected defaults
         $this->assertEquals('standard', $CFG->theme);
         $this->assertEquals(1, $CFG->slasharguments);
index 5fe9ea5..52771c9 100644 (file)
@@ -133,3 +133,100 @@ class xhtml_container_stack_testcase extends advanced_testcase {
         $this->assertDebuggingNotCalled();
     }
 }
+
+/**
+ * Tests the theme config class.
+ *
+ * @copyright 2012 Sam Hemelryk
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class theme_config_testcase extends advanced_testcase {
+    /**
+     * This function will test directives used to serve SVG images to make sure
+     * this are making the right decisions.
+     */
+    public function test_svg_image_use() {
+        global $CFG;
+
+        $this->resetAfterTest();
+
+        if (isset($_SERVER['HTTP_USER_AGENT'])) {
+            $ua = $_SERVER['HTTP_USER_AGENT'];
+        } else {
+            $ua = null;
+        }
+
+        // The two required tests.
+        $this->assertTrue(file_exists($CFG->dirroot.'/pix/i/test.svg'));
+        $this->assertTrue(file_exists($CFG->dirroot.'/pix/i/test.png'));
+
+        $theme = theme_config::load(theme_config::DEFAULT_THEME);
+
+        // First up test the forced setting.
+        $imagefile = $theme->resolve_image_location('i/test', 'moodle', true);
+        $this->assertEquals('test.svg', basename($imagefile));
+        $imagefile = $theme->resolve_image_location('i/test', 'moodle', false);
+        $this->assertEquals('test.png', basename($imagefile));
+
+        // Now test the use of the svgicons config setting.
+        // We need to clone the theme as usesvg property is calculated only once.
+        $testtheme = clone $theme;
+        $CFG->svgicons = true;
+        $imagefile = $testtheme->resolve_image_location('i/test', 'moodle', null);
+        $this->assertEquals('test.svg', basename($imagefile));
+        $CFG->svgicons = false;
+        // We need to clone the theme as usesvg property is calculated only once.
+        $testtheme = clone $theme;
+        $imagefile = $testtheme->resolve_image_location('i/test', 'moodle', null);
+        $this->assertEquals('test.png', basename($imagefile));
+        unset($CFG->svgicons);
+
+        // Finally test a few user agents.
+        $useragents = array(
+            // IE7 on XP.
+            'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1)' => false,
+            // IE8 on Vista.
+            'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0)' => false,
+            // IE8 on Vista in compatability mode.
+            'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)' => false,
+            // IE8 on Windows 7.
+            'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)' => false,
+            // IE9 on Windows 7.
+            'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' => true,
+            // IE9 on Windows 7 in compatability mode.
+            'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/5.0)' => false,
+            // Chrome 11 on Windows.
+            'Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.17 (KHTML, like Gecko) Chrome/11.0.652.0 Safari/534.17' => true,
+            // Chrome 22 on Windows.
+            'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1' => true,
+            // Chrome 21 on Ubuntu 12.04.
+            'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1' => true,
+            // Firefox 4 on Windows.
+            'Mozilla/5.0 (Windows NT 6.1; rv:1.9) Gecko/20100101 Firefox/4.0' => true,
+            // Firefox 15 on Windows.
+            'Mozilla/5.0 (Windows NT 6.1; rv:15.0) Gecko/20120716 Firefox/15.0.1' => true,
+            // Firefox 15 on Ubuntu.
+            'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:15.0) Gecko/20100101 Firefox/15.0.1' => true,
+            // Opera 12.02 on Ubuntu.
+            'Opera/9.80 (X11; Linux x86_64; U; en) Presto/2.10.289 Version/12.02' => true,
+            // Android browser pre 1.0
+            'Mozilla/5.0 (Linux; U; Android 0.5; en-us) AppleWebKit/522+ (KHTML, like Gecko) Safari/419.3' => false,
+            // Android browser 2.3 (HTC)
+            'Mozilla/5.0 (Linux; U; Android 2.3.5; en-us; HTC Vision Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1' => false,
+            // Android browser 3.0 (Motorola)
+            'Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13' => true
+        );
+        foreach ($useragents as $agent => $expected) {
+            $_SERVER['HTTP_USER_AGENT'] = $agent;
+            // We need to clone the theme as usesvg property is calculated only once.
+            $testtheme = clone $theme;
+            $imagefile = $testtheme->resolve_image_location('i/test', 'moodle', null);
+            $this->assertEquals($expected ? 'test.svg' : 'test.png', basename($imagefile),
+                    'Incorrect image returned for user agent `'.$agent.'`');
+        }
+
+        if ($ua !== null) {
+            $_SERVER['HTTP_USER_AGENT'] = $ua;
+        }
+    }
+}
\ No newline at end of file
index c9a8259..f92022a 100644 (file)
@@ -226,97 +226,115 @@ class core_message_renderer extends plugin_renderer_base {
         $output .= html_writer::start_tag('fieldset', array('id' => 'providers', 'class' => 'clearfix'));
         $output .= html_writer::nonempty_tag('legend', get_string('providers_config', 'message'), array('class' => 'ftoggler'));
 
-        // Display the messging options table
-        $table = new html_table();
-        $table->attributes['class'] = 'generaltable';
-        $table->data        = array();
-        $table->head        = array('');
-
-        foreach ($readyprocessors as $processor) {
-            $table->head[]  = get_string('pluginname', 'message_'.$processor->name);
+        foreach($providers as $provider) {
+            if($provider->component != 'moodle') {
+                $components[] = $provider->component;
+            }
         }
+        // Lets arrange by components so that core settings (moodle) appear as the first table.
+        $components = array_unique($components);
+        asort($components);
+        array_unshift($components, 'moodle'); // pop it in front! phew!
+        asort($providers);
 
-        $number_procs = count($processors);
-        // Populate the table with rows
-        foreach ( $providers as $provider) {
-            $preferencebase = $provider->component.'_'.$provider->name;
+        $numprocs = count($processors);
+        // Display the messaging options table(s)
+        foreach ($components as $component) {
+            $table = new html_table();
+            $table->attributes['class'] = 'generaltable';
+            $table->data = array();
+            if ($component != 'moodle') {
+                $componentname = get_string('pluginname', $component);
+            } else {
+                $componentname = get_string('coresystem');
+            }
+            $table->head = array($componentname);
 
-            $headerrow = new html_table_row();
-            $providername = get_string('messageprovider:'.$provider->name, $provider->component);
-            $providercell = new html_table_cell($providername);
-            $providercell->header = true;
-            $providercell->colspan = $number_procs + 1;
-            $providercell->attributes['class'] = 'c0';
-            $headerrow->cells = array($providercell);
-            $table->data[] = $headerrow;
+            foreach ($readyprocessors as $processor) {
+                $table->head[]  = get_string('pluginname', 'message_'.$processor->name);
+            }
 
-            foreach (array('loggedin', 'loggedoff') as $state) {
-                $optionrow = new html_table_row();
-                $optionname = new html_table_cell(get_string($state.'description', 'message'));
-                $optionname->attributes['class'] = 'c0';
-                $optionrow->cells = array($optionname);
-                foreach ($readyprocessors as $processor) {
-                    // determine the default setting
-                    $permitted = MESSAGE_DEFAULT_PERMITTED;
-                    $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
-                    if (isset($defaultpreferences->{$defaultpreference})) {
-                        $permitted = $defaultpreferences->{$defaultpreference};
-                    }
-                    // If settings are disallowed or forced, just display the
-                    // corresponding message, if not use user settings.
-                    if (in_array($permitted, array('disallowed', 'forced'))) {
-                        if ($state == 'loggedoff') {
-                            // skip if we are rendering the second line
-                            continue;
-                        }
-                        $cellcontent = html_writer::nonempty_tag('div', get_string($permitted, 'message'), array('class' => 'dimmed_text'));
-                        $optioncell = new html_table_cell($cellcontent);
-                        $optioncell->rowspan = 2;
-                        $optioncell->attributes['class'] = 'disallowed';
-                    } else {
-                        // determine user preferences and use them.
-                        $disabled = array();
-                        $checked = false;
-                        if ($notificationsdisabled) {
-                            $disabled['disabled'] = 1;
+            // Populate the table with rows
+            foreach ( $providers as $provider) {
+                if( $provider->component != $component) {
+                    continue;
+                }
+                $preferencebase = $provider->component.'_'.$provider->name;
+
+                $headerrow = new html_table_row();
+                $providername = get_string('messageprovider:'.$provider->name, $provider->component);
+                $providercell = new html_table_cell($providername);
+                $providercell->header = true;
+                $providercell->colspan = $numprocs;
+                $providercell->attributes['class'] = 'c0';
+                $headerrow->cells = array($providercell);
+                $table->data[] = $headerrow;
+
+                foreach (array('loggedin', 'loggedoff') as $state) {
+                    $optionrow = new html_table_row();
+                    $optionname = new html_table_cell(get_string($state.'description', 'message'));
+                    $optionname->attributes['class'] = 'c0';
+                    $optionrow->cells = array($optionname);
+                    foreach ($readyprocessors as $processor) {
+                        // determine the default setting
+                        $permitted = MESSAGE_DEFAULT_PERMITTED;
+                        $defaultpreference = $processor->name.'_provider_'.$preferencebase.'_permitted';
+                        if (isset($defaultpreferences->{$defaultpreference})) {
+                            $permitted = $defaultpreferences->{$defaultpreference};
                         }
-                        // See if user has touched this preference
-                        if (isset($preferences->{$preferencebase.'_'.$state})) {
-                            // User have some preferneces for this state in the database, use them
-                            $checked = isset($preferences->{$preferencebase.'_'.$state}[$processor->name]);
+                        // If settings are disallowed or forced, just display the
+                        // corresponding message, if not use user settings.
+                        if (in_array($permitted, array('disallowed', 'forced'))) {
+                            if ($state == 'loggedoff') {
+                                // skip if we are rendering the second line
+                                continue;
+                            }
+                            $cellcontent = html_writer::nonempty_tag('div', get_string($permitted, 'message'), array('class' => 'dimmed_text'));
+                            $optioncell = new html_table_cell($cellcontent);
+                            $optioncell->rowspan = 2;
+                            $optioncell->attributes['class'] = 'disallowed';
                         } else {
-                            // User has not set this preference yet, using site default preferences set by admin
-                            $defaultpreference = 'message_provider_'.$preferencebase.'_'.$state;
-                            if (isset($defaultpreferences->{$defaultpreference})) {
-                                $checked = (int)in_array($processor->name, explode(',', $defaultpreferences->{$defaultpreference}));
+                            // determine user preferences and use them.
+                            $disabled = array();
+                            $checked = false;
+                            if ($notificationsdisabled) {
+                                $disabled['disabled'] = 1;
                             }
+                            // See if user has touched this preference
+                            if (isset($preferences->{$preferencebase.'_'.$state})) {
+                                // User have some preferneces for this state in the database, use them
+                                $checked = isset($preferences->{$preferencebase.'_'.$state}[$processor->name]);
+                            } else {
+                                // User has not set this preference yet, using site default preferences set by admin
+                                $defaultpreference = 'message_provider_'.$preferencebase.'_'.$state;
+                                if (isset($defaultpreferences->{$defaultpreference})) {
+                                    $checked = (int)in_array($processor->name, explode(',', $defaultpreferences->{$defaultpreference}));
+                                }
+                            }
+                            $elementname = $preferencebase.'_'.$state.'['.$processor->name.']';
+                            // prepare language bits
+                            $processorname = get_string('pluginname', 'message_'.$processor->name);
+                            $statename = get_string($state, 'message');
+                            $labelparams = array(
+                                'provider'  => $providername,
+                                'processor' => $processorname,
+                                'state'     => $statename
+                            );
+                            $label = get_string('sendingviawhen', 'message', $labelparams);
+                            $cellcontent = html_writer::label($label, $elementname, true, array('class' => 'accesshide'));
+                            $cellcontent .= html_writer::checkbox($elementname, 1, $checked, '', array_merge(array('id' => $elementname, 'class' => 'notificationpreference'), $disabled));
+                            $optioncell = new html_table_cell($cellcontent);
+                            $optioncell->attributes['class'] = 'mdl-align';
                         }
-                        $elementname = $preferencebase.'_'.$state.'['.$processor->name.']';
-                        // prepare language bits
-                        $processorname = get_string('pluginname', 'message_'.$processor->name);
-                        $statename = get_string($state, 'message');
-                        $labelparams = array(
-                            'provider'  => $providername,
-                            'processor' => $processorname,
-                            'state'     => $statename
-                        );
-                        $label = get_string('sendingviawhen', 'message', $labelparams);
-                        $cellcontent = html_writer::label($label, $elementname, true, array('class' => 'accesshide'));
-                        $cellcontent .= html_writer::checkbox($elementname, 1, $checked, '', array_merge(array('id' => $elementname, 'class' => 'notificationpreference'), $disabled));
-                        $optioncell = new html_table_cell($cellcontent);
-                        $optioncell->attributes['class'] = 'mdl-align';
+                        $optionrow->cells[] = $optioncell;
                     }
-                    $optionrow->cells[] = $optioncell;
+                    $table->data[] = $optionrow;
                 }
-                $table->data[] = $optionrow;
             }
+            $output .= html_writer::start_tag('div', array('class' => 'messagesettingcomponent'));
+            $output .= html_writer::table($table);
+            $output .= html_writer::end_tag('div');
         }
-        $output .= html_writer::start_tag('div');
-        $output .= html_writer::table($table);
-        $output .= html_writer::end_tag('div');
-
-        $disableallcheckbox = $this->output->help_icon('disableall', 'message') . get_string('disableall', 'message') . html_writer::checkbox('disableall', 1, $notificationsdisabled, '', array('class'=>'disableallcheckbox'));
-        $output .= html_writer::nonempty_tag('div', $disableallcheckbox, array('class'=>'disableall'));
 
         $output .= html_writer::end_tag('fieldset');
 
@@ -337,6 +355,8 @@ class core_message_renderer extends plugin_renderer_base {
         $output .= get_string('blocknoncontacts', 'message').': ';
         $output .= html_writer::checkbox('blocknoncontacts', 1, $preferences->blocknoncontacts, '');
         $output .= html_writer::end_tag('div');
+        $disableallcheckbox = $this->output->help_icon('disableall', 'message') . get_string('disableall', 'message') . html_writer::checkbox('disableall', 1, $notificationsdisabled, '', array('class'=>'disableallcheckbox'));
+        $output .= html_writer::nonempty_tag('div', $disableallcheckbox, array('class'=>'disableall'));
         $output .= html_writer::end_tag('fieldset');
         $output .= html_writer::start_tag('div', array('class' => 'mdl-align'));
         $output .= html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('updatemyprofile'), 'class' => 'form-submit'));
index cf8f49d..326dc5a 100644 (file)
@@ -123,6 +123,11 @@ class assignfeedback_offline_import_grades_form extends moodleform implements re
             } else if ($assignment->grading_disabled($user->id)) {
                 // Skip grade is locked.
                 $skip = true;
+            } else if (!is_numeric($gradedesc) && ($assignment->get_instance()->grade) > -1) {
+                $skip = true;
+            } else if (($assignment->get_instance()->grade > -1) &&
+                      (($gradedesc < 0) || ($gradedesc > $assignment->get_instance()->grade))) {
+                $skip = true;
             }
 
             if (!$skip) {
index 3e6e54b..b24f4fa 100644 (file)
@@ -44,16 +44,17 @@ class mod_assign_grading_options_form extends moodleform {
     function definition() {
         $mform = $this->_form;
         $instance = $this->_customdata;
+        $dirtyclass = array('class'=>'ignoredirty');
 
         $mform->addElement('header', 'general', get_string('gradingoptions', 'assign'));
         // visible elements
         $options = array(-1=>get_string('all'),10=>'10', 20=>'20', 50=>'50', 100=>'100');
-        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options);
+        $mform->addElement('select', 'perpage', get_string('assignmentsperpage', 'assign'), $options, $dirtyclass);
         $options = array('' => get_string('filternone', 'assign'),
                          ASSIGN_FILTER_SUBMITTED => get_string('filtersubmitted', 'assign'),
                          ASSIGN_FILTER_REQUIRE_GRADING => get_string('filterrequiregrading', 'assign'));
         if ($instance['submissionsenabled']) {
-            $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options);
+            $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options, $dirtyclass);
         }
 
         // quickgrading
index 61d8624..c640ef2 100644 (file)
@@ -91,6 +91,7 @@ $string['cutoffdatefromdatevalidation'] = 'Cut-off date must be after the allow
 $string['defaultplugins'] = 'Default assignment settings';
 $string['defaultplugins_help'] = 'These settings define the defaults for all new assignments.';
 $string['defaultteam'] = 'Default team';
+$string['deleteallsubmissions'] = 'Delete all submissions';
 $string['deletepluginareyousure'] = 'Delete assignment plugin {$a}: are you sure?';
 $string['deletepluginareyousuremessage'] = 'You are about to completely delete the assignment plugin {$a}. This will completely delete everything in the database associated with this assignment plugin. Are you SURE you want to continue?';
 $string['deletingplugin'] = 'Deleting plugin {$a}.';
@@ -103,6 +104,7 @@ $string['duedateno'] = 'No due date';
 $string['duedatereached'] = 'The due date for this assignment has now passed';
 $string['duedatevalidation'] = 'Due date must be after the allow submissions from date.';
 $string['editsubmission'] = 'Edit my submission';
+$string['editingstatus'] = 'Editing status';
 $string['editaction'] = 'Actions...';
 $string['extensionduedate'] = 'Extension due date';
 $string['extensionnotafterduedate'] = 'Extension date must be after the due date';
index 9b52848..ae53088 100644 (file)
@@ -54,6 +54,72 @@ function assign_delete_instance($id) {
     return $assignment->delete_instance();
 }
 
+/**
+ * This function is used by the reset_course_userdata function in moodlelib.
+ * This function will remove all assignment submissions and feedbacks in the database
+ * and clean up any related data.
+ * @param $data the data submitted from the reset course.
+ * @return array status array
+ */
+function assign_reset_userdata($data) {
+    global $CFG, $DB;
+    require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+    $status = array();
+    $params = array('courseid'=>$data->courseid);
+    $sql = "SELECT a.id FROM {assign} a WHERE a.course=:courseid";
+    $course = $DB->get_record('course', array('id'=> $data->courseid), '*', MUST_EXIST);
+    if ($assigns = $DB->get_records_sql($sql,$params)) {
+        foreach ($assigns as $assign) {
+            $cm = get_coursemodule_from_instance('assign', $assign->id, $data->courseid, false, MUST_EXIST);
+            $context = context_module::instance($cm->id);
+            $assignment = new assign($context, $cm, $course);
+            $status = array_merge($status, $assignment->reset_userdata($data));
+        }
+    }
+    return $status;
+}
+
+/**
+ * Removes all grades from gradebook
+ *
+ * @param int $courseid The ID of the course to reset
+ * @param string $type Optional type of assignment to limit the reset to a particular assignment type
+ */
+function assign_reset_gradebook($courseid, $type='') {
+    global $CFG, $DB;
+
+    $params = array('moduletype'=>'assign','courseid'=>$courseid);
+    $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
+            FROM {assign} a, {course_modules} cm, {modules} m
+            WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid';
+
+    if ($assignments = $DB->get_records_sql($sql,$params)) {
+        foreach ($assignments as $assignment) {
+            assign_grade_item_update($assignment, 'reset');
+        }
+    }
+}
+
+/**
+ * Implementation of the function for printing the form elements that control
+ * whether the course reset functionality affects the assignment.
+ * @param $mform form passed by reference
+ */
+function assign_reset_course_form_definition(&$mform) {
+    $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
+    $mform->addElement('advcheckbox', 'reset_assign_submissions', get_string('deleteallsubmissions','assign'));
+}
+
+/**
+ * Course reset form defaults.
+ * @param  object $course
+ * @return array
+ */
+function assign_reset_course_form_defaults($course) {
+    return array('reset_assign_submissions'=>1);
+}
+
 /**
  * Update an assignment instance
  *
index 3994b40..f03751d 100644 (file)
@@ -552,6 +552,82 @@ class assign {
         return $result;
     }
 
+    /**
+    * Actual implementation of the reset course functionality, delete all the
+    * assignment submissions for course $data->courseid.
+    *
+    * @param $data the data submitted from the reset course.
+    * @return array status array
+    */
+    public function reset_userdata($data) {
+        global $CFG,$DB;
+
+        $componentstr = get_string('modulenameplural', 'assign');
+        $status = array();
+
+        $fs = get_file_storage();
+        if (!empty($data->reset_assign_submissions)) {
+            // Delete files associated with this assignment.
+            foreach ($this->submissionplugins as $plugin) {
+                $fileareas = array();
+                $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component'=>$componentstr,
+                                      'item'=>get_string('deleteallsubmissions','assign'),
+                                      'error'=>$plugin->get_error());
+                }
+            }
+
+            foreach ($this->feedbackplugins as $plugin) {
+                $fileareas = array();
+                $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component'=>$componentstr,
+                                      'item'=>get_string('deleteallsubmissions','assign'),
+                                      'error'=>$plugin->get_error());
+                }
+            }
+
+            $assignssql = "SELECT a.id
+                             FROM {assign} a
+                           WHERE a.course=:course";
+            $params = array ("course" => $data->courseid);
+
+            $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
+            $status[] = array('component'=>$componentstr,
+                              'item'=>get_string('deleteallsubmissions','assign'),
+                              'error'=>false);
+
+            if (empty($data->reset_gradebook_grades)) {
+                // Remove all grades from gradebook.
+                require_once($CFG->dirroot.'/mod/assign/lib.php');
+                assign_reset_gradebook($data->courseid);
+            }
+        }
+        // Updating dates - shift may be negative too.
+        if ($data->timeshift) {
+            shift_course_mod_dates('assign',
+                                    array('duedate', 'allowsubmissionsfromdate','cutoffdate'),
+                                    $data->timeshift,
+                                    $data->courseid);
+            $status[] = array('component'=>$componentstr,
+                              'item'=>get_string('datechanged'),
+                              'error'=>false);
+        }
+
+        return $status;
+    }
+
     /**
      * Update the settings for a single plugin
      *
@@ -1779,16 +1855,20 @@ class assign {
                 if ($this->get_instance()->teamsubmission) {
                     $submission = $this->get_group_submission($userid, 0, false);
                     $submissiongroup = $this->get_submission_group($userid);
-                    $groupname = '-' . $submissiongroup->name;
+                    if ($submissiongroup) {
+                        $groupname = $submissiongroup->name . '-';
+                    } else {
+                        $groupname = get_string('defaultteam', 'assign') . '-';
+                    }
                 } else {
                     $submission = $this->get_user_submission($userid, false);
                 }
 
                 if ($this->is_blind_marking()) {
-                    $prefix = clean_filename(str_replace('_', ' ', get_string('participant', 'assign') . $groupname) .
+                    $prefix = clean_filename(str_replace('_', ' ', $groupname . get_string('participant', 'assign')) .
                                              "_" . $this->get_uniqueid_for_user($userid) . "_");
                 } else {
-                    $prefix = clean_filename(str_replace('_', ' ', fullname($student) . $groupname) .
+                    $prefix = clean_filename(str_replace('_', ' ', $groupname . fullname($student)) .
                                              "_" . $this->get_uniqueid_for_user($userid) . "_");
                 }
 
index 6122b6e..a2f94da 100644 (file)
@@ -541,7 +541,7 @@ class mod_assign_renderer extends plugin_renderer_base {
         // Show graders whether this submission is editable by students.
         if ($status->view == assign_submission_status::GRADER_VIEW) {
             $row = new html_table_row();
-            $cell1 = new html_table_cell(get_string('open', 'assign'));
+            $cell1 = new html_table_cell(get_string('editingstatus', 'assign'));
             if ($status->canedit) {
                 $cell2 = new html_table_cell(get_string('submissioneditable', 'assign'));
                 $cell2->attributes = array('class'=>'submissioneditable');
index 470e18e..e15de10 100644 (file)
@@ -47,6 +47,7 @@ $string['editingchapter'] = 'Editing chapter';
 $string['chaptertitle'] = 'Chapter title';
 $string['content'] = 'Content';
 $string['subchapter'] = 'Subchapter';
+$string['nocontent'] = 'No content has been added to this book yet.';
 $string['numbering'] = 'Chapter formatting';
 $string['numbering_help'] = '* None - Chapter and subchapter titles have no formatting
 * Numbers - Chapters and subchapter titles are numbered 1, 1.1, 1.2, 2, ...
index eaae261..25ee6f7 100644 (file)
@@ -60,9 +60,9 @@ $mform = new booktool_importhtml_form(null, array('id'=>$id, 'chapterid'=>$chapt
 // If data submitted, then process and store.
 if ($mform->is_cancelled()) {
     if (empty($chapter->id)) {
-        redirect("/mod/book/view.php?id=$cm->id");
+        redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id");
     } else {
-        redirect("/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
+        redirect($CFG->wwwroot."/mod/book/view.php?id=$cm->id&chapterid=$chapter->id");
     }
 
 } else if ($data = $mform->get_data()) {
index 91b83b8..6b9365d 100644 (file)
@@ -87,13 +87,16 @@ if ($chapterid == '0') { // Go to first chapter if no given.
     }
 }
 
-if (!$chapterid or !$chapter = $DB->get_record('book_chapters', array('id'=>$chapterid, 'bookid'=>$book->id))) {
-    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
-}
+$courseurl = new moodle_url('/course/view.php', array('id' => $course->id));
 
-// chapter is hidden for students
-if ($chapter->hidden and !$viewhidden) {
-    print_error('errorchapter', 'mod_book', new moodle_url('/course/view.php', array('id'=>$course->id)));
+// No content in the book.
+if (!$chapterid) {
+    $PAGE->set_url('/mod/book/view.php', array('id' => $id));
+    notice(get_string('nocontent', 'mod_book'), $courseurl->out(false));
+}
+// Chapter doesnt exist or it is hidden for students
+if ((!$chapter = $DB->get_record('book_chapters', array('id' => $chapterid, 'bookid' => $book->id))) or ($chapter->hidden and !$viewhidden)) {
+    print_error('errorchapter', 'mod_book', $courseurl);
 }
 
 $PAGE->set_url('/mod/book/view.php', array('id'=>$id, 'chapterid'=>$chapterid));
index e73490c..a2d9cf1 100644 (file)
@@ -3426,21 +3426,28 @@ function data_page_type_list($pagetype, $parentcontext, $currentcontext) {
 /**
  * Get all of the record ids from a database activity.
  *
- * @param int $dataid      The dataid of the database module.
- * @return array $idarray  An array of record ids
+ * @param int    $dataid      The dataid of the database module.
+ * @param object $selectdata  Contains an additional sql statement for the
+ *                            where clause for group and approval fields.
+ * @param array  $params      Parameters that coincide with the sql statement.
+ * @return array $idarray     An array of record ids
  */
-function data_get_all_recordids($dataid) {
+function data_get_all_recordids($dataid, $selectdata = '', $params = null) {
     global $DB;
-    $initsql = 'SELECT c.recordid
-                  FROM {data_fields} f,
-                       {data_content} c
-                 WHERE f.dataid = :dataid
-                   AND f.id = c.fieldid
-              GROUP BY c.recordid';
-    $initrecord = $DB->get_recordset_sql($initsql, array('dataid' => $dataid));
+    $initsql = 'SELECT r.id
+                  FROM {data_records} r
+                 WHERE r.dataid = :dataid';
+    if ($selectdata != '') {
+        $initsql .= $selectdata;
+        $params = array_merge(array('dataid' => $dataid), $params);
+    } else {
+        $params = array('dataid' => $dataid);
+    }
+    $initsql .= ' GROUP BY r.id';
+    $initrecord = $DB->get_recordset_sql($initsql, $params);
     $idarray = array();
     foreach ($initrecord as $data) {
-        $idarray[] = $data->recordid;
+        $idarray[] = $data->id;
     }
     // Close the record set and free up resources.
     $initrecord->close();
@@ -3585,8 +3592,7 @@ function data_get_advanced_search_sql($sort, $data, $recordids, $selectdata, $so
     } else {
         list($insql, $inparam) = $DB->get_in_or_equal(array('-1'), SQL_PARAMS_NAMED);
     }
-    $nestfromsql .= ' AND c.recordid ' . $insql . $groupsql;
-    $nestfromsql = "$nestfromsql $selectdata";
+    $nestfromsql .= ' AND c.recordid ' . $insql . $selectdata . $groupsql;
     $sqlselect['sql'] = "$nestselectsql $nestfromsql $sortorder";
     $sqlselect['params'] = $inparam;
     return $sqlselect;
index 5ffa5bf..f740429 100644 (file)
 id,userid,groupid,dataid,timecreated,timemodified,approved
 1,1,0,1,1234567891,1234567892,1
-2,2,0,1,1234567891,1234567892,1
-3,3,0,1,1234567891,1234567892,1
-4,4,0,1,1234567891,1234567892,1
-5,5,0,1,1234567891,1234567892,1
-6,6,0,1,1234567891,1234567892,1
+2,2,1,1,1234567891,1234567892,1
+3,3,2,1,1234567891,1234567892,1
+4,4,1,1,1234567891,1234567892,1
+5,5,1,1,1234567891,1234567892,1
+6,6,2,1,1234567891,1234567892,1
 7,7,0,1,1234567891,1234567892,1
-8,8,0,1,1234567891,1234567892,1
-9,9,0,1,1234567891,1234567892,1
-10,10,0,1,1234567891,1234567892,1
-11,11,0,1,1234567891,1234567892,1
-12,12,0,1,1234567891,1234567892,1
-13,13,0,1,1234567891,1234567892,1
+8,8,2,1,1234567891,1234567892,1
+9,9,1,1,1234567891,1234567892,1
+10,10,1,1,1234567891,1234567892,1
+11,11,1,1,1234567891,1234567892,1
+12,12,2,1,1234567891,1234567892,1
+13,13,2,1,1234567891,1234567892,1
 14,14,0,1,1234567891,1234567892,1
-15,15,0,1,1234567891,1234567892,1
+15,15,2,1,1234567891,1234567892,1
 16,16,0,1,1234567891,1234567892,1
-17,17,0,1,1234567891,1234567892,1
-18,18,0,1,1234567891,1234567892,1
+17,17,1,1,1234567891,1234567892,1
+18,18,2,1,1234567891,1234567892,1
 19,19,0,1,1234567891,1234567892,1
-20,20,0,1,1234567891,1234567892,1
+20,20,1,1,1234567891,1234567892,1
 21,21,0,1,1234567891,1234567892,1
-22,22,0,1,1234567891,1234567892,1
+22,22,2,1,1234567891,1234567892,1
 23,23,0,1,1234567891,1234567892,1
-24,24,0,1,1234567891,1234567892,1
-25,25,0,1,1234567891,1234567892,1
+24,24,2,1,1234567891,1234567892,1
+25,25,2,1,1234567891,1234567892,1
 26,26,0,1,1234567891,1234567892,1
-27,27,0,1,1234567891,1234567892,1
-28,28,0,1,1234567891,1234567892,1
+27,27,1,1,1234567891,1234567892,1
+28,28,2,1,1234567891,1234567892,1
 29,29,0,1,1234567891,1234567892,1
-30,30,0,1,1234567891,1234567892,1
+30,30,2,1,1234567891,1234567892,1
 31,31,0,1,1234567891,1234567892,1
-32,32,0,1,1234567891,1234567892,1
-33,33,0,1,1234567891,1234567892,1
-34,34,0,1,1234567891,1234567892,1
-35,35,0,1,1234567891,1234567892,1
+32,32,1,1,1234567891,1234567892,1
+33,33,1,1,1234567891,1234567892,1
+34,34,2,1,1234567891,1234567892,1
+35,35,1,1,1234567891,1234567892,1
 36,36,0,1,1234567891,1234567892,1
 37,37,0,1,1234567891,1234567892,1
-38,38,0,1,1234567891,1234567892,1
-39,39,0,1,1234567891,1234567892,1
-40,40,0,1,1234567891,1234567892,1
+38,38,2,1,1234567891,1234567892,1
+39,39,1,1,1234567891,1234567892,1
+40,40,1,1,1234567891,1234567892,1
 41,41,0,1,1234567891,1234567892,1
-42,42,0,1,1234567891,1234567892,1
+42,42,1,1,1234567891,1234567892,1
 43,43,0,1,1234567891,1234567892,1
-44,44,0,1,1234567891,1234567892,1
+44,44,1,1,1234567891,1234567892,1
 45,45,0,1,1234567891,1234567892,1
 46,46,0,1,1234567891,1234567892,1
-47,47,0,1,1234567891,1234567892,1
+47,47,1,1,1234567891,1234567892,1
 48,48,0,1,1234567891,1234567892,1
 49,49,0,1,1234567891,1234567892,1
-50,50,0,1,1234567891,1234567892,1
+50,50,1,1,1234567891,1234567892,1
 51,51,0,1,1234567891,1234567892,1
 52,52,0,1,1234567891,1234567892,1
-53,53,0,1,1234567891,1234567892,1
-54,54,0,1,1234567891,1234567892,1
+53,53,1,1,1234567891,1234567892,1
+54,54,1,1,1234567891,1234567892,1
 55,55,0,1,1234567891,1234567892,1
-56,56,0,1,1234567891,1234567892,1
-57,57,0,1,1234567891,1234567892,1
-58,58,0,1,1234567891,1234567892,1
-59,59,0,1,1234567891,1234567892,1
-60,60,0,1,1234567891,1234567892,1
+56,56,2,1,1234567891,1234567892,1
+57,57,2,1,1234567891,1234567892,1
+58,58,2,1,1234567891,1234567892,1
+59,59,1,1,1234567891,1234567892,1
+60,60,1,1,1234567891,1234567892,1
 61,61,0,1,1234567891,1234567892,1
-62,62,0,1,1234567891,1234567892,1
+62,62,2,1,1234567891,1234567892,1
 63,63,0,1,1234567891,1234567892,1
 64,64,0,1,1234567891,1234567892,1
-65,65,0,1,1234567891,1234567892,1
-66,66,0,1,1234567891,1234567892,1
+65,65,1,1,1234567891,1234567892,1
+66,66,1,1,1234567891,1234567892,1
 67,67,0,1,1234567891,1234567892,1
 68,68,0,1,1234567891,1234567892,1
-69,69,0,1,1234567891,1234567892,1
-70,70,0,1,1234567891,1234567892,1
+69,69,2,1,1234567891,1234567892,1
+70,70,2,1,1234567891,1234567892,1
 71,71,0,1,1234567891,1234567892,1
-72,72,0,1,1234567891,1234567892,1
-73,73,0,1,1234567891,1234567892,1
+72,72,1,1,1234567891,1234567892,1
+73,73,1,1,1234567891,1234567892,1
 74,74,0,1,1234567891,1234567892,1
 75,75,0,1,1234567891,1234567892,1
-76,76,0,1,1234567891,1234567892,1
-77,77,0,1,1234567891,1234567892,1
+76,76,2,1,1234567891,1234567892,1
+77,77,2,1,1234567891,1234567892,1
 78,78,0,1,1234567891,1234567892,1
-79,79,0,1,1234567891,1234567892,1
-80,80,0,1,1234567891,1234567892,1
+79,79,1,1,1234567891,1234567892,1
+80,80,1,1,1234567891,1234567892,1
 81,81,0,1,1234567891,1234567892,1
-82,82,0,1,1234567891,1234567892,1
-83,83,0,1,1234567891,1234567892,1
-84,84,0,1,1234567891,1234567892,1
-85,85,0,1,1234567891,1234567892,1
+82,82,1,1,1234567891,1234567892,1
+83,83,1,1,1234567891,1234567892,1
+84,84,1,1,1234567891,1234567892,1
+85,85,1,1,1234567891,1234567892,1
 86,86,0,1,1234567891,1234567892,1
 87,87,0,1,1234567891,1234567892,1
 88,88,0,1,1234567891,1234567892,1
-89,89,0,1,1234567891,1234567892,1
-90,90,0,1,1234567891,1234567892,1
-91,91,0,1,1234567891,1234567892,1
-92,92,0,1,1234567891,1234567892,1
-93,93,0,1,1234567891,1234567892,1
-94,94,0,1,1234567891,1234567892,1
-95,95,0,1,1234567891,1234567892,1
-96,96,0,1,1234567891,1234567892,1
-97,97,0,1,1234567891,1234567892,1
-98,98,0,1,1234567891,1234567892,1
-99,99,0,1,1234567891,1234567892,1
-100,100,0,1,1234567891,1234567892,1
+89,89,1,1,1234567891,1234567892,1
+90,90,1,1,1234567891,1234567892,0
+91,91,2,1,1234567891,1234567892,0
+92,92,0,1,1234567891,1234567892,0
+93,93,2,1,1234567891,1234567892,0
+94,94,1,1,1234567891,1234567892,0
+95,95,1,1,1234567891,1234567892,0
+96,96,1,1,1234567891,1234567892,0
+97,97,0,1,1234567891,1234567892,0
+98,98,1,1,1234567891,1234567892,0
+99,99,2,1,1234567891,1234567892,0
+100,100,0,1,1234567891,1234567892,0
index 150bc5d..2980643 100644 (file)
@@ -70,6 +70,11 @@ class data_advanced_search_sql_test extends advanced_testcase {
      */
     public $datarecordcount = 100;
 
+    /**
+     * @var int $groupdatarecordcount  The number of records in the database in groups 0 and 1.
+     */
+    public $groupdatarecordcount = 75;
+
     /**
      * @var array $datarecordset   Expected record IDs.
      */
@@ -80,6 +85,11 @@ class data_advanced_search_sql_test extends advanced_testcase {
      */
     public $finalrecord = array();
 
+    /**
+     * @var int $approvedatarecordcount  The number of approved records in the database.
+     */
+    public $approvedatarecordcount = 89;
+
     /**
      * Set up function. In this instance we are setting up database
      * records to be used in the unit tests.
@@ -169,6 +179,13 @@ class data_advanced_search_sql_test extends advanced_testcase {
      * Test 4: data_get_advanced_search_sql provides an array which contains an sql string to be used for displaying records
      * to the user when they use the advanced search criteria and the parameters that go with the sql statement. This test
      * takes that information and does a search on the database, returning a record.
+     *
+     * Test 5: Returning to data_get_all_recordids(). Here we are ensuring that the total amount of record ids is reduced to
+     * match the group conditions that are provided. There are 25 entries which relate to group 2. They are removed
+     * from the total so we should only have 75 records total.
+     *
+     * Test 6: data_get_all_recordids() again. This time we are testing approved database records. We only want to
+     * display the records that have been approved. In this record set we have 89 approved records.
      */
     function test_advanced_search_sql_section() {
         global $DB;
@@ -193,5 +210,16 @@ class data_advanced_search_sql_test extends advanced_testcase {
         $allparams = array_merge($html['params'], array('dataid' => $this->recorddata->id));
         $records = $DB->get_records_sql($html['sql'], $allparams);
         $this->assertEquals($records, $this->finalrecord);
+
+        // Test 5
+        $groupsql = " AND (r.groupid = :currentgroup OR r.groupid = 0)";
+        $params = array('currentgroup' => 1);
+        $recordids = data_get_all_recordids($this->recorddata->id, $groupsql, $params);
+        $this->assertEquals($this->groupdatarecordcount, count($recordids));
+
+        // Test 6
+        $approvesql = ' AND r.approved=1 ';
+        $recordids = data_get_all_recordids($this->recorddata->id, $approvesql, $params);
+        $this->assertEquals($this->approvedatarecordcount, count($recordids));
     }
 }
index abedca4..691cac0 100644 (file)
     groups_print_activity_menu($cm, $returnurl);
     $currentgroup = groups_get_activity_group($cm);
     $groupmode = groups_get_activity_groupmode($cm);
+    // If a student is not part of a group and seperate groups is enabled, we don't
+    // want them seeing all records.
+    if ($currentgroup == 0 && $groupmode == 1 && !has_capability('mod/data:manageentries', $context)) {
+        $canviewallrecords = false;
+    } else {
+        $canviewallrecords = true;
+    }
 
     // detect entries not approved yet and show hint instead of not found error
     if ($record and $data->approval and !$record->approved and $record->userid != $USER->id and !has_capability('mod/data:manageentries', $context)) {
@@ -465,7 +472,13 @@ if ($showactivity) {
             $groupselect = " AND (r.groupid = :currentgroup OR r.groupid = 0)";
             $params['currentgroup'] = $currentgroup;
         } else {
-            $groupselect = ' ';
+            if ($canviewallrecords) {
+                $groupselect = ' ';
+            } else {
+                // If separate groups are enabled and the user isn't in a group or
+                // a teacher, manager, admin etc, then just show them entries for 'All participants'.
+                $groupselect = " AND r.groupid = 0";
+            }
         }
 
         // Init some variables to be used by advanced search
@@ -590,10 +603,19 @@ if ($showactivity) {
         $sqlmax     = "SELECT $count FROM $tables $where $groupselect $approveselect"; // number of all recoirds user may see
         $allparams  = array_merge($params, $advparams);
 
-        $recordids = data_get_all_recordids($data->id);
+        // Provide initial sql statements and parameters to reduce the number of total records.
+        $selectdata = $groupselect . $approveselect;
+        $initialparams = array();
+        if ($currentgroup) {
+            $initialparams['currentgroup'] = $params['currentgroup'];
+        }
+        if (!$approvecap && $data->approval && isloggedin()) {
+            $initialparams['myid1'] = $params['myid1'];
+        }
+
+        $recordids = data_get_all_recordids($data->id, $selectdata, $initialparams);
         $newrecordids = data_get_advance_search_ids($recordids, $search_array, $data->id);
         $totalcount = count($newrecordids);
-        $selectdata = $groupselect . $approveselect;
 
         if (!empty($advanced)) {
             $advancedsearchsql = data_get_advanced_search_sql($sort, $data, $newrecordids, $selectdata, $sortorder);
index a24dfb8..de44bab 100644 (file)
@@ -115,6 +115,7 @@ table.quizattemptsummary .noreviewmessage {color: gray;}
 #page-mod-quiz-view .generalbox#feedback {width:70%;margin-left:auto;margin-right:auto;padding-bottom:15px;}
 #page-mod-quiz-view .generalbox#feedback h2 {margin: 0;}
 #page-mod-quiz-view .generalbox#feedback h3 {text-align: left;}
+#page-mod-quiz-view.dir-rtl .generalbox#feedback h3 {text-align: center;}
 #page-mod-quiz-view .generalbox#feedback .overriddennotice {text-align: center;font-size: 0.7em;}
 .quizstartbuttondiv.quizsecuremoderequired input { display: none; }
 .jsenabled .quizstartbuttondiv.quizsecuremoderequired input { display: inline; }
@@ -250,6 +251,7 @@ table#categoryquestions {width: 100%;overflow: hidden;table-layout: fixed;}
 #page-mod-quiz-edit div.question div.content .singlequestion .questiontext{display:inline-block;}
 #page-mod-quiz-edit div.question div.content .singlequestion .questionpreview{background-color:#eee;}
 #page-mod-quiz-edit div.question div.content .questiontype{display:block;clear:left;float:left;}
+#page-mod-quiz-edit.dir-rtl div.question div.content .questiontype {clear: right;float: right;}
 #page-mod-quiz-edit div.question div.content .questionpreview {display:block;float:left;margin-left:0.3em;padding-left:0.2em;padding-right:0.2em;}
 #page-mod-quiz-edit div.question div.content .questionpreview a{background-color:#eee;}
 #page-mod-quiz-edit div.question div.content div.quiz_randomquestion .questionpreview{display:inline;float:none;}
@@ -398,7 +400,7 @@ bank window's title is prominent enough*/
 #page-mod-quiz-edit.dir-rtl div.question {clear: right;}
 #page-mod-quiz-edit.dir-rtl div.question div.qnum {float: right;}
 #page-mod-quiz-edit.dir-rtl div.editq div.question div.content {float: right;height: 40px;}
-#page-mod-quiz-edit.dir-rtl div.question div.content div.points {left: 50px;right:auto;}
+#page-mod-quiz-edit.dir-rtl div.question div.content div.points {left: 5px;right:auto;}
 #page-mod-quiz-edit.dir-rtl div.question div.content div.questioncontrols {float: left;left: 0.3em; right:auto;}
 #page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questionname,
 #page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questiontext {float: right; padding-right: 0.3em;}
index 4f25b70..99f55ac 100644 (file)
@@ -47,6 +47,9 @@ $string['asset'] = 'Asset';
 $string['assetlaunched'] = 'Asset - Viewed';
 $string['attempt'] = 'Attempt';
 $string['attempts'] = 'Attempts';
+$string['attemptstatusall'] = 'My home and entry page';
+$string['attemptstatusmy'] = 'My home only';
+$string['attemptstatusentry'] = 'Entry page only';
 $string['attemptsx'] = '{$a} attempts';
 $string['attempt1'] = '1 attempt';
 $string['attr_error'] = 'Bad value for attribute ({$a->attr}) in tag {$a->tag}.';
@@ -85,7 +88,7 @@ $string['directories'] = 'Show the directory links';
 $string['disabled'] = 'Disabled';
 $string['display'] = 'Display package';
 $string['displayattemptstatus'] = 'Display attempt status';
-$string['displayattemptstatus_help'] = 'If enabled, scores and grades for attempts are displayed on the SCORM outline page.';
+$string['displayattemptstatus_help'] = 'This preference allows a summary of the users attempts to show in the course overview block in My home and/or the SCORM entry page.';
 $string['displayattemptstatusdesc'] = 'This preference sets the default value for the display attempt status setting';
 $string['displaycoursestructure'] = 'Display course structure on entry page';
 $string['displaycoursestructure_help'] = 'If enabled, the table of contents is displayed on the SCORM outline page.';
index ee9b945..5575466 100644 (file)
@@ -41,6 +41,12 @@ define('SCORM_12', 1);
 define('SCORM_13', 2);
 define('SCORM_AICC', 3);
 
+// List of possible attemptstatusdisplay options.
+define('SCORM_DISPLAY_ATTEMPTSTATUS_NO', 0);
+define('SCORM_DISPLAY_ATTEMPTSTATUS_ALL', 1);
+define('SCORM_DISPLAY_ATTEMPTSTATUS_MY', 2);
+define('SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY', 3);
+
 /**
  * Return an array of status options
  *
@@ -1052,7 +1058,7 @@ function scorm_debug_log_remove($type, $scoid) {
  * @return mixed
  */
 function scorm_print_overview($courses, &$htmlarray) {
-    global $USER, $CFG, $DB;
+    global $USER, $CFG;
 
     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
         return array();
@@ -1062,46 +1068,38 @@ function scorm_print_overview($courses, &$htmlarray) {
         return;
     }
 
-    $scormids = array();
-
-    // Do scorm::isopen() here without loading the whole thing for speed
-    foreach ($scorms as $key => $scorm) {
-        $time = time();
-        if ($scorm->timeopen) {
-            $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose);
-        }
-        if (empty($scorm->displayattemptstatus) && (empty($isopen) || empty($scorm->timeclose))) {
-            unset($scorms[$key]);
-        } else {
-            $scormids[] = $scorm->id;
-        }
-    }
-
-    if (empty($scormids)) {
-        // no scorms to look at - we're done
-        return true;
-    }
     $strscorm   = get_string('modulename', 'scorm');
     $strduedate = get_string('duedate', 'scorm');
 
     foreach ($scorms as $scorm) {
-        $str = '<div class="scorm overview"><div class="name">'.$strscorm. ': '.
-               '<a '.($scorm->visible ? '':' class="dimmed"').
-               'title="'.$strscorm.'" href="'.$CFG->wwwroot.
-               '/mod/scorm/view.php?id='.$scorm->coursemodule.'">'.
-               $scorm->name.'</a></div>';
-        if ($scorm->timeclose) {
-            $str .= '<div class="info">'.$strduedate.': '.userdate($scorm->timeclose).'</div>';
+        $time = time();
+        $showattemptstatus = false;
+        if ($scorm->timeopen) {
+            $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose);
         }
-        if ($scorm->displayattemptstatus == 1) {
-            require_once($CFG->dirroot.'/mod/scorm/locallib.php');
-            $str .= '<div class="details">'.scorm_get_attempt_status($USER, $scorm).'</div>';
+        if ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL ||
+                $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_MY) {
+            $showattemptstatus = true;
         }
-        $str .= '</div>';
-        if (empty($htmlarray[$scorm->course]['scorm'])) {
-            $htmlarray[$scorm->course]['scorm'] = $str;
-        } else {
-            $htmlarray[$scorm->course]['scorm'] .= $str;
+        if ($showattemptstatus || !empty($isopen) || !empty($scorm->timeclose)) {
+            $str = '<div class="scorm overview"><div class="name">'.$strscorm. ': '.
+                '<a '.($scorm->visible ? '':' class="dimmed"').
+                'title="'.$strscorm.'" href="'.$CFG->wwwroot.
+                '/mod/scorm/view.php?id='.$scorm->coursemodule.'">'.
+                $scorm->name.'</a></div>';
+            if ($scorm->timeclose) {
+                $str .= '<div class="info">'.$strduedate.': '.userdate($scorm->timeclose).'</div>';
+            }
+            if ($showattemptstatus) {
+                require_once($CFG->dirroot.'/mod/scorm/locallib.php');
+                $str .= '<div class="details">'.scorm_get_attempt_status($USER, $scorm).'</div>';
+            }
+            $str .= '</div>';
+            if (empty($htmlarray[$scorm->course]['scorm'])) {
+                $htmlarray[$scorm->course]['scorm'] = $str;
+            } else {
+                $htmlarray[$scorm->course]['scorm'] .= $str;
+            }
         }
     }
 }
@@ -1346,4 +1344,4 @@ function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COM
             $completion->update_state($cm, $completionstate, $userid);
         }
     }
-}
\ No newline at end of file
+}
index de433a3..c827778 100644 (file)
@@ -166,6 +166,19 @@ function scorm_get_attempts_array() {
 
     return $attempts;
 }
+
+/**
+ * Returns an array of the attempt status options
+ *
+ * @return array an array of attempt status options
+ */
+function scorm_get_attemptstatus_array() {
+    return array(SCORM_DISPLAY_ATTEMPTSTATUS_NO => get_string('no'),
+                 SCORM_DISPLAY_ATTEMPTSTATUS_ALL => get_string('attemptstatusall', 'scorm'),
+                 SCORM_DISPLAY_ATTEMPTSTATUS_MY => get_string('attemptstatusmy', 'scorm'),
+                 SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY => get_string('attemptstatusentry', 'scorm'));
+}
+
 /**
  * Extracts scrom package, sets up all variables.
  * Called whenever scorm changes
index 4b0837c..3d620d0 100644 (file)
@@ -195,7 +195,7 @@ class mod_scorm_mod_form extends moodleform_mod {
         $mform->setAdvanced('whatgrade', $cfg_scorm->whatgrade_adv);
 
         // Display attempt status
-        $mform->addElement('selectyesno', 'displayattemptstatus', get_string('displayattemptstatus', 'scorm'));
+        $mform->addElement('select', 'displayattemptstatus', get_string('displayattemptstatus', 'scorm'), scorm_get_attemptstatus_array());
         $mform->addHelpButton('displayattemptstatus', 'displayattemptstatus', 'scorm');
         $mform->setDefault('displayattemptstatus', $cfg_scorm->displayattemptstatus);
         $mform->setAdvanced('displayattemptstatus', $cfg_scorm->displayattemptstatus_adv);
index fa6d290..e9027d1 100644 (file)
@@ -137,7 +137,8 @@ require($CFG->dirroot . '/mod/scorm/tabs.php');
 // Print the main part of the page
 echo $OUTPUT->heading(format_string($scorm->name));
 $attemptstatus = '';
-if ($scorm->displayattemptstatus == 1 && empty($launch)) {
+if (empty($launch) && ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL ||
+         $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY)) {
     $attemptstatus = scorm_get_attempt_status($USER, $scorm, $cm);
 }
 echo $OUTPUT->box(format_module_intro('scorm', $scorm, $cm->id).$attemptstatus, 'generalbox boxaligncenter boxwidthwide', 'intro');
index ded9452..f83380d 100644 (file)
@@ -8,11 +8,13 @@ new features:
 
 * mod/xxx/adminlib.php may now include 'plugininfo_yoursubplugintype' class definition
   used by plugin_manager; it is recommended to store extra admin settings classes in this file
-  
+
 optional - no changes needed:
 
 * mod_lesson_renderer::header() now accepts an additional parameter $extrapagetitle
 
+* mod/data/lib.php data_get_all_recordids() now has two new optional variables:  $selectdata and $params.
+
 === 2.3 ===
 
 required changes in code:
diff --git a/pix/f/epub.png b/pix/f/epub.png
new file mode 100644 (file)
index 0000000..a660c73
Binary files /dev/null and b/pix/f/epub.png differ
diff --git a/pix/i/test.png b/pix/i/test.png
new file mode 100644 (file)
index 0000000..5860abe
Binary files /dev/null and b/pix/i/test.png differ
diff --git a/pix/i/test.svg b/pix/i/test.svg
new file mode 100644 (file)
index 0000000..522af08
--- /dev/null
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="114.88917"
+   height="114.88917"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="New document 1">
+  <defs
+     id="defs4">
+    <linearGradient
+       id="linearGradient3988">
+      <stop
+         style="stop-color:#000000;stop-opacity:0"
+         offset="0"
+         id="stop3990" />
+      <stop
+         style="stop-color:#ff8000;stop-opacity:1;"
+         offset="1"
+         id="stop3992" />
+    </linearGradient>
+    <linearGradient
+       id="linearGradient3982">
+      <stop
+         id="stop3984"
+         offset="0"
+         style="stop-color:#ffab00;stop-opacity:1;" />
+      <stop
+         id="stop3986"
+         offset="1"
+         style="stop-color:#ff8000;stop-opacity:1;" />
+    </linearGradient>
+    <radialGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3982"
+       id="radialGradient3980"
+       cx="360.68207"
+       cy="435.11246"
+       fx="360.68207"
+       fy="435.11246"
+       r="45.40649"
+       gradientTransform="matrix(1,0,0,0.98110214,0,8.2226956)"
+       gradientUnits="userSpaceOnUse"
+       spreadMethod="pad" />
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient3988"
+       id="linearGradient4005"
+       x1="323.18604"
+       y1="445.29483"
+       x2="375.84158"
+       y2="514.34235"
+       gradientUnits="userSpaceOnUse" />
+    <filter
+       inkscape:collect="always"
+       id="filter4055"
+       color-interpolation-filters="sRGB">
+      <feGaussianBlur
+         inkscape:collect="always"
+         stdDeviation="2.1197264"
+         id="feGaussianBlur4057" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.7480769"
+     inkscape:cx="-31.349998"
+     inkscape:cy="12.481512"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1855"
+     inkscape:window-height="1056"
+     inkscape:window-x="65"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-304.95364,-378.52595)">
+    <path
+       sodipodi:type="arc"
+       style="fill:url(#radialGradient3980);fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       id="path3204"
+       sodipodi:cx="360.68207"
+       sodipodi:cy="435.11246"
+       sodipodi:rx="44.90649"
+       sodipodi:ry="44.048405"
+       d="m 405.58856,435.11246 c 0,24.32726 -20.10532,44.0484 -44.90649,44.0484 -24.80117,0 -44.90649,-19.72114 -44.90649,-44.0484 0,-24.32726 20.10532,-44.04841 44.90649,-44.04841 24.80117,0 44.90649,19.72115 44.90649,44.04841 z" />
+    <path
+       sodipodi:type="arc"
+       id="path3994"
+       sodipodi:cx="374.98349"
+       sodipodi:cy="445.12344"
+       sodipodi:rx="52.343235"
+       sodipodi:ry="52.343235"
+       d="m 427.32673,445.12344 c 0,28.90837 -23.43487,52.34324 -52.34324,52.34324 -28.90837,0 -52.34324,-23.43487 -52.34324,-52.34324 0,-28.90837 23.43487,-52.34323 52.34324,-52.34323 28.90837,0 52.34324,23.43486 52.34324,52.34323 z"
+       transform="matrix(0.95405918,-0.29961823,0.29961823,0.95405918,-128.72531,123.64832)"
+       style="opacity:0.47736631;fill:url(#linearGradient4005);fill-opacity:1;filter:url(#filter4055)" />
+  </g>
+</svg>
index beb3788..2fbe4e0 100644 (file)
@@ -27,7 +27,7 @@ M.qtype_multianswer = M.qtype_multianswer || {};
 
 
 M.qtype_multianswer.init = function (Y, questiondiv) {
-    Y.one(questiondiv).all('label.subq').each(function(subqspan, i) {
+    Y.one(questiondiv).all('span.subquestion').each(function(subqspan, i) {
         var feedbackspan = subqspan.one('.feedbackspan');
         if (!feedbackspan) {
             return;
index 0134eb4..5afe4ff 100644 (file)
@@ -227,12 +227,13 @@ class qtype_multianswer_textfield_renderer extends qtype_multianswer_subq_render
                         $qa, 'question', 'answerfeedback', $matchinganswer->id),
                 s($correctanswer->answer), $options);
 
-        $output = '';
+        $output = html_writer::start_tag('span', array('class' => 'subquestion'));
         $output .= html_writer::tag('label', get_string('answer'),
                 array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
         $output .= html_writer::empty_tag('input', $inputattributes);
         $output .= $feedbackimg;
         $output .= $feedbackpopup;
+        $output .= html_writer::end_tag('span');
 
         return $output;
     }
@@ -294,12 +295,13 @@ class qtype_multianswer_multichoice_inline_renderer
                 $subq->format_text($rightanswer->answer, $rightanswer->answerformat,
                         $qa, 'question', 'answer', $rightanswer->id), $options);
 
-        $output = '';
+        $output = html_writer::start_tag('span', array('class' => 'subquestion'));
         $output .= html_writer::tag('label', get_string('answer'),
                 array('class' => 'subq accesshide', 'for' => $inputattributes['id']));
         $output .= $select;
         $output .= $feedbackimg;
         $output .= $feedbackpopup;
+        $output .= html_writer::end_tag('span');
 
         return $output;
     }
index 1d5664d..1a0210a 100644 (file)
@@ -34,4 +34,4 @@ $string['upload_error_no_file'] = 'No file was uploaded.';
 $string['upload_error_no_tmp_dir'] = 'PHP is missing a temporary folder.';
 $string['upload_error_cant_write'] = 'Failed to write file to disk.';
 $string['upload_error_extension'] = 'A PHP extension stopped the file upload.';
-$string['upload_error_invalid_file'] = 'The file \'{$a}\' has no data in it - did you try to upload a folder?';
\ No newline at end of file
+$string['upload_error_invalid_file'] = 'The file \'{$a}\' is either empty or a folder. To upload folders zip them first.';
\ No newline at end of file
index cb80bc2..0b4746e 100644 (file)
@@ -67,7 +67,7 @@ $string['ctmo_onfrontpageonly'] = 'in the front page only'; // ctmo == credits t
 $string['customcss'] = 'Custom CSS';
 $string['customcssdesc'] = 'Any CSS you enter here will be added to every page allowing your to easily customise this theme.';
 $string['customlogourl'] = 'Custom logo';
-$string['customlogourldesc'] = 'Change the logo for this theme by entering the URL to an image you wish to use (i.e. http://www.yoursite.local/mylogo.png). As a reference the default logo is 200px wide, 50px high and a transparent png will work best.';
+$string['customlogourldesc'] = 'Change the logo for this theme by entering the full or relatve URL to an image you wish to use (i.e. http://www.yoursite.tld/mylogo.png or ../path/to/your/logo.png). As a reference the default logo is 200px wide, 50px high and a transparent png will work best.';
 $string['displayheading'] = 'Display page heading';
 $string['displaylogo'] = 'Display logo';
 $string['fontsizereference'] = 'Font size reference';
@@ -77,7 +77,7 @@ $string['footnotedesc'] = 'The content from this textarea will be displayed in t
 $string['framemargin'] = 'Frame margin';
 $string['framemargindesc'] = 'Room between the frame and the edge of the browser window. (This setting will be ignored if "{$a}" is requested).';
 $string['frontpagelogourl'] = 'Custom front page logo';
-$string['frontpagelogourldesc'] = 'Change the logo that is displayed on the front page of your site by entering the URL to the image you wish to use (i.e. http://www.yoursite.local/myfrontpagelogo.png). This setting overrides the custom logo setting. As a reference the default logo is 300px wide, 80px high and a transparent png will work best.';
+$string['frontpagelogourldesc'] = 'Change the logo that is displayed on the front page of your site by entering the full or relatve URL to the image you wish to use (i.e. http://www.yoursite.tld/myfrontpagelogo.png or ../path/to/your/logo.png). This setting overrides the custom logo setting. As a reference the default logo is 300px wide, 80px high and a transparent png will work best.';
 $string['headerbgc'] = 'Header background colour';
 $string['headerbgcdesc'] = 'This sets the blocks header background colour for the theme.';
 $string['headercontent'] = 'Header content';
index 1ddeb26..75b7c59 100644 (file)
@@ -37,6 +37,13 @@ if ($slashargument = min_get_slash_argument()) {
     if (substr_count($slashargument, '/') < 3) {
         image_not_found();
     }
+    if (strpos($slashargument, '_s/') === 0) {
+        // Can't use SVG
+        $slashargument = substr($slashargument, 3);
+        $usesvg = false;
+    } else {
+        $usesvg = true;
+    }
     // image must be last because it may contain "/"
     list($themename, $component, $rev, $image) = explode('/', $slashargument, 4);
     $themename = min_clean_param($themename, 'SAFEDIR');
@@ -49,6 +56,7 @@ if ($slashargument = min_get_slash_argument()) {
     $component = min_optional_param('component', 'core', 'SAFEDIR');
     $rev       = min_optional_param('rev', -1, 'INT');
     $image     = min_optional_param('image', '', 'SAFEPATH');
+    $usesvg    = (bool)min_optional_param('svg', '1', 'INT');
 }
 
 if (empty($component) or $component === 'moodle' or $component === 'core') {
@@ -77,12 +85,15 @@ if ($rev > -1) {
         image_not_found();
     }
     $cacheimage = false;
-    if (file_exists("$candidatelocation/$image.gif")) {
-        $cacheimage = "$candidatelocation/$image.gif";
-        $ext = 'gif';
+    if ($usesvg && file_exists("$candidatelocation/$image.svg")) {
+        $cacheimage = "$candidatelocation/$image.svg";
+        $ext = 'svg';
     } else if (file_exists("$candidatelocation/$image.png")) {
         $cacheimage = "$candidatelocation/$image.png";
         $ext = 'png';
+    } else if (file_exists("$candidatelocation/$image.gif")) {
+        $cacheimage = "$candidatelocation/$image.gif";
+        $ext = 'gif';
     } else if (file_exists("$candidatelocation/$image.jpg")) {
         $cacheimage = "$candidatelocation/$image.jpg";
         $ext = 'jpg';
@@ -120,11 +131,38 @@ define('NO_UPGRADE_CHECK', true);  // Ignore upgrade check
 require("$CFG->dirroot/lib/setup.php");
 
 $theme = theme_config::load($themename);
-$imagefile = $theme->resolve_image_location($image, $component);
-
 $rev = theme_get_revision();
 $etag = sha1("$themename/$component/$rev/$image");
 
+// We're not using SVG and there is no cached version of this file (in any format).
+// As we're going to be caching a format other than svg, and because svg use is conditional we need to ensure that at the same
+// time we cache a version of the SVG if it exists. If we don't do this other users who ask for SVG would not ever get it as
+// there is a cached image already of another format.
+// Remember this only gets run once before any candidate exists, and only if we want a cached revision.
+if (!$usesvg && $rev > -1) {
+    $imagefile = $theme->resolve_image_location($image, $component, true);
+    if (!empty($imagefile) && is_readable($imagefile)) {
+        $cacheimage = cache_image($image, $imagefile, $candidatelocation);
+        $pathinfo = pathinfo($imagefile);
+        // There is no SVG equivilant, we've just successfully cached an image of another format.
+        if ($pathinfo['extension'] !== 'svg') {
+            // Serve the file as we would in a normal request.
+            if (connection_aborted()) {
+                die;
+            }
+            // make sure nothing failed
+            clearstatcache();
+            if (file_exists($cacheimage)) {
+                send_cached_image($cacheimage, $etag);
+            }
+            send_uncached_image($imagefile);
+            exit;
+        }
+    }
+}
+
+// Either SVG was requested or we've cached a SVG version and are ready to serve a regular format.
+$imagefile = $theme->resolve_image_location($image, $component, $usesvg);
 if (empty($imagefile) or !is_readable($imagefile)) {
     if ($rev > -1) {
         if (!file_exists($candidatelocation)) {
@@ -139,23 +177,7 @@ if (empty($imagefile) or !is_readable($imagefile)) {
 }
 
 if ($rev > -1) {
-    $pathinfo = pathinfo($imagefile);
-    $cacheimage = "$candidatelocation/$image.".$pathinfo['extension'];
-
-    clearstatcache();
-    if (!file_exists(dirname($cacheimage))) {
-        @mkdir(dirname($cacheimage), $CFG->directorypermissions, true);
-    }
-
-    // Prevent serving of incomplete file from concurrent request,
-    // the rename() should be more atomic than copy().
-    ignore_user_abort(true);
-    if (@copy($imagefile, $cacheimage.'.tmp')) {
-        rename($cacheimage.'.tmp', $cacheimage);
-        @chmod($cacheimage, $CFG->filepermissions);
-        @unlink($cacheimage.'.tmp'); // just in case anything fails
-    }
-    ignore_user_abort(false);
+    $cacheimage = cache_image($image, $imagefile, $candidatelocation);
     if (connection_aborted()) {
         die;
     }
@@ -229,10 +251,12 @@ function image_not_found() {
 
 function get_contenttype_from_ext($ext) {
     switch ($ext) {
-        case 'gif':
-            return 'image/gif';
+        case 'svg':
+            return 'image/svg+xml';
         case 'png':
             return 'image/png';
+        case 'gif':
+            return 'image/gif';
         case 'jpg':
         case 'jpeg':
             return 'image/jpeg';
@@ -241,3 +265,32 @@ function get_contenttype_from_ext($ext) {
     }
     return 'document/unknown';
 }
+
+/**
+ * Caches a given image file.
+ *
+ * @param string $image The name of the image that was requested.
+ * @param string $imagefile The location of the image file we want to cache.
+ * @param string $candidatelocation The location to cache it in.
+ * @return string The path to the cached image.
+ */
+function cache_image($image, $imagefile, $candidatelocation) {
+    global $CFG;
+    $pathinfo = pathinfo($imagefile);
+    $cacheimage = "$candidatelocation/$image.".$pathinfo['extension'];
+
+    clearstatcache();
+    if (!file_exists(dirname($cacheimage))) {
+        @mkdir(dirname($cacheimage), $CFG->directorypermissions, true);
+    }
+
+    // Prevent serving of incomplete file from concurrent request,
+    // the rename() should be more atomic than copy().
+    ignore_user_abort(true);
+    if (@copy($imagefile, $cacheimage.'.tmp')) {
+        rename($cacheimage.'.tmp', $cacheimage);
+        @chmod($cacheimage, $CFG->filepermissions);
+        @unlink($cacheimage.'.tmp'); // just in case anything fails
+    }
+    return $cacheimage;
+}
\ No newline at end of file
index f57325a..31ea104 100644 (file)
@@ -16,34 +16,34 @@ html {
     background: none;
 }
 
-body,h1,h2,h3,h4,h5,h6,p,ul,ol,dl,input,textarea { 
+body,h1,h2,h3,h4,h5,h6,p,ul,ol,dl,input,textarea {
     font-family: Georgia, "Times New Roman", Times, serif;
     color: #2a2513;
 }
 
-#wrapper { 
+#wrapper {
     background: #fff;
     margin: 2% 5%;
     padding: 5px;
     border: 1px solid #c1bc9d;
 }
 
-a { 
+a {
     color: #336699;
 }
 
-a:hover { 
+a:hover {
     text-decoration: underline;
 }
 
-.pagelayout-frontpage #page-content #region-main-box #region-post-box { 
+.pagelayout-frontpage #page-content #region-main-box #region-post-box {
     padding-top: 0;
 }
 
 /* Header
 ------------------------*/
 
-#page-header { 
+#page-header {
     background: #ddd6cc url([[pix:theme|header_grass]]) repeat-x 0 100%;
     margin-bottom: 5px;
 }
@@ -52,7 +52,7 @@ a:hover {
     margin-bottom: 0px;
 }
 
-.headermain { 
+.headermain {
     font-weight: normal;
     margin: 1em 0.5em 0.75em;
 }
@@ -67,10 +67,10 @@ a:hover {
     float:none;
 }
 
-/* Navbar 
+/* Navbar
 -------------------------*/
 
-.navbar { 
+.navbar {
     background: #aeb9c6 url([[pix:theme|breadcrumb]]) repeat-x 0 100%;
     padding: 5px;
 }
@@ -78,21 +78,21 @@ a:hover {
 /* Blocks
 -------------------------*/
 
-.block { 
+.block {
     border-color: #eee;
 }
 
-.block .header { 
+.block .header {
     background: #9eb1bf;
     padding-left: 5px;
 }
 
-.block .title { 
+.block .title {
     background: #867f6a;
     padding-left: 5px;
 }
 
-.block .title h2 { 
+.block .title h2 {
     background: #6e6855;
     margin: 0;
     padding: 5px;
@@ -100,24 +100,24 @@ a:hover {
     color: #fff;
 }
 
-.block_action { 
+.block_action {
     padding: 5px 0 0;
 }
 
-.block .content { 
+.block .content {
     background: #d8d2c6;
     border: 1px solid #867f6a;
 }
 
 .block .minicalendar td,
-.block .minicalendar th { 
+.block .minicalendar th {
     border-color: #d8d2c6;
 }
 
 /* Course
 ----------------------------*/
 
-.headingblock { 
+.headingblock {
     background: url([[pix:theme|headingblock]]) repeat-x 0 0;
     color: #fff;
     font-weight: normal;
@@ -142,10 +142,10 @@ a:hover {
     background: #fff;
 }
 
-/* Forums 
+/* Forums
 -----------------------------*/
 
-.forumpost .topic { 
+.forumpost .topic {
     background: #cad5e1;
     border-width: 1px;
     border-color: #eee #eee #aaa;
@@ -158,11 +158,11 @@ a:hover {
 }
 
 
-.forumpost .starter .subject { 
+.forumpost .starter .subject {
     font-weight: bold;
 }
 
-.forumpost .content { 
+.forumpost .content {
     border-color: #eee;
     border-width: 0 1px 1px;
     border-style: solid;
@@ -172,56 +172,56 @@ a:hover {
 /* Dock
 -----------------------------*/
 
-body.has_dock { 
+body.has_dock {
     margin: 0;
 }
 
-#dock { 
+#dock {
     left: 5%;
     margin-left: -29px;
     border-width: 0;
     background-color: transparent;
 }
 
-#dock .controls { 
+#dock .controls {
     bottom: auto;
     background-color: #DDD6CC;
 }
 
-#dock .dockeditem_container { 
+#dock .dockeditem_container {
     position: relative;
 }
 
-#dock .dockeditem.firstdockitem { 
+#dock .dockeditem.firstdockitem {
     margin-top: 50px;
 }
 
-#dock .dockeditem { 
+#dock .dockeditem {
     background-color: #fff;
     padding: 2px;
     padding-right: 0px;
 }
 
-#dock .dockedtitle { 
+#dock .dockedtitle {
     border-width: 0;
 }
 
-#dock .dockedtitle h2 { 
+#dock .dockedtitle h2 {
     margin: 0;
     padding: 10px 3px;
 }
 
-#dock .dockedtitle.activeitem { 
+#dock .dockedtitle.activeitem {
     background-color: #817b65;
     width: 35px;
 }
 
-#dockeditempanel { 
+#dockeditempanel {
     background-color: #817b65;
     margin-left: 5px;
 }
 
-#dockeditempanel .dockeditempanel_content { 
+#dockeditempanel .dockeditempanel_content {
     background-color: #eee9e0;
     margin: 0 3px;
     position: relative;
@@ -230,78 +230,77 @@ body.has_dock {
     border-color: #6f6856;
 }
 
-#dockeditempanel .dockeditempanel_hd { 
+#dockeditempanel .dockeditempanel_hd {
     background-image: url([[pix:theme|headingblock]]);
     border-width: 0;
 }
 
-#dockeditempanel .dockeditempanel_hd h2 { 
+#dockeditempanel .dockeditempanel_hd h2 {
     font-size: 1em;
     color: #fff;
 }
 
 /*cutom menu */
 /*YUI Reset */
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menu-content,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menu-label,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menu-label-active,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menuitem-active .yui3-menuitem-content,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menu-label-menuvisible {background-position: -10000px -10000px;}
-.yui3-skin-sam #page .yui3-menu-label,
-.yui3-skin-sam #page .yui3-menu .yui3-menu .yui3-menu-label,
-.yui3-skin-sam #page .yui3-menubuttonnav .yui3-menu-label em {background-position: right center;}
-.yui3-skin-sam #page .yui3-splitbuttonnav .yui3-menu-label .yui3-menu-toggle {background-position: 3px center;}
-.yui3-skin-sam #page .yui3-splitbuttonnav .yui3-menu-label-menuvisible .yui3-menu-toggle {background-position: 0% 50%;}
-
+#custommenu .yui3-menu-horizontal .yui3-menu-content,
+#custommenu .yui3-menu-horizontal .yui3-menu-label,
+#custommenu .yui3-menu-horizontal .yui3-menu-label-active,
+#custommenu .yui3-menu-horizontal .yui3-menuitem-active .yui3-menuitem-content,
+#custommenu .yui3-menu-horizontal .yui3-menu-label-menuvisible {
+    background-position: -10000px -10000px;
+}
+#custommenu .yui3-menu-label,
+#custommenu .yui3-menu .yui3-menu .yui3-menu-label,
+#custommenu .yui3-menubuttonnav .yui3-menu-label em {
+    background-position: right center;
+}
+#custommenu .yui3-splitbuttonnav .yui3-menu-label .yui3-menu-toggle {
+    background-position: 3px center;
+}
+#custommenu .yui3-splitbuttonnav .yui3-menu-label-menuvisible .yui3-menu-toggle {
+    background-position: 0% 50%;
+}
 #custommenu {
     clear: both;
     background-image: url([[pix:theme|headingblock]]);
     margin-bottom: 5px;
 }
-
-.yui3-skin-sam #page .yui3-menu-label,
-.yui3-skin-sam #page .yui3-menuitem-content  {
+#custommenu .yui3-menu-label,
+#custommenu .yui3-menuitem-content {
     color: #fff;
     font-weight: 800;
     line-height: 30px;
 }
-
-.custom_menu_submenu .yui3-menu-label,
-.custom_menu_submenu .yui3-menuitem-content {
-    color: #000 !important;
-    text-shadow: none !important;
+#custommenu .custom_menu_submenu .yui3-menu-label,
+#custommenu .custom_menu_submenu .yui3-menuitem-content {
+    color: #333;
+    text-shadow: none;
     line-height: 25px;
 }
-
-.yui3-skin-sam #page .yui3-menu-label.yui3-menu-label-active,
-.yui3-skin-sam #page .yui3-menu-label.yui3-menu-label-menuvisible,
-.yui3-skin-sam #page .yui3-menuitem-active .yui3-menuitem-content {
+#custommenu .yui3-menu-label.yui3-menu-label-active,
+#custommenu .yui3-menu-label.yui3-menu-label-menuvisible,
+#custommenu .yui3-menuitem-active .yui3-menuitem-content {
     color: #000;
     background-color: #d8d2c6;
 }
-
-.yui3-skin-sam #page .yui3-menu-content,
-.yui3-skin-sam #page .yui3-menu-content,
-.yui3-skin-sam #page .yui3-menu .yui3-menu .yui3-menu-content,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menu-label,
-.yui3-skin-sam #page .yui3-menu-horizontal .yui3-menuitem-content  {
-    border: none !important;
+#custommenu .yui3-menu-content,
+#custommenu .yui3-menu-content,
+#custommenu .yui3-menu .yui3-menu .yui3-menu-content,
+#custommenu .yui3-menu-horizontal .yui3-menu-label,
+#custommenu .yui3-menu-horizontal .yui3-menuitem-content  {
+    border: 0 none;
 }
-
-.yui3-skin-sam .yui3-menu-horizontal .yui3-menu-label,
-.yui3-skin-sam .yui3-menu-horizontal .yui3-menuitem-content {
-    border-color:#808080;
-    border-style:solid;
-    border-width:0px 0;
+#custommenu .yui3-menu-horizontal .yui3-menu-label,
+#custommenu .yui3-menu-horizontal .yui3-menuitem-content {
+    border: 0 none;
 }
-
-#page .custom_menu_submenu {
-    border: 2px solid #d8d2c6 !important;
+#custommenu .custom_menu_submenu {
+    border: 2px solid #d8d2c6;
     background: #fff;
-     -webkit-border-radius: 2px;
-    -moz-border-radius: 2px;
-    border-radius: 2px;
+    -webkit-border-radius: 2px;
+       -moz-border-radius: 2px;
+            border-radius: 2px;
     -webkit-box-shadow: 0px 1px 3px #ccc;
-    -moz-box-shadow: 0px 1px 3px #ccc;
-    box-shadow: 0px 1px 3px #ccc;
-}
\ No newline at end of file
+       -moz-box-shadow: 0px 1px 3px #ccc;
+            box-shadow: 0px 1px 3px #ccc;
+}
index d3410e7..7e24f07 100644 (file)
@@ -450,6 +450,7 @@ table#tag-management-list {margin: 10px auto;width: 80%;}
 .portfolio-add-icon {margin-left:5px;}
 
 /* Messaging options */
+#page-message-edit .mform fieldset div.messagesettingcomponent {float: left;}
 #page-message-edit table.generaltable th.c0 {text-align: left;}
 #page-message-edit table.generaltable td.c0 {text-align: right;}
 #page-message-edit table.generaltable td.disallowed {text-align: center;vertical-align:middle;}
index d1094a0..24daf82 100644 (file)
@@ -221,8 +221,9 @@ M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearc
 
             // Output each optgroup.
             var count = 0;
-            for (var groupname in data.results) {
-                this.output_group(groupname, data.results[groupname], selectedusers, true);
+            for (var key in data.results) {
+                var groupdata = data.results[key];
+                this.output_group(groupdata.name, groupdata.users, selectedusers, true);
                 count++;
             }
             if (!count) {
@@ -248,14 +249,14 @@ M.core_user.init_user_selector = function (Y, name, hash, extrafields, lastsearc
         output_group : function(groupname, users, selectedusers, processsingle) {
             var optgroup = Y.Node.create('<optgroup></optgroup>');
             var count = 0;
-            for (var userid in users) {
-                var user = users[userid];
-                var option = Y.Node.create('<option value="'+userid+'">'+user.name+'</option>');
+            for (var key in users) {
+                var user = users[key];
+                var option = Y.Node.create('<option value="'+user.id+'">'+user.name+'</option>');
                 if (user.disabled) {
                     option.set('disabled', true);
-                } else if (selectedusers===true || selectedusers[userid]) {
+                } else if (selectedusers===true || selectedusers[user.id]) {
                     option.set('selected', true);
-                    delete selectedusers[userid];
+                    delete selectedusers[user.id];
                 } else {
                     option.set('selected', false);
                 }
index a4f45e4..a7b8d9c 100644 (file)
@@ -78,9 +78,11 @@ if (isset($options['file'])) {
 $userselector = new $classname($name, $options);
 
 // Do the search and output the results.
-$users = $userselector->find_users($search);
-foreach ($users as &$group) {
-    foreach ($group as $user) {
+$results = $userselector->find_users($search);
+$json = array();
+foreach ($results as $groupname => $users) {
+    $groupdata = array('name' => $groupname, 'users' => array());
+    foreach ($users as $user) {
         $output = new stdClass;
         $output->id = $user->id;
         $output->name = $userselector->output_user($user);
@@ -90,8 +92,9 @@ foreach ($users as &$group) {
         if (!empty($user->infobelow)) {
             $output->infobelow = $user->infobelow;
         }
-        $group[$user->id] = $output;
+        $groupdata['users'][] = $output;
     }
+    $json[] = $groupdata;
 }
 
-echo json_encode(array('results' => $users));
+echo json_encode(array('results' => $json));