Merge branch 'wip-MDL-36048-master' of git://github.com/marinaglancy/moodle
authorDan Poltawski <dan@moodle.com>
Wed, 14 Nov 2012 01:58:27 +0000 (09:58 +0800)
committerDan Poltawski <dan@moodle.com>
Wed, 14 Nov 2012 01:58:27 +0000 (09:58 +0800)
101 files changed:
admin/courseformats.php [new file with mode: 0644]
admin/settings/courses.php
admin/settings/plugins.php
backup/moodle2/backup_stepslib.php
cache/classes/definition.php
cache/classes/dummystore.php
cache/classes/interfaces.php
cache/classes/loaders.php
cache/locallib.php
cache/stores/file/lib.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
cache/stores/mongodb/lib.php
cache/stores/session/lib.php
cache/stores/static/lib.php
course/edit_form.php
course/format/formatlegacy.php
course/format/lib.php
course/format/upgrade.txt
course/lib.php
course/renderer.php
enrol/renderer.php
lang/en/admin.php
lang/en/error.php
lang/en/moodle.php
lib/adminlib.php
lib/db/install.xml
lib/db/upgrade.php
lib/editor/tinymce/plugins/dragmath/dragmath.php
lib/editor/tinymce/plugins/loader.php
lib/editor/tinymce/plugins/moodleemoticon/dialog.php
lib/editor/tinymce/plugins/spellchecker/classes/GoogleSpell.php
lib/editor/tinymce/readme_moodle.txt
lib/installlib.php
lib/messagelib.php
lib/moodlelib.php
lib/outputlib.php
lib/pluginlib.php
lib/setup.php
lib/setuplib.php
lib/statslib.php
lib/tests/fixtures/statslib-test00.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test01.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test02.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test03.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test04.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test05.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test06.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test07.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test08.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test09.xml [new file with mode: 0644]
lib/tests/fixtures/statslib-test10.xml [new file with mode: 0644]
lib/tests/messagelib_test.php [new file with mode: 0644]
lib/tests/moodlelib_test.php
lib/tests/outputlib_test.php
lib/tests/statslib_test.php [new file with mode: 0644]
lib/weblib.php
mod/assign/gradingtable.php
mod/quiz/lib.php
mod/quiz/tests/generator/lib.php [new file with mode: 0644]
mod/scorm/datamodel.php
mod/survey/lang/en/survey.php
mod/workshop/lang/en/workshop.php
pix/help.png
pix/help.svg
pix/i/filter.png [new file with mode: 0644]
pix/i/filter.svg [new file with mode: 0644]
pix/i/info.png [new file with mode: 0644]
pix/i/info.svg [new file with mode: 0644]
pix/i/item.png [new file with mode: 0644]
pix/i/item.svg [new file with mode: 0644]
pix/i/navigationitem.png
pix/i/navigationitem.svg [new file with mode: 0644]
pix/i/outcomes.png
pix/i/outcomes.svg
pix/t/block_to_dock.png
pix/t/block_to_dock.svg [new file with mode: 0644]
pix/t/block_to_dock_rtl.png [new file with mode: 0644]
pix/t/block_to_dock_rtl.svg [new file with mode: 0644]
pix/t/collapsed.png
pix/t/collapsed.svg [new file with mode: 0644]
pix/t/collapsed_empty.png
pix/t/collapsed_empty.svg [new file with mode: 0644]
pix/t/collapsed_empty_rtl.png
pix/t/collapsed_empty_rtl.svg [new file with mode: 0644]
pix/t/collapsed_rtl.png
pix/t/collapsed_rtl.svg [new file with mode: 0644]
pix/t/dock_to_block.png
pix/t/dock_to_block.svg [new file with mode: 0644]
pix/t/dock_to_block_rtl.png [new file with mode: 0644]
pix/t/dock_to_block_rtl.svg [new file with mode: 0644]
pix/t/dockclose.png
pix/t/dockclose.svg [new file with mode: 0644]
pix/t/expanded.png
pix/t/expanded.svg [new file with mode: 0644]
pix/t/switch_minus.png [new file with mode: 0644]
pix/t/switch_minus.svg [new file with mode: 0644]
pix/t/switch_plus.png [new file with mode: 0644]
pix/t/switch_plus.svg [new file with mode: 0644]
user/selector/lib.php
version.php

diff --git a/admin/courseformats.php b/admin/courseformats.php
new file mode 100644 (file)
index 0000000..a8377de
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Allows the admin to enable, disable and uninstall course formats
+ *
+ * @package    core_admin
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/pluginlib.php');
+
+$action  = required_param('action', PARAM_ALPHANUMEXT);
+$formatname   = required_param('format', PARAM_PLUGIN);
+$confirm = optional_param('confirm', 0, PARAM_BOOL);
+
+$syscontext = context_system::instance();
+$PAGE->set_url('/admin/courseformats.php');
+$PAGE->set_context($syscontext);
+
+require_login();
+require_capability('moodle/site:config', $syscontext);
+require_sesskey();
+
+$return = new moodle_url('/admin/settings.php', array('section' => 'manageformats'));
+
+$allplugins = plugin_manager::instance()->get_plugins();
+$formatplugins = $allplugins['format'];
+$sortorder = array_flip(array_keys($formatplugins));
+
+if (!isset($formatplugins[$formatname])) {
+    print_error('courseformatnotfound', 'error', $return, $formatname);
+}
+
+switch ($action) {
+    case 'disable':
+        if ($formatplugins[$formatname]->is_enabled()) {
+            if (get_config('moodlecourse', 'format') === $formatname) {
+                print_error('cannotdisableformat', 'error', $return);
+            }
+            set_config('disabled', 1, 'format_'. $formatname);
+        }
+        break;
+    case 'enable':
+        if (!$formatplugins[$formatname]->is_enabled()) {
+            unset_config('disabled', 'format_'. $formatname);
+        }
+        break;
+    case 'up':
+        if ($sortorder[$formatname]) {
+            $currentindex = $sortorder[$formatname];
+            $seq = array_keys($formatplugins);
+            $seq[$currentindex] = $seq[$currentindex-1];
+            $seq[$currentindex-1] = $formatname;
+            set_config('format_plugins_sortorder', implode(',', $seq));
+        }
+        break;
+    case 'down':
+        if ($sortorder[$formatname] < count($sortorder)-1) {
+            $currentindex = $sortorder[$formatname];
+            $seq = array_keys($formatplugins);
+            $seq[$currentindex] = $seq[$currentindex+1];
+            $seq[$currentindex+1] = $formatname;
+            set_config('format_plugins_sortorder', implode(',', $seq));
+        }
+        break;
+    case 'uninstall':
+        echo $OUTPUT->header();
+        echo $OUTPUT->heading(get_string('courseformats', 'moodle'));
+
+        $coursecount = $DB->count_records('course', array('format' => $formatname));
+        if ($coursecount) {
+            // Check that default format is set. It will be used to convert courses
+            // using this format
+            $defaultformat = get_config('moodlecourse', 'format');
+            $defaultformat = $formatplugins[get_config('moodlecourse', 'format')];
+            if (!$defaultformat) {
+                echo $OUTPUT->error_text(get_string('defaultformatnotset', 'admin'));
+                echo $OUTPUT->footer();
+                exit;
+            }
+        }
+
+        $format = $formatplugins[$formatname];
+        $deleteurl = $format->get_uninstall_url();
+        if (!$deleteurl) {
+            // somebody was trying to cheat and type non-existing link
+            echo $OUTPUT->error_text(get_string('cannotuninstall', 'admin', $format->displayname));
+            echo $OUTPUT->footer();
+            exit;
+        }
+
+        if (!$confirm) {
+            if ($coursecount) {
+                $message = get_string('formatuninstallwithcourses', 'admin',
+                        (object)array('count' => $coursecount, 'format' => $format->displayname,
+                            'defaultformat' => $defaultformat->displayname));
+            } else {
+                $message = get_string('formatuninstallconfirm', 'admin', $format->displayname);
+            }
+            $deleteurl->param('confirm', 1);
+            echo $OUTPUT->confirm($message, $deleteurl, $return);
+        } else {
+            $a = new stdClass();
+            $a->plugin = $format->displayname;
+            $a->directory = $format->rootdir;
+            uninstall_plugin('format', $formatname);
+            echo $OUTPUT->notification(get_string('formatuninstalled', 'admin', $a), 'notifysuccess');
+            echo $OUTPUT->continue_button($return);
+        }
+
+        echo $OUTPUT->footer();
+        exit;
+}
+redirect($return);
index 363bfb1..b68a10a 100644 (file)
@@ -15,9 +15,10 @@ if ($hassiteconfig
 /// NOTE: these settings must be applied after all other settings because they depend on them
     ///main course settings
     $temp = new admin_settingpage('coursesettings', new lang_string('coursesettings'));
-    $courseformats = get_plugin_list('format');
+    require_once($CFG->dirroot.'/course/lib.php');
+    $courseformats = get_sorted_course_formats(true);
     $formcourseformats = array();
-    foreach ($courseformats as $courseformat => $courseformatdir) {
+    foreach ($courseformats as $courseformat) {
         $formcourseformats[$courseformat] = new lang_string('pluginname', "format_$courseformat");
     }
     $temp->add(new admin_setting_configselect('moodlecourse/format', new lang_string('format'), new lang_string('coursehelpformat'), 'weeks',$formcourseformats));
index 8d29028..b7fa57e 100644 (file)
@@ -20,6 +20,15 @@ if ($hassiteconfig) {
     // hidden script for converting journals to online assignments (or something like that) linked from elsewhere
     $ADMIN->add('modsettings', new admin_externalpage('oacleanup', 'Online Assignment Cleanup', $CFG->wwwroot.'/'.$CFG->admin.'/oacleanup.php', 'moodle/site:config', true));
 
+    // course formats
+    $ADMIN->add('modules', new admin_category('formatsettings', new lang_string('courseformats')));
+    $temp = new admin_settingpage('manageformats', new lang_string('manageformats', 'core_admin'));
+    $temp->add(new admin_setting_manageformats());
+    $ADMIN->add('formatsettings', $temp);
+    foreach ($allplugins['format'] as $format) {
+        $format->load_settings($ADMIN, 'formatsettings', $hassiteconfig);
+    }
+
     // blocks
     $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks')));
     $ADMIN->add('blocksettings', new admin_page_manageblocks());
@@ -303,6 +312,10 @@ if ($hassiteconfig) {
 
 // Question type settings
 if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
+    if (!$hassiteconfig) {
+        require_once("$CFG->libdir/pluginlib.php");
+        $allplugins = plugin_manager::instance()->get_plugins();
+    }
     // Question behaviour settings.
     $ADMIN->add('modules', new admin_category('qbehavioursettings', new lang_string('questionbehaviours', 'admin')));
     $ADMIN->add('qbehavioursettings', new admin_page_manageqbehaviours());
index a82cd02..f2ba2af 100644 (file)
@@ -912,7 +912,7 @@ class backup_gradebook_structure_step extends backup_structure_step {
         $grade_category   = new backup_nested_element('grade_category', array('id'), array(
                 //'courseid',
                 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
-                'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
+                'droplow', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
                 'timecreated', 'timemodified', 'hidden'));
 
         $letters = new backup_nested_element('grade_letters');
index d02e082..fd93324 100644 (file)
@@ -290,10 +290,10 @@ class cache_definition {
             throw new coding_exception('You must provide a mode when creating a cache definition');
         }
         if (!array_key_exists('component', $definition)) {
-            throw new coding_exception('You must provide a mode when creating a cache definition');
+            throw new coding_exception('You must provide a component when creating a cache definition');
         }
         if (!array_key_exists('area', $definition)) {
-            throw new coding_exception('You must provide a mode when creating a cache definition');
+            throw new coding_exception('You must provide an area when creating a cache definition');
         }
         $mode = (int)$definition['mode'];
         $component = (string)$definition['component'];
@@ -638,7 +638,7 @@ class cache_definition {
      */
     public function get_data_source() {
         if (!$this->has_data_source()) {
-            throw new coding_exception('This cache does not use a datasource.');
+            throw new coding_exception('This cache does not use a data source.');
         }
         return forward_static_call(array($this->datasource, 'get_instance_for_cache'), $this);
     }
index 870d960..4888f60 100644 (file)
@@ -147,7 +147,7 @@ class cachestore_dummy implements cache_store {
      * Returns true if this store supports multiple identifiers.
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 1f96fa0..983bc6b 100644 (file)
@@ -194,8 +194,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if the lock could be acquired, false otherwise.
@@ -207,8 +207,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it,
@@ -221,8 +221,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if the lock has been released, false if there was a problem releasing the lock.
@@ -314,7 +314,7 @@ interface cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers();
+    public function supports_multiple_identifiers();
 
     /**
      * Returns true if this cache store instance promotes data guarantee.
index d0b3a4d..c837cd8 100644 (file)
@@ -18,7 +18,7 @@
  * Cache loaders
  *
  * This file is part of Moodle's cache API, affectionately called MUC.
- * It contains the components that are requried in order to use caching.
+ * It contains the components that are required in order to use caching.
  *
  * @package    core
  * @category   cache
@@ -795,7 +795,7 @@ class cache implements cache_loader {
      */
     protected function parse_key($key) {
         // First up if the store supports multiple keys we'll go with that.
-        if ($this->store->supports_multiple_indentifiers()) {
+        if ($this->store->supports_multiple_identifiers()) {
             $result = $this->definition->generate_multi_key_parts();
             $result['key'] = $key;
             return $result;
index fc927e0..e8e43a3 100644 (file)
@@ -518,7 +518,7 @@ abstract class cache_administration_helper extends cache_helper {
                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
                 ),
                 'supports' => array(
-                    'multipleidentifiers' => $store->supports_multiple_indentifiers(),
+                    'multipleidentifiers' => $store->supports_multiple_identifiers(),
                     'dataguarantee' => $store->supports_data_guarantee(),
                     'nativettl' => $store->supports_native_ttl(),
                     'nativelocking' => ($store instanceof cache_is_lockable),
index 0aa3f48..0a18b3a 100644 (file)
@@ -209,7 +209,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
@@ -255,8 +255,11 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      * Pre-scan the cache to see which keys are present.
      */
     protected function prescan_keys() {
-        foreach (glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT) as $filename) {
-            $this->keys[basename($filename)] = filemtime($filename);
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
+                $this->keys[basename($filename)] = filemtime($filename);
+            }
         }
     }
 
@@ -510,8 +513,11 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      * @return boolean True on success. False otherwise.
      */
     public function purge() {
-        foreach (glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT) as $filename) {
-            @unlink($filename);
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
+                @unlink($filename);
+            }
         }
         $this->keys = array();
         return true;
index d684403..d62fac7 100644 (file)
@@ -178,7 +178,7 @@ class cachestore_memcache implements cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 527a5ca..419e640 100644 (file)
@@ -204,7 +204,7 @@ class cachestore_memcached implements cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 4bd4229..2427d92 100644 (file)
@@ -230,7 +230,7 @@ class cachestore_mongodb implements cache_store {
      * Returns true if this store is making use of multiple identifiers.
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return $this->extendedmode;
     }
 
index bd9d0a0..ba82c17 100644 (file)
@@ -127,7 +127,7 @@ class cachestore_session extends session_data_store implements cache_store, cach
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 8e0f41c..eae6c00 100644 (file)
@@ -127,7 +127,7 @@ class cachestore_static extends static_data_store implements cache_store, cache_
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 70b709a..34dbe12 100644 (file)
@@ -113,11 +113,20 @@ class course_edit_form extends moodleform {
             $mform->hardFreeze('summary_editor');
         }
 
-        $courseformats = get_plugin_list('format');
+        $courseformats = get_sorted_course_formats(true);
         $formcourseformats = array();
-        foreach ($courseformats as $courseformat => $formatdir) {
+        foreach ($courseformats as $courseformat) {
             $formcourseformats[$courseformat] = get_string('pluginname', "format_$courseformat");
         }
+        if (isset($course->format)) {
+            $course->format = course_get_format($course)->get_format(); // replace with default if not found
+            if (!in_array($course->format, $courseformats)) {
+                // this format is disabled. Still display it in the dropdown
+                $formcourseformats[$course->format] = get_string('withdisablednote', 'moodle',
+                        get_string('pluginname', 'format_'.$course->format));
+            }
+        }
+
         $mform->addElement('select', 'format', get_string('format'), $formcourseformats);
         $mform->addHelpButton('format', 'format');
         $mform->setDefault('format', $courseconfig->format);
index 838c6fc..82af237 100644 (file)
@@ -339,9 +339,21 @@ class format_legacy extends format_base {
         if ($oldcourse !== null) {
             $data = (array)$data;
             $oldcourse = (array)$oldcourse;
-            foreach ($this->course_format_options() as $key => $unused) {
-                if (array_key_exists($key, $oldcourse) && !array_key_exists($key, $data)) {
-                    $data[$key] = $oldcourse[$key];
+            $options = $this->course_format_options();
+            foreach ($options as $key => $unused) {
+                if (!array_key_exists($key, $data)) {
+                    if (array_key_exists($key, $oldcourse)) {
+                        $data[$key] = $oldcourse[$key];
+                    } else if ($key === 'numsections') {
+                        // If previous format does not have the field 'numsections' and this one does,
+                        // and $data['numsections'] is not set fill it with the maximum section number from the DB
+                        $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                            WHERE course = ?', array($this->courseid));
+                        if ($maxsection) {
+                            // If there are no sections, or just default 0-section, 'numsections' will be set to default
+                            $data['numsections'] = $maxsection;
+                        }
+                    }
                 }
             }
         }
index 3e6af20..7f6dfc9 100644 (file)
@@ -97,13 +97,21 @@ abstract class format_base {
         if ($format === 'site') {
             return $format;
         }
-        $plugins = get_plugin_list('format'); // TODO MDL-35260 filter only enabled
-        if (isset($plugins[$format])) {
+        $plugins = get_sorted_course_formats();
+        if (in_array($format, $plugins)) {
             return $format;
         }
         // Else return default format
-        $defaultformat = reset($plugins); // TODO MDL-35260 get default format from config
-        debugging('Format plugin format_'.$format.' is not found or is not enabled. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
+        $defaultformat = get_config('moodlecourse', 'format');
+        if (!in_array($defaultformat, $plugins)) {
+            // when default format is not set correctly, use the first available format
+            $defaultformat = reset($plugins);
+        }
+        static $warningprinted = array();
+        if (empty($warningprinted[$format])) {
+            debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
+            $warningprinted[$format] = true;
+        }
         return $defaultformat;
     }
 
index 6beb68b..f14a891 100644 (file)
@@ -12,6 +12,7 @@ format.
   functions callback_XXXX_request_key() are no longer used (where XXXX is the course format name)
 * functions get_generic_section_name(), get_all_sections(), add_mod_to_section(), get_all_mods()
   are deprecated. See their phpdocs in lib/deprecatedlib.php on how to replace them
+* Course formats may now have their settings.php file as the most of other plugin types
 
 === 2.3 ===
 
index a772578..2fc4b68 100644 (file)
@@ -4512,6 +4512,35 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
     return true;
 }
 
+/**
+ * Returns the sorted list of available course formats, filtered by enabled if necessary
+ *
+ * @param bool $enabledonly return only formats that are enabled
+ * @return array array of sorted format names
+ */
+function get_sorted_course_formats($enabledonly = false) {
+    global $CFG;
+    $formats = get_plugin_list('format');
+
+    if (!empty($CFG->format_plugins_sortorder)) {
+        $order = explode(',', $CFG->format_plugins_sortorder);
+        $order = array_merge(array_intersect($order, array_keys($formats)),
+                    array_diff(array_keys($formats), $order));
+    } else {
+        $order = array_keys($formats);
+    }
+    if (!$enabledonly) {
+        return $order;
+    }
+    $sortedformats = array();
+    foreach ($order as $formatname) {
+        if (!get_config('format_'.$formatname, 'disabled')) {
+            $sortedformats[] = $formatname;
+        }
+    }
+    return $sortedformats;
+}
+
 /**
  * The URL to use for the specified course (with section)
  *
index bce30df..bbb761e 100644 (file)
@@ -233,17 +233,19 @@ class core_course_renderer extends plugin_renderer_base {
         // Put all options into one tag 'alloptions' to allow us to handle scrolling
         $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
 
-        // Activities
-        $activities = array_filter($modules,
-                create_function('$mod', 'return ($mod->archetype !== MOD_CLASS_RESOURCE);'));
+         // Activities
+        $activities = array_filter($modules, function($mod) {
+            return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
+        });
         if (count($activities)) {
             $formcontent .= $this->course_modchooser_title('activities');
             $formcontent .= $this->course_modchooser_module_types($activities);
         }
 
         // Resources
-        $resources = array_filter($modules,
-                create_function('$mod', 'return ($mod->archetype === MOD_CLASS_RESOURCE);'));
+        $resources = array_filter($modules, function($mod) {
+            return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
+        });
         if (count($resources)) {
             $formcontent .= $this->course_modchooser_title('resources');
             $formcontent .= $this->course_modchooser_module_types($resources);
index d630884..aa71854 100644 (file)
@@ -431,7 +431,7 @@ class course_enrolment_table extends html_table implements renderable {
 
         $this->page           = optional_param(self::PAGEVAR, 0, PARAM_INT);
         $this->perpage        = optional_param(self::PERPAGEVAR, self::DEFAULTPERPAGE, PARAM_INT);
-        $this->sort           = optional_param(self::SORTVAR, self::DEFAULTSORT, PARAM_ALPHA);
+        $this->sort           = optional_param(self::SORTVAR, self::DEFAULTSORT, PARAM_ALPHANUM);
         $this->sortdirection  = optional_param(self::SORTDIRECTIONVAR, self::DEFAULTSORTDIRECTION, PARAM_ALPHA);
 
         $this->attributes = array('class'=>'userenrolment');
index 208f556..dbf755f 100644 (file)
@@ -95,6 +95,7 @@ $string['calendarexportsalt'] = 'Calendar export salt';
 $string['calendarsettings'] = 'Calendar';
 $string['calendar_weekend'] = 'Weekend days';
 $string['cannotdeletemodfilter'] = 'You cannot uninstall the \'{$a->filter}\' because it is part of the \'{$a->module}\' module.';
+$string['cannotuninstall'] = '{$a} can not be uninstalled.';
 $string['cfgwwwrootslashwarning'] = 'You have defined $CFG->wwwroot incorrectly in your config.php file. You have included a \'/\' character at the end. Please remove it, or you will experience strange bugs like <a href=\'http://tracker.moodle.org/browse/MDL-11061\'>MDL-11061</a>.';
 $string['cfgwwwrootwarning'] = 'You have defined $CFG->wwwroot incorrectly in your config.php file. It does not match the URL you are using to access this page. Please correct it, or you will experience strange bugs like <a href=\'http://tracker.moodle.org/browse/MDL-11061\'>MDL-11061</a>.';
 $string['clamfailureonupload'] = 'On clam AV failure';
@@ -417,6 +418,7 @@ $string['debugstringids_desc'] = 'This option is designed to help translators. W
 $string['debugvalidators'] = 'Show validator links';
 $string['defaultcity'] = 'Default city';
 $string['defaultcity_help'] = 'A city entered here will be the default city when creating new user accounts.';
+$string['defaultformatnotset'] = 'Error determining default course format. Please check site settings.';
 $string['defaulthomepage'] = 'Default home page for users';
 $string['defaultrequestcategory'] = 'Default category for course requests';
 $string['defaultsettinginfo'] = 'Default: {$a}';
@@ -533,6 +535,9 @@ $string['forceloginforprofileimage'] = 'Force users to login to view user pictur
 $string['forceloginforprofileimage_help'] = 'If enabled, users must login in order to view user profile pictures and the default user picture will be used in all notification emails.';
 $string['forceloginforprofiles'] = 'Force users to login for profiles';
 $string['forcetimezone'] = 'Force default timezone';
+$string['formatuninstallwithcourses'] = 'There are {$a->count} courses using {$a->format}. Their format will be changed to {$a->defaultformat} (default format for this site). Some format-specific data may be lost. Are you sure you want to proceed?';
+$string['formatuninstallconfirm'] = '{$a} will be uninstalled. No courses currently use it. Continue?';
+$string['formatuninstalled'] = 'All data associated with the format plugin \'{$a->plugin}\' has been deleted from the database.  To complete the deletion (and prevent the plugin re-installing itself), you should now delete this directory from your server: {$a->directory}';
 $string['frontpage'] = 'Front page';
 $string['frontpagebackup'] = 'Front page backup';
 $string['frontpagedefaultrole'] = 'Default frontpage role';
@@ -641,6 +646,8 @@ $string['longtimewarning'] = '<b>Please note that this process can take a long t
 $string['maintenancemode'] = 'In maintenance mode';
 $string['maintfileopenerror'] = 'Error opening maintenance files!';
 $string['maintinprogress'] = 'Maintenance is in progress...';
+$string['manageformats'] = 'Manage course formats';
+$string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
 $string['managelang'] = 'Manage';
 $string['managelicenses'] = 'Manage licences';
 $string['manageqbehaviours'] = 'Manage question behaviours';
index bf9e294..543a49f 100644 (file)
@@ -65,6 +65,7 @@ $string['cannotdeletefile'] = 'Cannot delete this file';
 $string['cannotdeleterole'] = 'It cannot be deleted, because {$a}';
 $string['cannotdeleterolewithid'] = 'Could not delete role with ID {$a}';
 $string['cannotdeletethisrole'] = 'You cannot delete this role because it is used by the system, or because it is the last role with administrator capabilities.';
+$string['cannotdisableformat'] = 'You can not disable the default format';
 $string['cannotdownloadcomponents'] = 'Cannot download components';
 $string['cannotdownloadlanguageupdatelist'] = 'Cannot download list of language updates from download.moodle.org';
 $string['cannotdownloadzipfile'] = 'Cannot download ZIP file';
index 960eac4..16430f7 100644 (file)
@@ -297,7 +297,7 @@ $string['coursecompletions'] = 'Course completions';
 $string['coursecreators'] = 'Course creator';
 $string['coursecreatorsdescription'] = 'Course creators can create new courses.';
 $string['coursedisplay'] = 'Course layout';
-$string['coursedisplay_help'] = 'This setting determines whether the whole course is displayed on one page or split over several pages. The setting has no affect on certain course formats, such as SCORM format.';
+$string['coursedisplay_help'] = 'This setting determines whether the whole course is displayed on one page or split over several pages.';
 $string['coursedisplay_single'] = 'Show all sections on one page';
 $string['coursedisplay_multi'] = 'Show one section per page';
 $string['coursedeleted'] = 'Deleted course {$a}';
@@ -1805,6 +1805,7 @@ $string['whattocallzip'] = 'What do you want to call the zip file?';
 $string['whattodo'] = 'What to do';
 $string['windowclosing'] = 'This window should close automatically. If not, please close it now.';
 $string['withchosenfiles'] = 'With chosen files';
+$string['withdisablednote'] = '{$a} (disabled)';
 $string['withoutuserdata'] = 'without user data';
 $string['withselectedusers'] = 'With selected users...';
 $string['withselectedusers_help'] = '* Send message - For sending a message to one or more participants
index 3813b4d..e5b4760 100644 (file)
@@ -266,6 +266,16 @@ function uninstall_plugin($type, $name) {
             // Delete block
             $DB->delete_records('block', array('id'=>$block->id));
         }
+    } else if ($type === 'format') {
+        if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $name) {
+            $courses = $DB->get_records('course', array('format' => $name), 'id');
+            $data = (object)array('id' => null, 'format' => $defaultformat);
+            foreach ($courses as $record) {
+                $data->id = $record->id;
+                update_course($data);
+            }
+        }
+        $DB->delete_records('course_format_options', array('format' => $name));
     }
 
     // perform clean-up task common for all the plugin/subplugin types
@@ -5289,7 +5299,8 @@ class admin_page_manageqtypes extends admin_externalpage {
      */
     public function __construct() {
         global $CFG;
-        parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'), "$CFG->wwwroot/$CFG->admin/qtypes.php");
+        parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
+                new moodle_url('/admin/qtypes.php'));
     }
 
     /**
@@ -5897,6 +5908,147 @@ class admin_setting_managelicenses extends admin_setting {
     }
 }
 
+/**
+ * Course formats manager. Allows to enable/disable formats and jump to settings
+ */
+class admin_setting_manageformats extends admin_setting {
+
+    /**
+     * Calls parent::__construct with specific arguments
+     */
+    public function __construct() {
+        $this->nosave = true;
+        parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_setting() {
+        return true;
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_defaultsetting() {
+        return true;
+    }
+
+    /**
+     * Always returns '' and doesn't write anything
+     *
+     * @param mixed $data string or array, must not be NULL
+     * @return string Always returns ''
+     */
+    public function write_setting($data) {
+        // do not write any setting
+        return '';
+    }
+
+    /**
+     * Search to find if Query is related to format plugin
+     *
+     * @param string $query The string to search for
+     * @return bool true for related false for not
+     */
+    public function is_related($query) {
+        if (parent::is_related($query)) {
+            return true;
+        }
+        $allplugins = plugin_manager::instance()->get_plugins();
+        $formats = $allplugins['format'];
+        foreach ($formats as $format) {
+            if (strpos($format->component, $query) !== false ||
+                    strpos(textlib::strtolower($format->displayname), $query) !== false) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return XHTML to display control
+     *
+     * @param mixed $data Unused
+     * @param string $query
+     * @return string highlight
+     */
+    public function output_html($data, $query='') {
+        global $CFG, $OUTPUT;
+        $return = '';
+        $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
+        $return .= $OUTPUT->box_start('generalbox formatsui');
+
+        $allplugins = plugin_manager::instance()->get_plugins();
+        $formats = $allplugins['format'];
+
+        // display strings
+        $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default', 'delete'));
+        $txt->updown = "$txt->up/$txt->down";
+
+        $table = new html_table();
+        $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->delete, $txt->settings);
+        $table->align = array('left', 'center', 'center', 'center', 'center');
+        $table->width = '90%';
+        $table->attributes['class'] = 'manageformattable generaltable';
+        $table->data  = array();
+
+        $cnt = 0;
+        $defaultformat = get_config('moodlecourse', 'format');
+        $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon'));
+        foreach ($formats as $format) {
+            $url = new moodle_url('/admin/courseformats.php',
+                    array('sesskey' => sesskey(), 'format' => $format->name));
+            $isdefault = '';
+            if ($format->is_enabled()) {
+                $strformatname = html_writer::tag('span', $format->displayname);
+                if ($defaultformat === $format->name) {
+                    $hideshow = $txt->default;
+                } else {
+                    $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
+                            $OUTPUT->pix_icon('i/hide', $txt->disable, 'moodle', array('class' => 'icon')));
+                }
+            } else {
+                $strformatname = html_writer::tag('span', $format->displayname, array('class' => 'dimmed_text'));
+                $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
+                    $OUTPUT->pix_icon('i/show', $txt->enable, 'moodle', array('class' => 'icon')));
+            }
+            $updown = '';
+            if ($cnt) {
+                $updown .= html_writer::link($url->out(false, array('action' => 'up')),
+                    $OUTPUT->pix_icon('t/up', $txt->up, 'moodle')). '&nbsp;';
+            } else {
+                $updown .= $spacer;
+            }
+            if ($cnt < count($formats) - 1) {
+                $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
+                    $OUTPUT->pix_icon('t/down', $txt->down, 'moodle'));
+            } else {
+                $updown .= $spacer;
+            }
+            $cnt++;
+            $settings = '';
+            if ($format->get_settings_url()) {
+                $settings = html_writer::link($format->get_settings_url(), $txt->settings);
+            }
+            $uninstall = '';
+            if ($defaultformat !== $format->name) {
+                $uninstall = html_writer::link($format->get_uninstall_url(), $txt->delete);
+            }
+            $table->data[] =array($strformatname, $hideshow, $updown, $uninstall, $settings);
+        }
+        $return .= html_writer::table($table);
+        $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
+        $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
+        $return .= $OUTPUT->box_end();
+        return highlight($query, $return);
+    }
+}
 
 /**
  * Special class for filter administration.
index aee878f..8f4ae4b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20121102" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20121112" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <KEY NAME="fk_raterid" TYPE="foreign" FIELDS="raterid" REFTABLE="user" REFFIELDS="id" PREVIOUS="fk_definitionid"/>
       </KEYS>
     </TABLE>
-    <TABLE NAME="event_subscriptions" COMMENT="Tracks subscriptions to remote calendars." PREVIOUS="grading_instances">
+    <TABLE NAME="event_subscriptions" COMMENT="Tracks subscriptions to remote calendars." PREVIOUS="grading_instances" NEXT="temp_enroled_template">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="url"/>
         <FIELD NAME="url" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="courseid"/>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
       </KEYS>
     </TABLE>
+    <TABLE NAME="temp_enroled_template" COMMENT="Temporary storage for course enrolments" PREVIOUS="event_subscriptions" NEXT="temp_log_template">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="courseid"/>
+        <FIELD NAME="courseid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="userid" NEXT="roleid"/>
+        <FIELD NAME="roleid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" PREVIOUS="courseid"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" NEXT="courseid"/>
+        <INDEX NAME="courseid" UNIQUE="false" FIELDS="courseid" PREVIOUS="userid" NEXT="roleid"/>
+        <INDEX NAME="roleid" UNIQUE="false" FIELDS="roleid" PREVIOUS="courseid"/>
+      </INDEXES>
+    </TABLE>
+    <TABLE NAME="temp_log_template" COMMENT="Temporary storage for daily logs" PREVIOUS="temp_enroled_template">
+      <FIELDS>
+        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true" NEXT="userid"/>
+        <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="id" NEXT="course"/>
+        <FIELD NAME="course" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" PREVIOUS="userid" NEXT="action"/>
+        <FIELD NAME="action" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" PREVIOUS="course"/>
+      </FIELDS>
+      <KEYS>
+        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
+      </KEYS>
+      <INDEXES>
+        <INDEX NAME="action" UNIQUE="false" FIELDS="action" NEXT="course"/>
+        <INDEX NAME="course" UNIQUE="false" FIELDS="course" PREVIOUS="action" NEXT="user"/>
+        <INDEX NAME="user" UNIQUE="false" FIELDS="userid" PREVIOUS="course" NEXT="usercourseaction"/>
+        <INDEX NAME="usercourseaction" UNIQUE="false" FIELDS="userid, course, action" PREVIOUS="user"/>
+      </INDEXES>
+    </TABLE>
   </TABLES>
-</XMLDB>
+</XMLDB>
\ No newline at end of file
index 6e904b2..fc2df44 100644 (file)
@@ -1414,5 +1414,57 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012110700.01);
     }
 
+    if ($oldversion < 2012111200.00) {
+
+        // Define table temp_enroled_template to be created
+        $table = new xmldb_table('temp_enroled_template');
+
+        // Adding fields to table temp_enroled_template
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('roleid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table temp_enroled_template
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Adding indexes to table temp_enroled_template
+        $table->add_index('userid', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+        $table->add_index('courseid', XMLDB_INDEX_NOTUNIQUE, array('courseid'));
+        $table->add_index('roleid', XMLDB_INDEX_NOTUNIQUE, array('roleid'));
+
+        // Conditionally launch create table for temp_enroled_template
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Define table temp_log_template to be created
+        $table = new xmldb_table('temp_log_template');
+
+        // Adding fields to table temp_log_template
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_field('action', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null);
+
+        // Adding keys to table temp_log_template
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+
+        // Adding indexes to table temp_log_template
+        $table->add_index('action', XMLDB_INDEX_NOTUNIQUE, array('action'));
+        $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, array('course'));
+        $table->add_index('user', XMLDB_INDEX_NOTUNIQUE, array('userid'));
+        $table->add_index('usercourseaction', XMLDB_INDEX_NOTUNIQUE, array('userid', 'course', 'action'));
+
+        // Conditionally launch create table for temp_log_template
+        if (!$dbman->table_exists($table)) {
+            $dbman->create_table($table);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012111200.00);
+    }
+
+
     return true;
 }
index e2b74da..57ce3d9 100644 (file)
@@ -56,6 +56,7 @@ $relroot = preg_replace('|^http.?://[^/]+|', '', $CFG->wwwroot);
 
 $htmllang = get_html_lang();
 header('Content-Type: text/html; charset=utf-8');
+header('X-UA-Compatible: IE=edge');
 ?>
 <!DOCTYPE html>
 <html <?php echo $htmllang ?>
index 7ca35ce..071c481 100644 (file)
@@ -88,6 +88,8 @@ if ($mimetype === 'application/x-javascript' && $allowcache) {
     }
 
     $file = $cachefile;
+} else if ($mimetype === 'text/html') {
+    header('X-UA-Compatible: IE=edge');
 }
 
 // Serve file.
index 39bf1ac..d26d8eb 100644 (file)
@@ -37,6 +37,7 @@ $plugin = $editor->get_plugin('moodleemoticon');
 
 $htmllang = get_html_lang();
 header('Content-Type: text/html; charset=utf-8');
+header('X-UA-Compatible: IE=edge');
 ?>
 <!DOCTYPE html>
 <html <?php echo $htmllang ?>
index afb60da..e3acf2d 100644 (file)
@@ -39,7 +39,7 @@ class GoogleSpell extends SpellChecker {
                $matches = $this->_getMatches($lang, $word);\r
 \r
                if (count($matches) > 0)\r
-                       $sug = explode("\t", utf8_encode($this->_unhtmlentities($matches[0][4])));\r
+                       $sug = explode("\t", $this->_unhtmlentities($matches[0][4]));\r
 \r
                // Remove empty\r
                foreach ($sug as $item) {\r
index 47fdd0d..21bc763 100644 (file)
@@ -19,8 +19,9 @@ Upgrade procedure:
 
 Changes:
 
-1/ zIndex 300000 and 200000 changed to 3000 and 2000 - this prevents collision with YUI,
+ * zIndex 300000 and 200000 changed to 3000 and 2000 - this prevents collision with YUI,
    see MDL-35771
+ * MDL-25736 - French spellchecker fixes.
 
 TODO:
  * create some new automated script that sends other languages from upstream into AMOS
index bc6f902..7b8cca2 100644 (file)
@@ -253,6 +253,7 @@ function install_print_help_page($help) {
     global $CFG, $OUTPUT; //TODO: MUST NOT USE $OUTPUT HERE!!!
 
     @header('Content-Type: text/html; charset=UTF-8');
+    @header('X-UA-Compatible: IE=edge');
     @header('Cache-Control: no-store, no-cache, must-revalidate');
     @header('Cache-Control: post-check=0, pre-check=0', false);
     @header('Pragma: no-cache');
@@ -299,6 +300,7 @@ function install_print_header($config, $stagename, $heading, $stagetext) {
     global $CFG;
 
     @header('Content-Type: text/html; charset=UTF-8');
+    @header('X-UA-Compatible: IE=edge');
     @header('Cache-Control: no-store, no-cache, must-revalidate');
     @header('Cache-Control: post-check=0, pre-check=0', false);
     @header('Pragma: no-cache');
index d007432..3ab00ea 100644 (file)
@@ -414,32 +414,98 @@ function message_get_my_providers() {
 function message_get_providers_for_user($userid) {
     global $DB, $CFG;
 
-    $systemcontext = context_system::instance();
-
     $providers = get_message_providers();
 
-    // Remove all the providers we aren't allowed to see now
-    foreach ($providers as $providerid => $provider) {
-        if (!empty($provider->capability)) {
-            if (!has_capability($provider->capability, $systemcontext, $userid)) {
-                unset($providers[$providerid]);   // Not allowed to see this
-                continue;
+    // Ensure user is not allowed to configure instantmessage if it is globally disabled.
+    if (!$CFG->messaging) {
+        foreach ($providers as $providerid => $provider) {
+            if ($provider->name == 'instantmessage') {
+                unset($providers[$providerid]);
+                break;
             }
         }
+    }
 
-        // Ensure user is not allowed to configure instantmessage if it is globally disabled.
-        if (!$CFG->messaging && $provider->name == 'instantmessage') {
+    // If the component is an enrolment plugin, check it is enabled
+    foreach ($providers as $providerid => $provider) {
+        list($type, $name) = normalize_component($provider->component);
+        if ($type == 'enrol' && !enrol_is_enabled($name)) {
             unset($providers[$providerid]);
+        }
+    }
+
+    // Now we need to check capabilities. We need to eliminate the providers
+    // where the user does not have the corresponding capability anywhere.
+    // Here we deal with the common simple case of the user having the
+    // capability in the system context. That handles $CFG->defaultuserroleid.
+    // For the remaining providers/capabilities, we need to do a more complex
+    // query involving all overrides everywhere.
+    $unsureproviders = array();
+    $unsurecapabilities = array();
+    $systemcontext = context_system::instance();
+    foreach ($providers as $providerid => $provider) {
+        if (empty($provider->capability) || has_capability($provider->capability, $systemcontext, $userid)) {
+            // The provider is relevant to this user.
             continue;
         }
 
-        // If the component is an enrolment plugin, check it is enabled
-        list($type, $name) = normalize_component($provider->component);
-        if ($type == 'enrol') {
-            if (!enrol_is_enabled($name)) {
-                unset($providers[$providerid]);
-                continue;
-            }
+        $unsureproviders[$providerid] = $provider;
+        $unsurecapabilities[$provider->capability] = 1;
+        unset($providers[$providerid]);
+    }
+
+    if (empty($unsureproviders)) {
+        // More complex checks are not required.
+        return $providers;
+    }
+
+    // Now check the unsure capabilities.
+    list($capcondition, $params) = $DB->get_in_or_equal(
+            array_keys($unsurecapabilities), SQL_PARAMS_NAMED);
+    $params['userid'] = $userid;
+
+    $sql = "SELECT DISTINCT rc.capability, 1
+
+              FROM {role_assignments} ra
+              JOIN {context} actx ON actx.id = ra.contextid
+              JOIN {role_capabilities} rc ON rc.roleid = ra.roleid
+              JOIN {context} cctx ON cctx.id = rc.contextid
+
+             WHERE ra.userid = :userid
+               AND rc.capability $capcondition
+               AND rc.permission > 0
+               AND (".$DB->sql_concat('actx.path', "'/'")." LIKE ".$DB->sql_concat('cctx.path', "'/%'").
+               " OR ".$DB->sql_concat('cctx.path', "'/'")." LIKE ".$DB->sql_concat('actx.path', "'/%'").")";
+
+    if (!empty($CFG->defaultfrontpageroleid)) {
+        $frontpagecontext = context_course::instance(SITEID);
+
+        list($capcondition2, $params2) = $DB->get_in_or_equal(
+                array_keys($unsurecapabilities), SQL_PARAMS_NAMED);
+        $params = array_merge($params, $params2);
+        $params['frontpageroleid'] = $CFG->defaultfrontpageroleid;
+        $params['frontpagepathpattern'] = $frontpagecontext->path . '/';
+
+        $sql .= "
+             UNION
+
+            SELECT DISTINCT rc.capability, 1
+
+              FROM {role_capabilities} rc
+              JOIN {context} cctx ON cctx.id = rc.contextid
+
+             WHERE rc.roleid = :frontpageroleid
+               AND rc.capability $capcondition2
+               AND rc.permission > 0
+               AND ".$DB->sql_concat('cctx.path', "'/'")." LIKE :frontpagepathpattern";
+    }
+
+    $relevantcapabilities = $DB->get_records_sql_menu($sql, $params);
+
+    // Add back any providers based on the detailed capability check.
+    foreach ($unsureproviders as $providerid => $provider) {
+        if (array_key_exists($provider->capability, $relevantcapabilities)) {
+            $providers[$providerid] = $provider;
         }
     }
 
index 4b54f37..77e9883 100644 (file)
@@ -4572,7 +4572,8 @@ function delete_course($courseorid, $showfeedback = true) {
     // which should know about this updated property, as this event is meant to pass the full course record
     $course->timemodified = time();
 
-    $DB->delete_records("course", array("id"=>$courseid));
+    $DB->delete_records("course", array("id" => $courseid));
+    $DB->delete_records("course_format_options", array("courseid" => $courseid));
 
     //trigger events
     $course->context = $context; // you can not fetch context in the event because it was already deleted
@@ -8543,17 +8544,25 @@ function check_php_version($version='5.2.4') {
           if (strpos($agent, 'Opera') !== false) {     // Reject Opera
               return false;
           }
-          // in case of IE we have to deal with BC of the version parameter
+          // In case of IE we have to deal with BC of the version parameter.
           if (is_null($version)) {
-              $version = 5.5; // anything older is not considered a browser at all!
+              $version = 5.5; // Anything older is not considered a browser at all!
           }
-
-          //see: http://www.useragentstring.com/pages/Internet%20Explorer/
+          // IE uses simple versions, let's cast it to float to simplify the logic here.
+          $version = round($version, 1);
+          // See: http://www.useragentstring.com/pages/Internet%20Explorer/
           if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
-              if (version_compare($match[1], $version) >= 0) {
-                  return true;
-              }
+              $browser = $match[1];
+          } else {
+              return false;
+          }
+          // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
+          // the Trident should always describe the capabilities of IE in any emulation mode.
+          if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $agent, $match)) {
+              $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
           }
+          $browser = round($browser, 1);
+          return ($browser >= $version);
           break;
 
 
@@ -8831,14 +8840,11 @@ function get_browser_version_classes() {
 
     if (check_browser_version("MSIE", "0")) {
         $classes[] = 'ie';
-        if (check_browser_version("MSIE", 9)) {
-            $classes[] = 'ie9';
-        } else if (check_browser_version("MSIE", 8)) {
-            $classes[] = 'ie8';
-        } elseif (check_browser_version("MSIE", 7)) {
-            $classes[] = 'ie7';
-        } elseif (check_browser_version("MSIE", 6)) {
-            $classes[] = 'ie6';
+        for($i=12; $i>=6; $i--) {
+            if (check_browser_version("MSIE", $i)) {
+                $classes[] = 'ie'.$i;
+                break;
+            }
         }
 
     } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
index e5d1410..e8357c9 100644 (file)
@@ -1120,7 +1120,7 @@ class theme_config {
                 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'])) {
+                } else if (check_browser_version('MSIE', 0) and !check_browser_version('MSIE', 9)) {
                     // IE < 9 doesn't support SVG. Say no.
                     $this->usesvg = false;
                 } else if (preg_match('#Android +[0-2]\.#', $_SERVER['HTTP_USER_AGENT'])) {
index e8cb001..a449bc6 100644 (file)
@@ -913,7 +913,10 @@ class available_update_checker {
                 $this->recentfetch = $config->recentfetch;
                 $this->recentresponse = $this->decode_response($config->recentresponse);
             } catch (available_update_checker_exception $e) {
-                debugging('Invalid info about available updates detected and will be ignored: '.$e->getMessage(), DEBUG_ALL);
+                // The server response is not valid. Behave as if no data were fetched yet.
+                // This may happen when the most recent update info (cached locally) has been
+                // fetched with the previous branch of Moodle (like during an upgrade from 2.x
+                // to 2.y) or when the API of the response has changed.
                 $this->recentresponse = array();
             }
 
@@ -2924,9 +2927,11 @@ class plugininfo_qtype extends plugininfo_base {
         $section = $this->get_settings_section_name();
 
         $settings = null;
-        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
+        $systemcontext = context_system::instance();
+        if (($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) &&
+                file_exists($this->full_path('settings.php'))) {
             $settings = new admin_settingpage($section, $this->displayname,
-                    'moodle/site:config', $this->is_enabled() === false);
+                    'moodle/question:config', $this->is_enabled() === false);
             include($this->full_path('settings.php')); // this may also set $settings to null
         }
         if ($settings) {
@@ -3362,3 +3367,61 @@ class plugininfo_webservice extends plugininfo_base {
                 array('sesskey' => sesskey(), 'action' => 'uninstall', 'webservice' => $this->name));
     }
 }
+
+/**
+ * Class for course formats
+ */
+class plugininfo_format extends plugininfo_base {
+
+    /**
+     * Gathers and returns the information about all plugins of the given type
+     *
+     * @param string $type the name of the plugintype, eg. mod, auth or workshopform
+     * @param string $typerootdir full path to the location of the plugin dir
+     * @param string $typeclass the name of the actually called class
+     * @return array of plugintype classes, indexed by the plugin name
+     */
+    public static function get_plugins($type, $typerootdir, $typeclass) {
+        global $CFG;
+        $formats = parent::get_plugins($type, $typerootdir, $typeclass);
+        require_once($CFG->dirroot.'/course/lib.php');
+        $order = get_sorted_course_formats();
+        $sortedformats = array();
+        foreach ($order as $formatname) {
+            $sortedformats[$formatname] = $formats[$formatname];
+        }
+        return $sortedformats;
+    }
+
+    public function get_settings_section_name() {
+        return 'formatsetting' . $this->name;
+    }
+
+    public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
+        $ADMIN = $adminroot; // also may be used in settings.php
+        $section = $this->get_settings_section_name();
+
+        $settings = null;
+        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
+            $settings = new admin_settingpage($section, $this->displayname,
+                    'moodle/site:config', $this->is_enabled() === false);
+            include($this->full_path('settings.php')); // this may also set $settings to null
+        }
+        if ($settings) {
+            $ADMIN->add($parentnodename, $settings);
+        }
+    }
+
+    public function is_enabled() {
+        return !get_config($this->component, 'disabled');
+    }
+
+    public function get_uninstall_url() {
+        if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') {
+            return new moodle_url('/admin/courseformats.php',
+                    array('sesskey' => sesskey(), 'action' => 'uninstall', 'format' => $this->name));
+        }
+        return parent::get_uninstall_url();
+    }
+}
index 1e7c439..90c5c73 100644 (file)
@@ -182,6 +182,7 @@ if (defined('WEB_CRON_EMULATED_CLI')) {
 if (file_exists("$CFG->dataroot/climaintenance.html")) {
     if (!CLI_SCRIPT) {
         header('Content-type: text/html; charset=utf-8');
+        header('X-UA-Compatible: IE=edge');
         /// Headers to make it not cacheable and json
         header('Cache-Control: no-store, no-cache, must-revalidate');
         header('Cache-Control: post-check=0, pre-check=0', false);
index ace9c43..33894ad 100644 (file)
@@ -1540,6 +1540,7 @@ width: 80%; -moz-border-radius: 20px; padding: 15px">
 
         // better disable any caching
         @header('Content-Type: text/html; charset=utf-8');
+        @header('X-UA-Compatible: IE=edge');
         @header('Cache-Control: no-store, no-cache, must-revalidate');
         @header('Cache-Control: post-check=0, pre-check=0', false);
         @header('Pragma: no-cache');
index 6b2220f..3d201fd 100644 (file)
@@ -71,31 +71,56 @@ define('STATS_MODE_GENERAL',1);
 define('STATS_MODE_DETAILED',2);
 define('STATS_MODE_RANKED',3); // admins only - ranks courses
 
+// Output string when nodebug is on
+define('STATS_PLACEHOLDER_OUTPUT', '.');
+
 /**
  * Print daily cron progress
  * @param string $ident
  */
-function stats_daily_progress($ident) {
+function stats_progress($ident) {
     static $start = 0;
     static $init  = 0;
 
     if ($ident == 'init') {
-        $init = $start = time();
+        $init = $start = microtime(true);
         return;
     }
 
-    $elapsed = time() - $start;
-    $start   = time();
+    $elapsed = round(microtime(true) - $start);
+    $start   = microtime(true);
 
     if (debugging('', DEBUG_ALL)) {
         mtrace("$ident:$elapsed ", '');
     } else {
-        mtrace('.', '');
+        mtrace(STATS_PLACEHOLDER_OUTPUT, '');
+    }
+}
+
+/**
+ * Execute individual daily statistics queries
+ *
+ * @param string $sql The query to run
+ * @return boolean success
+ */
+function stats_run_query($sql, $parameters = array()) {
+    global $DB;
+
+    try {
+        $DB->execute($sql, $parameters);
+    } catch (dml_exception $e) {
+
+       if (debugging('', DEBUG_ALL)) {
+           mtrace($e->getMessage());
+       }
+       return false;
     }
+    return true;
 }
 
 /**
  * Execute daily statistics gathering
+ *
  * @param int $maxdays maximum number of days to be processed
  * @return boolean success
  */
@@ -117,7 +142,7 @@ function stats_cron_daily($maxdays=1) {
 
     // Note: This will work fine for sites running cron each 4 hours or less (hopefully, 99.99% of sites). MDL-16709
     // check to make sure we're due to run, at least 20 hours after last run
-    if (isset($CFG->statslastexecution) and ((time() - 20*60*60) < $CFG->statslastexecution)) {
+    if (isset($CFG->statslastexecution) && ((time() - 20*60*60) < $CFG->statslastexecution)) {
         mtrace("...preventing stats to run, last execution was less than 20 hours ago.");
         return false;
     // also check that we are a max of 4 hours after scheduled time, stats won't run after that
@@ -156,14 +181,29 @@ function stats_cron_daily($maxdays=1) {
 
     mtrace("Running daily statistics gathering, starting at $timestart:");
 
-    $days = 0;
-    $failed = false; // failed stats flag
+    $days  = 0;
+    $total = 0;
+    $failed  = false; // failed stats flag
+    $timeout = false;
+
+    if (!stats_temp_table_create()) {
+        $days = 1;
+        $failed = true;
+    }
+    mtrace('Temporary tables created');
+
+    if(!stats_temp_table_setup()) {
+        $days = 1;
+        $failed = true;
+    }
+    mtrace('Enrolments calculated');
+
+    $totalactiveusers = $DB->count_records('user', array('deleted' => '0'));
 
-    while ($now > $nextmidnight) {
+    while (!$failed && ($now > $nextmidnight)) {
         if ($days >= $maxdays) {
-            mtrace("...stopping early, reached maximum number of $maxdays days - will continue next time.");
-            set_cron_lock('statsrunning', null);
-            return false;
+            $timeout = true;
+            break;
         }
 
         $days++;
@@ -176,53 +216,63 @@ function stats_cron_daily($maxdays=1) {
 
         $daystart = time();
 
-        $timesql  = "l.time >= $timestart  AND l.time  < $nextmidnight";
-        $timesql1 = "l1.time >= $timestart AND l1.time < $nextmidnight";
-        $timesql2 = "l2.time >= $timestart AND l2.time < $nextmidnight";
-
-        stats_daily_progress('init');
+        stats_progress('init');
 
+        if (!stats_temp_table_fill($timestart, $nextmidnight)) {
+            $failed = true;
+            break;
+        }
 
-    /// find out if any logs available for this day
-        $sql = "SELECT 'x'
-                  FROM {log} l
-                 WHERE $timesql";
+        // Find out if any logs available for this day
+        $sql = "SELECT 'x' FROM {temp_log1} l";
         $logspresent = $DB->get_records_sql($sql, null, 0, 1);
 
-    /// process login info first
-        $sql = "INSERT INTO {stats_user_daily} (stattype, timeend, courseid, userid, statsreads)
+        if ($logspresent) {
+            // Insert blank record to force Query 10 to generate additional row when no logs for
+            // the site with userid 0 exist.  Added for backwards compatibility.
+            $DB->insert_record('temp_log1', array('userid' => 0, 'course' => SITEID, 'action' => ''));
+        }
 
-                SELECT 'logins', timeend, courseid, userid, count(statsreads)
-                 FROM (
-                          SELECT $nextmidnight AS timeend, ".SITEID." AS courseid, l.userid, l.id AS statsreads
-                            FROM {log} l
-                           WHERE action = 'login' AND $timesql
-                       ) inline_view
+        // Calculate the number of active users today
+        $sql = 'SELECT COUNT(DISTINCT u.id)
+                  FROM {user} u
+                  JOIN {temp_log1} l ON l.userid = u.id
+                 WHERE u.deleted = 0';
+        $dailyactiveusers = $DB->count_records_sql($sql);
+
+        stats_progress('0');
+
+        // Process login info first
+        // Note: PostgreSQL doesn't like aliases in HAVING clauses
+        $sql = "INSERT INTO {temp_stats_user_daily}
+                            (stattype, timeend, courseid, userid, statsreads)
+
+                SELECT 'logins', $nextmidnight AS timeend, ".SITEID." AS courseid,
+                        userid, COUNT(id) AS statsreads
+                  FROM {temp_log1} l
+                 WHERE action = 'login'
               GROUP BY timeend, courseid, userid
-                HAVING count(statsreads) > 0";
+                HAVING COUNT(id) > 0";
 
-        if ($logspresent and !$DB->execute($sql)) {
+        if ($logspresent && !stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('1');
 
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        stats_progress('1');
 
-                SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID." as courseid, 0,
-                       COALESCE((SELECT SUM(statsreads)
-                                       FROM {stats_user_daily} s1
-                                      WHERE s1.stattype = 'logins' AND timeend = $nextmidnight), 0) AS stat1,
-                       (SELECT COUNT('x')
-                          FROM {stats_user_daily} s2
-                         WHERE s2.stattype = 'logins' AND timeend = $nextmidnight) AS stat2" .
-                $DB->sql_null_from_clause();
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+
+                SELECT 'logins' AS stattype, $nextmidnight AS timeend, ".SITEID." AS courseid, 0,
+                       COALESCE(SUM(statsreads), 0) as stat1, COUNT('x') as stat2
+                  FROM {temp_stats_user_daily}
+                 WHERE stattype = 'logins' AND timeend = $nextmidnight";
 
-        if ($logspresent and !$DB->execute($sql)) {
+        if ($logspresent && !stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('2');
+        stats_progress('2');
 
 
         // Enrolments and active enrolled users
@@ -235,353 +285,356 @@ function stats_cron_daily($maxdays=1) {
         //   in that case, we'll count non-deleted users.
         //
 
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'enrolments', timeend, courseid, roleid, COUNT(DISTINCT userid), 0
-                  FROM (
-                           SELECT $nextmidnight AS timeend, e.courseid, ra.roleid, ue.userid
-                             FROM {role_assignments} ra
-                             JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
-                             JOIN {enrol} e ON e.courseid = c.instanceid
-                             JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
-                        ) inline_view
-              GROUP BY timeend, courseid, roleid";
+                SELECT 'enrolments' as stattype, $nextmidnight as timeend, courseid, roleid,
+                        COUNT(DISTINCT userid) as stat1, 0 as stat2
+                  FROM {temp_enroled}
+              GROUP BY courseid, roleid";
 
-        if (!$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
+        if (!stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('3');
+        stats_progress('3');
 
+        // Set stat2 to the number distinct users with role assignments in the course that were active
         // using table alias in UPDATE does not work in pg < 8.2
-        $sql = "UPDATE {stats_daily}
-                   SET stat2 = (SELECT COUNT(DISTINCT ra.userid)
-                                  FROM {role_assignments} ra
-                                  JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
-                                  JOIN {enrol} e ON e.courseid = c.instanceid
-                                  JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
-                                  WHERE ra.roleid = {stats_daily}.roleid AND
-                                       e.courseid = {stats_daily}.courseid AND
-                                       EXISTS (SELECT 'x'
-                                                 FROM {log} l
-                                                WHERE l.course = {stats_daily}.courseid AND
-                                                      l.userid = ra.userid AND $timesql))
-                 WHERE {stats_daily}.stattype = 'enrolments' AND
-                       {stats_daily}.timeend = $nextmidnight AND
-                       {stats_daily}.courseid IN
-                          (SELECT DISTINCT l.course
-                             FROM {log} l
-                            WHERE $timesql)";
-
-        if (!$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
+        $sql = "UPDATE {temp_stats_daily}
+                   SET stat2 = (
+
+                    SELECT COUNT(DISTINCT userid)
+                      FROM {temp_enroled} te
+                     WHERE roleid = {temp_stats_daily}.roleid
+                       AND courseid = {temp_stats_daily}.courseid
+                       AND EXISTS (
+
+                        SELECT 'x'
+                          FROM {temp_log1} l
+                         WHERE l.course = {temp_stats_daily}.courseid
+                           AND l.userid = te.userid
+                                  )
+                               )
+                 WHERE {temp_stats_daily}.stattype = 'enrolments'
+                   AND {temp_stats_daily}.timeend = $nextmidnight
+                   AND {temp_stats_daily}.courseid IN (
+
+                    SELECT DISTINCT course FROM {temp_log2})";
+
+        if ($logspresent && !stats_run_query($sql, array('courselevel'=>CONTEXT_COURSE))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('4');
+        stats_progress('4');
 
-    /// now get course total enrolments (roleid==0) - except frontpage
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        // Now get course total enrolments (roleid==0) - except frontpage
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'enrolments', timeend, id, nroleid, COUNT(DISTINCT userid), 0
-                  FROM (
-                           SELECT $nextmidnight AS timeend, e.courseid AS id, 0 AS nroleid, ue.userid
-                             FROM {enrol} e
-                             JOIN {user_enrolments} ue ON ue.enrolid = e.id
-                       ) inline_view
-              GROUP BY timeend, id, nroleid
+                SELECT 'enrolments', $nextmidnight AS timeend, te.courseid AS courseid, 0 AS roleid,
+                       COUNT(DISTINCT userid) AS stat1, 0 AS stat2
+                  FROM {temp_enroled} te
+              GROUP BY courseid
                 HAVING COUNT(DISTINCT userid) > 0";
 
-        if ($logspresent and !$DB->execute($sql)) {
+        if ($logspresent && !stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('5');
-
-        $sql = "UPDATE {stats_daily}
-                   SET stat2 = (SELECT COUNT(DISTINCT ue.userid)
-                                  FROM {enrol} e
-                                  JOIN {user_enrolments} ue ON ue.enrolid = e.id
-                                 WHERE e.courseid = {stats_daily}.courseid AND
-                                       EXISTS (SELECT 'x'
-                                                 FROM {log} l
-                                                WHERE l.course = {stats_daily}.courseid AND
-                                                      l.userid = ue.userid AND $timesql))
-                 WHERE {stats_daily}.stattype = 'enrolments' AND
-                       {stats_daily}.timeend = $nextmidnight AND
-                       {stats_daily}.roleid = 0 AND
-                       {stats_daily}.courseid IN
-                          (SELECT l.course
-                             FROM {log} l
-                            WHERE $timesql AND l.course <> ".SITEID.")";
-
-        if ($logspresent and !$DB->execute($sql, array())) {
+        stats_progress('5');
+
+        // Set stat 2 to the number of enrolled users who were active in the course
+        $sql = "UPDATE {temp_stats_daily}
+                   SET stat2 = (
+
+                    SELECT COUNT(DISTINCT te.userid)
+                      FROM {temp_enroled} te
+                     WHERE te.courseid = {temp_stats_daily}.courseid
+                       AND EXISTS (
+
+                        SELECT 'x'
+                          FROM {temp_log1} l
+                         WHERE l.course = {temp_stats_daily}.courseid
+                           AND l.userid = te.userid
+                                  )
+                               )
+
+                 WHERE {temp_stats_daily}.stattype = 'enrolments'
+                   AND {temp_stats_daily}.timeend = $nextmidnight
+                   AND {temp_stats_daily}.roleid = 0
+                   AND {temp_stats_daily}.courseid IN (
+
+                    SELECT l.course
+                      FROM {temp_log2} l
+                     WHERE l.course <> ".SITEID.")";
+
+        if ($logspresent && !stats_run_query($sql, array())) {
             $failed = true;
             break;
         }
-        stats_daily_progress('6');
+        stats_progress('6');
 
-    /// frontapge(==site) enrolments total
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        // Frontpage(==site) enrolments total
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'enrolments', $nextmidnight, ".SITEID.", 0,
-                       (SELECT COUNT('x')
-                          FROM {user} u
-                         WHERE u.deleted = 0) AS stat1,
-                       (SELECT COUNT(DISTINCT u.id)
-                          FROM {user} u
-                               JOIN {log} l ON l.userid = u.id
-                         WHERE u.deleted = 0 AND $timesql) AS stat2" .
+                SELECT 'enrolments', $nextmidnight, ".SITEID.", 0, $totalactiveusers AS stat1,
+                       $dailyactiveusers AS stat2" .
                 $DB->sql_null_from_clause();
 
-        if ($logspresent and !$DB->execute($sql)) {
+        if ($logspresent && !stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('7');
+        stats_progress('7');
 
-    /// Default frontpage role enrolments are all site users (not deleted)
+        // Default frontpage role enrolments are all site users (not deleted)
         if ($defaultfproleid) {
             // first remove default frontpage role counts if created by previous query
             $sql = "DELETE
-                      FROM {stats_daily}
-                     WHERE stattype = 'enrolments' AND courseid = ".SITEID." AND
-                           roleid = $defaultfproleid AND timeend = $nextmidnight";
-            if ($logspresent and !$DB->execute($sql)) {
+                      FROM {temp_stats_daily}
+                     WHERE stattype = 'enrolments'
+                       AND courseid = ".SITEID."
+                       AND roleid = $defaultfproleid
+                       AND timeend = $nextmidnight";
+
+            if ($logspresent && !stats_run_query($sql)) {
                 $failed = true;
                 break;
             }
-            stats_daily_progress('8');
+            stats_progress('8');
 
-            $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+            $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
                     SELECT 'enrolments', $nextmidnight, ".SITEID.", $defaultfproleid,
-                           (SELECT COUNT('x')
-                              FROM {user} u
-                             WHERE u.deleted = 0) AS stat1,
-                           (SELECT COUNT(DISTINCT u.id)
-                              FROM {user} u
-                                   JOIN {log} l ON l.userid = u.id
-                             WHERE u.deleted = 0 AND $timesql) AS stat2" .
+                           $totalactiveusers AS stat1, $dailyactiveusers AS stat2" .
                     $DB->sql_null_from_clause();;
 
-            if ($logspresent and !$DB->execute($sql)) {
+            if ($logspresent && !stats_run_query($sql)) {
                 $failed = true;
                 break;
             }
-            stats_daily_progress('9');
+            stats_progress('9');
 
         } else {
-            stats_daily_progress('x');
-            stats_daily_progress('x');
+            stats_progress('x');
+            stats_progress('x');
         }
 
 
-
-    /// individual user stats (including not-logged-in) in each course, this is slow - reuse this data if possible
+        /// individual user stats (including not-logged-in) in each course, this is slow - reuse this data if possible
         list($viewactionssql, $params1) = $DB->get_in_or_equal($viewactions, SQL_PARAMS_NAMED, 'view');
         list($postactionssql, $params2) = $DB->get_in_or_equal($postactions, SQL_PARAMS_NAMED, 'post');
-        $sql = "INSERT INTO {stats_user_daily} (stattype, timeend, courseid, userid, statsreads, statswrites)
+        $sql = "INSERT INTO {temp_stats_user_daily} (stattype, timeend, courseid, userid, statsreads, statswrites)
 
-                SELECT 'activity' AS stattype, $nextmidnight AS timeend, d.courseid, d.userid,
-                       (SELECT COUNT('x')
-                          FROM {log} l
-                         WHERE l.userid = d.userid AND
-                               l.course = d.courseid AND $timesql AND
-                               l.action $viewactionssql) AS statsreads,
-                       (SELECT COUNT('x')
-                          FROM {log} l
-                         WHERE l.userid = d.userid AND
-                               l.course = d.courseid AND $timesql AND
-                               l.action $postactionssql) AS statswrites
-                  FROM (SELECT DISTINCT u.id AS userid, l.course AS courseid
-                          FROM {user} u, {log} l
-                         WHERE u.id = l.userid AND $timesql
-                       UNION
-                        SELECT 0 AS userid, ".SITEID." AS courseid" . $DB->sql_null_from_clause() . ") d";
-                        // can not use group by here because pg can not handle it :-(
-
-        if ($logspresent and !$DB->execute($sql, array_merge($params1, $params2))) {
+                SELECT 'activity' AS stattype, $nextmidnight AS timeend, course AS courseid, userid,
+                       SUM(CASE WHEN action $viewactionssql THEN 1 ELSE 0 END) AS statsreads,
+                       SUM(CASE WHEN action $postactionssql THEN 1 ELSE 0 END) AS statswrites
+                  FROM {temp_log1} l
+              GROUP BY userid, courseid";
+
+        if ($logspresent && !stats_run_query($sql, array_merge($params1, $params2))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('10');
+        stats_progress('10');
 
 
-    /// how many view/post actions in each course total
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        /// How many view/post actions in each course total
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
                 SELECT 'activity' AS stattype, $nextmidnight AS timeend, c.id AS courseid, 0,
-                       (SELECT COUNT('x')
-                          FROM {log} l1
-                         WHERE l1.course = c.id AND l1.action $viewactionssql AND
-                               $timesql1) AS stat1,
-                       (SELECT COUNT('x')
-                          FROM {log} l2
-                         WHERE l2.course = c.id AND l2.action $postactionssql AND
-                               $timesql2) AS stat2
-                  FROM {course} c
-                 WHERE EXISTS (SELECT 'x'
-                                 FROM {log} l
-                                WHERE l.course = c.id and $timesql)";
-
-        if ($logspresent and !$DB->execute($sql, array_merge($params1, $params2))) {
+                       SUM(CASE WHEN l.action $viewactionssql THEN 1 ELSE 0 END) AS stat1,
+                       SUM(CASE WHEN l.action $postactionssql THEN 1 ELSE 0 END) AS stat2
+                  FROM {course} c, {temp_log1} l
+                 WHERE l.course = c.id
+              GROUP BY courseid";
+
+        if ($logspresent && !stats_run_query($sql, array_merge($params1, $params2))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('11');
+        stats_progress('11');
 
 
-    /// how many view actions for each course+role - excluding guests and frontpage
+        /// how many view actions for each course+role - excluding guests and frontpage
 
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'activity', timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
+                SELECT 'activity', $nextmidnight AS timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
                   FROM (
-                           SELECT $nextmidnight AS timeend, pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
-                             FROM {stats_user_daily} sud,
-                                      (SELECT DISTINCT ra.userid, ra.roleid, e.courseid
-                                         FROM {role_assignments} ra
-                                         JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
-                                         JOIN {enrol} e ON e.courseid = c.instanceid
-                                         JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)
-                                        WHERE ra.roleid <> $guestrole AND
-                                              ra.userid <> $guest
-                                      ) pl
-                            WHERE sud.userid = pl.userid AND
-                                  sud.courseid = pl.courseid AND
-                                  sud.timeend = $nextmidnight AND
-                                  sud.stattype='activity'
+
+                    SELECT pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
+                      FROM {temp_stats_user_daily} sud, (
+
+                        SELECT DISTINCT te.userid, te.roleid, te.courseid
+                          FROM {temp_enroled} te
+                         WHERE te.roleid <> $guestrole
+                           AND te.userid <> $guest
+                                                        ) pl
+
+                     WHERE sud.userid = pl.userid
+                       AND sud.courseid = pl.courseid
+                       AND sud.timeend = $nextmidnight
+                       AND sud.stattype='activity'
                        ) inline_view
+
               GROUP BY timeend, courseid, roleid
                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
 
-        if ($logspresent and !$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
+        if ($logspresent && !stats_run_query($sql, array('courselevel'=>CONTEXT_COURSE))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('12');
+        stats_progress('12');
 
-    /// how many view actions from guests only in each course - excluding frontpage
-    /// normal users may enter course with temporary guest access too
+        /// how many view actions from guests only in each course - excluding frontpage
+        /// normal users may enter course with temporary guest access too
 
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
+                SELECT 'activity', $nextmidnight AS timeend, courseid, $guestrole AS roleid,
+                       SUM(statsreads), SUM(statswrites)
                   FROM (
-                           SELECT $nextmidnight AS timeend, sud.courseid, $guestrole AS nroleid, sud.statsreads, sud.statswrites
-                             FROM {stats_user_daily} sud
-                            WHERE sud.timeend = $nextmidnight AND sud.courseid <> ".SITEID." AND
-                                  sud.stattype='activity' AND
-                                  (sud.userid = $guest OR sud.userid
-                                    NOT IN (SELECT ue.userid
-                                              FROM {user_enrolments} ue
-                                              JOIN {enrol} e ON ue.enrolid = e.id
-                                             WHERE e.courseid = sud.courseid))
+
+                    SELECT sud.courseid, sud.statsreads, sud.statswrites
+                      FROM {temp_stats_user_daily} sud
+                     WHERE sud.timeend = $nextmidnight
+                       AND sud.courseid <> ".SITEID."
+                       AND sud.stattype='activity'
+                       AND (sud.userid = $guest OR sud.userid NOT IN (
+
+                        SELECT userid
+                          FROM {temp_enroled} te
+                         WHERE te.courseid = sud.courseid
+                                                                     ))
                        ) inline_view
-              GROUP BY timeend, courseid, nroleid
+
+              GROUP BY timeend, courseid, roleid
                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
 
-        if ($logspresent and !$DB->execute($sql, array())) {
+        if ($logspresent && !stats_run_query($sql, array())) {
             $failed = true;
             break;
         }
-        stats_daily_progress('13');
+        stats_progress('13');
 
 
-    /// how many view actions for each role on frontpage - excluding guests, not-logged-in and default frontpage role
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        /// How many view actions for each role on frontpage - excluding guests, not-logged-in and default frontpage role
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'activity', timeend, courseid, roleid, SUM(statsreads), SUM(statswrites)
+                SELECT 'activity', $nextmidnight AS timeend, courseid, roleid,
+                       SUM(statsreads), SUM(statswrites)
                   FROM (
-                           SELECT $nextmidnight AS timeend, pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
-                             FROM {stats_user_daily} sud,
-                                      (SELECT DISTINCT ra.userid, ra.roleid, c.instanceid AS courseid
-                                         FROM {role_assignments} ra
-                                         JOIN {context} c ON c.id = ra.contextid
-                                        WHERE ra.contextid = :fpcontext AND
-                                              ra.roleid <> $defaultfproleid AND
-                                              ra.roleid <> $guestrole AND
-                                              ra.userid <> $guest
-                                      ) pl
-                            WHERE sud.userid = pl.userid AND
-                                  sud.courseid = pl.courseid AND
-                                  sud.timeend = $nextmidnight AND
-                                  sud.stattype='activity'
+                    SELECT pl.courseid, pl.roleid, sud.statsreads, sud.statswrites
+                      FROM {temp_stats_user_daily} sud, (
+
+                        SELECT DISTINCT ra.userid, ra.roleid, c.instanceid AS courseid
+                          FROM {role_assignments} ra
+                          JOIN {context} c ON c.id = ra.contextid
+                         WHERE ra.contextid = :fpcontext
+                           AND ra.roleid <> $defaultfproleid
+                           AND ra.roleid <> $guestrole
+                           AND ra.userid <> $guest
+                                                   ) pl
+                     WHERE sud.userid = pl.userid
+                       AND sud.courseid = pl.courseid
+                       AND sud.timeend = $nextmidnight
+                       AND sud.stattype='activity'
                        ) inline_view
+
               GROUP BY timeend, courseid, roleid
                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
 
-        if ($logspresent and !$DB->execute($sql, array('fpcontext'=>$fpcontext->id))) {
+        if ($logspresent && !stats_run_query($sql, array('fpcontext'=>$fpcontext->id))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('14');
+        stats_progress('14');
 
 
-    /// how many view actions for default frontpage role on frontpage only
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        // How many view actions for default frontpage role on frontpage only
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
+                SELECT 'activity', timeend, courseid, $defaultfproleid AS roleid,
+                       SUM(statsreads), SUM(statswrites)
                   FROM (
-                           SELECT sud.timeend AS timeend, sud.courseid, $defaultfproleid AS nroleid, sud.statsreads, sud.statswrites
-                             FROM {stats_user_daily} sud
-                            WHERE sud.timeend = :nextm AND sud.courseid = :siteid AND
-                                  sud.stattype='activity' AND
-                                  sud.userid <> $guest AND sud.userid <> 0 AND sud.userid
-                                  NOT IN (SELECT ra.userid
-                                            FROM {role_assignments} ra
-                                           WHERE ra.roleid <> $guestrole AND
-                                                 ra.roleid <> $defaultfproleid AND ra.contextid = :fpcontext)
+                    SELECT sud.timeend AS timeend, sud.courseid, sud.statsreads, sud.statswrites
+                      FROM {temp_stats_user_daily} sud
+                     WHERE sud.timeend = :nextm
+                       AND sud.courseid = :siteid
+                       AND sud.stattype='activity'
+                       AND sud.userid <> $guest
+                       AND sud.userid <> 0
+                       AND sud.userid NOT IN (
+
+                        SELECT ra.userid
+                          FROM {role_assignments} ra
+                         WHERE ra.roleid <> $guestrole
+                           AND ra.roleid <> $defaultfproleid
+                           AND ra.contextid = :fpcontext)
                        ) inline_view
-              GROUP BY timeend, courseid, nroleid
+
+              GROUP BY timeend, courseid, roleid
                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
 
-        if ($logspresent and !$DB->execute($sql, array('fpcontext'=>$fpcontext->id, 'siteid'=>SITEID, 'nextm'=>$nextmidnight))) {
+        if ($logspresent && !stats_run_query($sql, array('fpcontext'=>$fpcontext->id, 'siteid'=>SITEID, 'nextm'=>$nextmidnight))) {
             $failed = true;
             break;
         }
-        stats_daily_progress('15');
+        stats_progress('15');
 
-    /// how many view actions for guests or not-logged-in on frontpage
-        $sql = "INSERT INTO {stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
+        // How many view actions for guests or not-logged-in on frontpage
+        $sql = "INSERT INTO {temp_stats_daily} (stattype, timeend, courseid, roleid, stat1, stat2)
 
-                SELECT 'activity', timeend, courseid, nroleid, SUM(statsreads), SUM(statswrites)
+                SELECT 'activity', $nextmidnight AS timeend, ".SITEID." AS courseid, $guestrole AS roleid,
+                       SUM(statsreads), SUM(statswrites)
                   FROM (
-                           SELECT $nextmidnight AS timeend, ".SITEID." AS courseid, $guestrole AS nroleid, pl.statsreads, pl.statswrites
-                             FROM (
-                                      SELECT sud.statsreads, sud.statswrites
-                                        FROM {stats_user_daily} sud
-                                       WHERE (sud.userid = $guest OR sud.userid = 0) AND
-                                             sud.timeend = $nextmidnight AND sud.courseid = ".SITEID." AND
-                                             sud.stattype='activity'
-                                  ) pl
-                       ) inline_view
-              GROUP BY timeend, courseid, nroleid
+
+                    SELECT sud.statsreads, sud.statswrites
+                      FROM {temp_stats_user_daily} sud
+                     WHERE (sud.userid = $guest OR sud.userid = 0)
+                       AND sud.timeend = $nextmidnight
+                       AND sud.courseid = ".SITEID."
+                       AND sud.stattype='activity'
+                        ) inline_view
+
+              GROUP BY timeend, courseid, roleid
                 HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
 
-        if ($logspresent and !$DB->execute($sql)) {
+        if ($logspresent && !stats_run_query($sql)) {
             $failed = true;
             break;
         }
-        stats_daily_progress('16');
+        stats_progress('16');
+
+        stats_temp_table_clean();
+
+        stats_progress('out');
 
         // remember processed days
         set_config('statslastdaily', $nextmidnight);
-        mtrace("  finished until $nextmidnight: ".userdate($nextmidnight)." (in ".(time()-$daystart)." s)");
+        $elapsed = time()-$daystart;
+        mtrace("  finished until $nextmidnight: ".userdate($nextmidnight)." (in $elapsed s)");
+        $total += $elapsed;
 
         $timestart    = $nextmidnight;
         $nextmidnight = stats_get_next_day_start($nextmidnight);
     }
 
+    stats_temp_table_drop();
+
     set_cron_lock('statsrunning', null);
 
     if ($failed) {
         $days--;
-        mtrace("...error occurred, completed $days days of statistics.");
+        mtrace("...error occurred, completed $days days of statistics in {$total} s.");
+        return false;
+
+    } else if ($timeout) {
+        mtrace("...stopping early, reached maximum number of $maxdays days ({$total} s) - will continue next time.");
         return false;
 
     } else {
-        mtrace("...completed $days days of statistics.");
+        mtrace("...completed $days days of statistics in {$total} s.");
         return true;
     }
 }
@@ -634,6 +687,9 @@ function stats_cron_weekly() {
         $logtimesql  = "l.time >= $timestart AND l.time < $nextstartweek";
         $stattimesql = "timeend > $timestart AND timeend <= $nextstartweek";
 
+        $weekstart = time();
+        stats_progress('init');
+
     /// process login info first
         $sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads)
 
@@ -644,10 +700,12 @@ function stats_cron_weekly() {
                             WHERE action = 'login' AND $logtimesql
                        ) inline_view
               GROUP BY timeend, courseid, userid
-                HAVING count(statsreads) > 0";
+                HAVING COUNT(statsreads) > 0";
 
         $DB->execute($sql);
 
+        stats_progress('1');
+
         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
 
                 SELECT 'logins' AS stattype, $nextstartweek AS timeend, ".SITEID." as courseid, 0,
@@ -661,6 +719,7 @@ function stats_cron_weekly() {
 
         $DB->execute($sql);
 
+        stats_progress('2');
 
     /// now enrolments averages
         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
@@ -675,6 +734,7 @@ function stats_cron_weekly() {
 
         $DB->execute($sql);
 
+        stats_progress('3');
 
     /// activity read/write averages
         $sql = "INSERT INTO {stats_weekly} (stattype, timeend, courseid, roleid, stat1, stat2)
@@ -689,6 +749,7 @@ function stats_cron_weekly() {
 
         $DB->execute($sql);
 
+        stats_progress('4');
 
     /// user read/write averages
         $sql = "INSERT INTO {stats_user_weekly} (stattype, timeend, courseid, userid, statsreads, statswrites)
@@ -703,8 +764,11 @@ function stats_cron_weekly() {
 
         $DB->execute($sql);
 
+        stats_progress('5');
+
         set_config('statslastweekly', $nextstartweek);
-        mtrace(" finished until $nextstartweek: ".userdate($nextstartweek));
+        $elapsed = time()-$weekstart;
+        mtrace(" finished until $nextstartweek: ".userdate($nextstartweek) ." (in $elapsed s)");
 
         $timestart     = $nextstartweek;
         $nextstartweek = stats_get_next_week_start($nextstartweek);
@@ -765,6 +829,9 @@ function stats_cron_monthly() {
         $logtimesql  = "l.time >= $timestart AND l.time < $nextstartmonth";
         $stattimesql = "timeend > $timestart AND timeend <= $nextstartmonth";
 
+        $monthstart = time();
+        stats_progress('init');
+
     /// process login info first
         $sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads)
 
@@ -778,6 +845,8 @@ function stats_cron_monthly() {
 
         $DB->execute($sql);
 
+        stats_progress('1');
+
         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
 
                 SELECT 'logins' AS stattype, $nextstartmonth AS timeend, ".SITEID." as courseid, 0,
@@ -791,6 +860,7 @@ function stats_cron_monthly() {
 
         $DB->execute($sql);
 
+        stats_progress('2');
 
     /// now enrolments averages
         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
@@ -805,6 +875,7 @@ function stats_cron_monthly() {
 
         $DB->execute($sql);
 
+        stats_progress('3');
 
     /// activity read/write averages
         $sql = "INSERT INTO {stats_monthly} (stattype, timeend, courseid, roleid, stat1, stat2)
@@ -819,6 +890,7 @@ function stats_cron_monthly() {
 
         $DB->execute($sql);
 
+        stats_progress('4');
 
     /// user read/write averages
         $sql = "INSERT INTO {stats_user_monthly} (stattype, timeend, courseid, userid, statsreads, statswrites)
@@ -833,8 +905,11 @@ function stats_cron_monthly() {
 
         $DB->execute($sql);
 
+        stats_progress('5');
+
         set_config('statslastmonthly', $nextstartmonth);
-        mtrace(" finished until $nextstartmonth: ".userdate($nextstartmonth));
+        $elapsed = time() - $monthstart;
+        mtrace(" finished until $nextstartmonth: ".userdate($nextstartmonth) ." (in $elapsed s)");
 
         $timestart      = $nextstartmonth;
         $nextstartmonth = stats_get_next_month_start($nextstartmonth);
@@ -1362,7 +1437,7 @@ function stats_get_report_options($courseid,$mode) {
             $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES);
             $reportoptions[STATS_REPORT_PARTICIPATORY_COURSES_RW] = get_string('statsreport'.STATS_REPORT_PARTICIPATORY_COURSES_RW);
         }
-     break;
+        break;
     }
 
     return $reportoptions;
@@ -1462,3 +1537,182 @@ function stats_check_uptodate($courseid=0) {
     //return error as string
     return get_string('statscatchupmode','error',$a);
 }
+
+/**
+ * Create temporary tables to speed up log generation
+ */
+function stats_temp_table_create() {
+    global $CFG, $DB;
+
+    $dbman = $DB->get_manager(); // We are going to use database_manager services
+
+    stats_temp_table_drop();
+
+    $xmlfile  = $CFG->dirroot . '/lib/db/install.xml';
+    $tempfile = $CFG->dirroot . '/lib/db/temp_stats_log_template.xml';
+    $tables   = array();
+
+    // Allows for the additional xml files to be used (if necessary)
+    $files    = array(
+        $xmlfile  => array(
+            'stats_daily'           => array('temp_stats_daily'),
+            'stats_user_daily'      => array('temp_stats_user_daily'),
+            'temp_enroled_template' => array('temp_enroled'),
+            'temp_log_template'     => array('temp_log1', 'temp_log2'),
+        ),
+    );
+
+    foreach ($files as $file => $contents) {
+
+        $xmldb_file = new xmldb_file($file);
+        if (!$xmldb_file->fileExists()) {
+            throw new ddl_exception('ddlxmlfileerror', null, 'File does not exist');
+        }
+        $loaded = $xmldb_file->loadXMLStructure();
+        if (!$loaded || !$xmldb_file->isLoaded()) {
+            throw new ddl_exception('ddlxmlfileerror', null, 'not loaded??');
+        }
+        $xmldb_structure = $xmldb_file->getStructure();
+
+        foreach ($contents as $template => $names) {
+            $table = $xmldb_structure->getTable($template);
+
+            if (is_null($table)) {
+                throw new ddl_exception('ddlunknowntable', null, 'The table '. $name .' is not defined in the file '. $xmlfile);
+            }
+            $table->setNext(null);\r
+            $table->setPrevious(null);
+
+            foreach ($names as $name) {
+                $named = clone $table;
+                $named->setName($name);
+                $tables[$name] = $named;
+            }
+        }
+    }
+
+    try {
+
+        foreach ($tables as $table) {
+            $dbman->create_temp_table($table);
+        }
+
+    } catch (Exception $e) {
+        mtrace('Temporary table creation failed: '. $e->getMessage());
+        return false;
+    }
+
+    return true;
+}
+
+/**
+ * Deletes summary logs table for stats calculation
+ */
+function stats_temp_table_drop() {
+    global $DB;
+
+    $dbman = $DB->get_manager();
+
+    $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily', 'temp_enroled');
+
+    foreach ($tables as $name) {
+
+        if ($dbman->table_exists($name)) {
+            $table = new xmldb_table($name);
+
+            try {
+                $dbman->drop_table($table);
+            } catch (Exception $e) {
+                mtrace("Error occured while dropping temporary tables!");
+            }
+        }
+    }
+}
+
+/**
+ * Fills the temporary stats tables with new data
+ *
+ * This function is meant to be called once at the start of stats generation
+ *
+ * @param timestart timestamp of the start time of logs view
+ * @param timeend timestamp of the end time of logs view
+ * @returns boolen success (true) or failure(false)
+ */
+function stats_temp_table_setup() {
+    global $DB;
+
+    $sql = "INSERT INTO {temp_enroled} (userid, courseid, roleid)
+
+               SELECT ue.userid, e.courseid, ra.roleid
+                FROM {role_assignments} ra
+                JOIN {context} c ON (c.id = ra.contextid AND c.contextlevel = :courselevel)
+                JOIN {enrol} e ON e.courseid = c.instanceid
+                JOIN {user_enrolments} ue ON (ue.enrolid = e.id AND ue.userid = ra.userid)";
+
+    return stats_run_query($sql, array('courselevel' => CONTEXT_COURSE));
+}
+
+/**
+ * Fills the temporary stats tables with new data
+ *
+ * This function is meant to be called to get a new day of data
+ *
+ * @param timestart timestamp of the start time of logs view
+ * @param timeend timestamp of the end time of logs view
+ * @returns boolen success (true) or failure(false)
+ */
+function stats_temp_table_fill($timestart, $timeend) {
+    global $DB;
+
+    $sql = 'INSERT INTO {temp_log1} (userid, course, action)
+
+            SELECT userid, course, action FROM {log}
+             WHERE time >= ? AND time < ?';
+
+    $DB->execute($sql, array($timestart, $timeend));
+
+    $sql = 'INSERT INTO {temp_log2} (userid, course, action)
+
+            SELECT userid, course, action FROM {temp_log1}';
+
+    $DB->execute($sql);
+
+    return true;
+}
+
+
+/**
+ * Deletes summary logs table for stats calculation
+ *
+ * @returns boolen success (true) or failure(false)
+ */
+function stats_temp_table_clean() {
+    global $DB;
+
+    $sql = array();
+
+    $sql['up1'] = 'INSERT INTO {stats_daily} (courseid, roleid, stattype, timeend, stat1, stat2)
+
+                   SELECT courseid, roleid, stattype, timeend, stat1, stat2 FROM {temp_stats_daily}';
+
+    $sql['up2'] = 'INSERT INTO {stats_user_daily}
+                               (courseid, userid, roleid, timeend, statsreads, statswrites, stattype)
+
+                   SELECT courseid, userid, roleid, timeend, statsreads, statswrites, stattype
+                     FROM {temp_stats_user_daily}';
+
+    foreach ($sql as $id => $query) {
+        if (! stats_run_query($query)) {
+            mtrace("Error during table cleanup!");
+            return false;
+        }
+    }
+
+    $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily');
+
+    foreach ($tables as $name) {
+        $DB->delete_records($name);
+    }
+
+    return true;
+}
diff --git a/lib/tests/fixtures/statslib-test00.xml b/lib/tests/fixtures/statslib-test00.xml
new file mode 100644 (file)
index 0000000..f41e8f3
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Tests no logs - Only query 3 should be processed -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <row>
+            <value>[course1_id]</value>
+            <value>[end_no_logs]</value>
+            <value>5</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test01.xml b/lib/tests/fixtures/statslib-test01.xml
new file mode 100644 (file)
index 0000000..bcfc903
--- /dev/null
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- No login test - Tests queries 2, 3, 5, 7, 9 (and 8), 10 (read) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[guest_id]</value>
+            <value>[site_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 16 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[guest_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 10 - read -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test02.xml b/lib/tests/fixtures/statslib-test02.xml
new file mode 100644 (file)
index 0000000..1b1cd35
--- /dev/null
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Single login - Tests queries 1, 2 (with logins), 4 -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 - read -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test03.xml b/lib/tests/fixtures/statslib-test03.xml
new file mode 100644 (file)
index 0000000..db0c2a0
--- /dev/null
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Guest login and view course - Tests queries 11 (read), 13 (guest) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[guest_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[guest_id]</value>
+            <value>[course1_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 13 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[guest_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test04.xml b/lib/tests/fixtures/statslib-test04.xml
new file mode 100644 (file)
index 0000000..efe9e2a
--- /dev/null
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Guest login, view course, and upload assignment - Tests queries 10+11 (write) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[guest_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[guest_id]</value>
+            <value>[course1_id]</value>
+            <value>view</value>
+        </row>
+        <row>
+            <value>[start_3]</value>
+            <value>[guest_id]</value>
+            <value>[course1_id]</value>
+            <value>add post</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 13 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[guest_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>1</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test05.xml b/lib/tests/fixtures/statslib-test05.xml
new file mode 100644 (file)
index 0000000..b839f74
--- /dev/null
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- User login and view course - Tests queries 4, 6, 10, 12, 14 (read) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[user1_id]</value>
+            <value>[course1_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Queris 3 (stat1) and 4 (stat2) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Queries 5 (stat1) and 6 (stat2) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 12 (read) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test06.xml b/lib/tests/fixtures/statslib-test06.xml
new file mode 100644 (file)
index 0000000..0401710
--- /dev/null
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- User login, view course and post - Tests queries 10, 12, 14 (write) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[user1_id]</value>
+            <value>[course1_id]</value>
+            <value>view</value>
+        </row>
+        <row>
+            <value>[start_3]</value>
+            <value>[user1_id]</value>
+            <value>[course1_id]</value>
+            <value>add post</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Queris 3 (stat1) and 4 (stat2) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Queries 5 (stat1) and 6 (stat2) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 12 (read) -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>1</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test07.xml b/lib/tests/fixtures/statslib-test07.xml
new file mode 100644 (file)
index 0000000..86379b8
--- /dev/null
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- User login and view course (not enrolled) - Tests queries 13 (not enroled), 14 (not default) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[user2_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[user2_id]</value>
+            <value>[site_id]</value>
+            <value>view</value>
+        </row>
+        <row>
+            <value>[start_3]</value>
+            <value>[user2_id]</value>
+            <value>[course1_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 13 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[guest_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 14 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user2_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[user2_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user2_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test08.xml b/lib/tests/fixtures/statslib-test08.xml
new file mode 100644 (file)
index 0000000..62a90c9
--- /dev/null
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- User login and view site - Tests query 15 (front page views) -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_2]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 15 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test09.xml b/lib/tests/fixtures/statslib-test09.xml
new file mode 100644 (file)
index 0000000..efe77b6
--- /dev/null
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- Multiple logins on different days -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_0]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_1]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+        <row>
+            <value>[start_4]</value>
+            <value>[user1_id]</value>
+            <value>[site_id]</value>
+            <value>login</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>1</value>
+            <value>1</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 9 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[frontpage_roleid]</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 1 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>logins</value>
+        </row>
+        <!-- Query 10 - read -->
+        <row>
+            <value>[site_id]</value>
+            <value>[user1_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/fixtures/statslib-test10.xml b/lib/tests/fixtures/statslib-test10.xml
new file mode 100644 (file)
index 0000000..83d7fcc
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- No default profile id test -->
+<dataset>
+    <table name="log">
+        <column>time</column>
+        <column>userid</column>
+        <column>course</column>
+        <column>action</column>
+        <row>
+            <value>[start_1]</value>
+            <value>[guest_id]</value>
+            <value>[site_id]</value>
+            <value>view</value>
+        </row>
+    </table>
+    <table name="stats_daily">
+        <column>courseid</column>
+        <column>timeend</column>
+        <column>roleid</column>
+        <column>stattype</column>
+        <column>stat1</column>
+        <column>stat2</column>
+        <!-- Query 2 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>logins</value>
+            <value>0</value>
+            <value>0</value>
+        </row>
+        <!-- Query 3 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>[student_roleid]</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 5 -->
+        <row>
+            <value>[course1_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 7 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>enrolments</value>
+            <value>4</value>
+            <value>1</value>
+        </row>
+        <!-- Query 11 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+        <!-- Query 16 -->
+        <row>
+            <value>[site_id]</value>
+            <value>[end]</value>
+            <value>[guest_roleid]</value>
+            <value>activity</value>
+            <value>1</value>
+            <value>0</value>
+        </row>
+    </table>
+    <table name="stats_user_daily">
+        <column>courseid</column>
+        <column>userid</column>
+        <column>roleid</column>
+        <column>timeend</column>
+        <column>statsreads</column>
+        <column>statswrites</column>
+        <column>stattype</column>
+        <!-- Query 10 - read -->
+        <row>
+            <value>[site_id]</value>
+            <value>[guest_id]</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>1</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+        <!-- Query 10 - default record -->
+        <row>
+            <value>[site_id]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>[end]</value>
+            <value>0</value>
+            <value>0</value>
+            <value>activity</value>
+        </row>
+    </table>
+</dataset>
diff --git a/lib/tests/messagelib_test.php b/lib/tests/messagelib_test.php
new file mode 100644 (file)
index 0000000..5d21b82
--- /dev/null
@@ -0,0 +1,157 @@
+<?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/>.
+
+/**
+ * Tests for messagelib.php.
+ *
+ * @package    core_message
+ * @copyright  2012 The Open Universtiy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class messagelib_testcase extends advanced_testcase {
+
+    public function test_message_get_providers_for_user() {
+        global $CFG, $DB;
+
+        $this->resetAfterTest(true);
+
+        $generator = $this->getDataGenerator();
+
+        // Create a course category and course
+        $cat = $generator->create_category(array('parent' => 0));
+        $course = $generator->create_course(array('category' => $cat->id));
+        $quiz = $generator->create_module('quiz', array('course' => $course->id));
+        $user = $generator->create_user();
+
+        $coursecontext = context_course::instance($course->id);
+        $quizcontext = context_module::instance($quiz->cmid);
+        $frontpagecontext = context_course::instance(SITEID);
+
+        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
+
+        // The user is a student in a course, and has the capability for quiz
+        // confirmation emails in one quiz in that course.
+        role_assign($studentrole->id, $user->id, $coursecontext->id);
+        assign_capability('mod/quiz:emailconfirmsubmission', CAP_ALLOW, $studentrole->id, $quizcontext->id);
+
+        // Give this message type to the front page role.
+        assign_capability('mod/quiz:emailwarnoverdue', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagecontext->id);
+
+        $providers = message_get_providers_for_user($user->id);
+        $this->assertTrue($this->message_type_present('mod_forum', 'posts', $providers));
+        $this->assertTrue($this->message_type_present('mod_quiz', 'confirmation', $providers));
+        $this->assertTrue($this->message_type_present('mod_quiz', 'attempt_overdue', $providers));
+        $this->assertFalse($this->message_type_present('mod_quiz', 'submission', $providers));
+
+        // A user is a student in a different course, they should not get confirmation.
+        $course2 = $generator->create_course(array('category' => $cat->id));
+        $user2 = $generator->create_user();
+        $coursecontext2 = context_course::instance($course2->id);
+        role_assign($studentrole->id, $user2->id, $coursecontext2->id);
+        accesslib_clear_all_caches_for_unit_testing();
+        $providers = message_get_providers_for_user($user2->id);
+        $this->assertTrue($this->message_type_present('mod_forum', 'posts', $providers));
+        $this->assertFalse($this->message_type_present('mod_quiz', 'confirmation', $providers));
+
+        // Now remove the frontpage role id, and attempt_overdue message should go away.
+        unset_config('defaultfrontpageroleid');
+        accesslib_clear_all_caches_for_unit_testing();
+
+        $providers = message_get_providers_for_user($user->id);
+        $this->assertTrue($this->message_type_present('mod_quiz', 'confirmation', $providers));
+        $this->assertFalse($this->message_type_present('mod_quiz', 'attempt_overdue', $providers));
+        $this->assertFalse($this->message_type_present('mod_quiz', 'submission', $providers));
+    }
+
+    public function test_message_get_providers_for_user_more() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Create a course
+        $course = $this->getDataGenerator()->create_course();
+        $coursecontext = context_course::instance($course->id);
+
+        // It would probably be better to use a quiz instance as it has capability controlled messages
+        // however mod_quiz doesn't have a data generator
+        // Instead we're going to use backup notifications and give and take away the capability at various levels
+        $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
+        $modulecontext = context_module::instance($assign->id);
+
+        // Create and enrol a teacher
+        $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
+        $teacher = $this->getDataGenerator()->create_user();
+        role_assign($teacherrole->id, $teacher->id, $coursecontext);
+        $enrolplugin = enrol_get_plugin('manual');
+        $enrolplugin->add_instance($course);
+        $enrolinstances = enrol_get_instances($course->id, false);
+        foreach ($enrolinstances as $enrolinstance) {
+            if ($enrolinstance->enrol === 'manual') {
+                break;
+            }
+        }
+        $enrolplugin->enrol_user($enrolinstance, $teacher->id);
+
+        // Make the teacher the current user
+        $this->setUser($teacher);
+
+        // Teacher shouldn't have the required capability so they shouldn't be able to see the backup message
+        $this->assertFalse(has_capability('moodle/site:config', $modulecontext));
+        $providers = message_get_providers_for_user($teacher->id);
+        $this->assertFalse($this->message_type_present('moodle', 'backup', $providers));
+
+        // Give the user the required capability in an activity module
+        // They should now be able to see the backup message
+        assign_capability('moodle/site:config', CAP_ALLOW, $teacherrole->id, $modulecontext->id, true);
+        accesslib_clear_all_caches_for_unit_testing();
+        $modulecontext = context_module::instance($assign->id);
+        $this->assertTrue(has_capability('moodle/site:config', $modulecontext));
+
+        $providers = message_get_providers_for_user($teacher->id);
+        $this->assertTrue($this->message_type_present('moodle', 'backup', $providers));
+
+        // Prohibit the capability for the user at the course level
+        // This overrules the CAP_ALLOW at the module level
+        // They should not be able to see the backup message
+        assign_capability('moodle/site:config', CAP_PROHIBIT, $teacherrole->id, $coursecontext->id, true);
+        accesslib_clear_all_caches_for_unit_testing();
+        $modulecontext = context_module::instance($assign->id);
+        $this->assertFalse(has_capability('moodle/site:config', $modulecontext));
+
+        $providers = message_get_providers_for_user($teacher->id);
+        // Actually, handling PROHIBITs would be too expensive. We do not
+        // care if users with PROHIBITs see a few more preferences than they should.
+        // $this->assertFalse($this->message_type_present('moodle', 'backup', $providers));
+    }
+
+    /**
+     * Is a particular message type in the list of message types.
+     * @param string $name a message name.
+     * @param array $providers as returned by message_get_providers_for_user.
+     * @return bool whether the message type is present.
+     */
+    protected function message_type_present($component, $name, $providers) {
+        foreach ($providers as $provider) {
+            if ($provider->component == $component && $provider->name == $name) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
index e433394..33c014c 100644 (file)
@@ -41,8 +41,10 @@ class moodlelib_testcase extends advanced_testcase {
             '6.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'),
             '7.0' => array('Windows XP SP2' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; YPC 3.0.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)'),
             '8.0' => array('Windows Vista' => 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 1.1.4322; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648)'),
-            '9.0' => array('Windows 7' => 'Mozilla/5.0 (Windows; U; MSIE 9.0; WIndows NT 9.0; en-US))'),
-
+            '9.0' => array('Windows 7' => 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'),
+            '9.0i' => array('Windows 7' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0)'),
+            '10.0' => array('Windows 8' => 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0; Touch)'),
+            '10.0i' => array('Windows 8' => 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; Trident/6.0; Touch; .NET4.0E; .NET4.0C; Tablet PC 2.0)'),
         ),
         'Firefox' => array(
             '1.0.6'   => array('Windows XP' => 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.10) Gecko/20050716 Firefox/1.0.6'),
@@ -253,6 +255,29 @@ class moodlelib_testcase extends advanced_testcase {
         $this->assertTrue(check_browser_version('MSIE', '9.0'));
         $this->assertFalse(check_browser_version('MSIE', '10'));
 
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['9.0i']['Windows 7'];
+        $this->assertTrue(check_browser_version('MSIE'));
+        $this->assertTrue(check_browser_version('MSIE', 0));
+        $this->assertTrue(check_browser_version('MSIE', '5.0'));
+        $this->assertTrue(check_browser_version('MSIE', '9.0'));
+        $this->assertFalse(check_browser_version('MSIE', '10'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['10.0']['Windows 8'];
+        $this->assertTrue(check_browser_version('MSIE'));
+        $this->assertTrue(check_browser_version('MSIE', 0));
+        $this->assertTrue(check_browser_version('MSIE', '5.0'));
+        $this->assertTrue(check_browser_version('MSIE', '9.0'));
+        $this->assertTrue(check_browser_version('MSIE', '10'));
+        $this->assertFalse(check_browser_version('MSIE', '11'));
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['10.0i']['Windows 8'];
+        $this->assertTrue(check_browser_version('MSIE'));
+        $this->assertTrue(check_browser_version('MSIE', 0));
+        $this->assertTrue(check_browser_version('MSIE', '5.0'));
+        $this->assertTrue(check_browser_version('MSIE', '9.0'));
+        $this->assertTrue(check_browser_version('MSIE', '10'));
+        $this->assertFalse(check_browser_version('MSIE', '11'));
+
         $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP'];
         $this->assertTrue(check_browser_version('Firefox'));
         $this->assertTrue(check_browser_version('Firefox', '1.5'));
@@ -394,6 +419,18 @@ class moodlelib_testcase extends advanced_testcase {
         $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['8.0']['Windows Vista'];
         $this->assertEquals(array('ie', 'ie8'), get_browser_version_classes());
 
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['9.0']['Windows 7'];
+        $this->assertEquals(array('ie', 'ie9'), get_browser_version_classes());
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['9.0i']['Windows 7'];
+        $this->assertEquals(array('ie', 'ie9'), get_browser_version_classes());
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['10.0']['Windows 8'];
+        $this->assertEquals(array('ie', 'ie10'), get_browser_version_classes());
+
+        $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['MSIE']['10.0i']['Windows 8'];
+        $this->assertEquals(array('ie', 'ie10'), get_browser_version_classes());
+
         $_SERVER['HTTP_USER_AGENT'] = $this->user_agents['Firefox']['2.0']['Windows XP'];
         $this->assertEquals(array('gecko', 'gecko18'), get_browser_version_classes());
 
index 52771c9..8fd97b6 100644 (file)
@@ -193,8 +193,8 @@ class theme_config_testcase extends advanced_testcase {
             '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,
+            // IE9 on Windows 7 in intranet mode.
+            'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/5.0)' => true,
             // 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.
diff --git a/lib/tests/statslib_test.php b/lib/tests/statslib_test.php
new file mode 100644 (file)
index 0000000..8cec9c6
--- /dev/null
@@ -0,0 +1,613 @@
+<?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/>.
+
+/**
+ * Tests for ../statslib.php
+ *
+ * @package    core
+ * @subpackage stats
+ * @copyright  2012 Tyler Bannister
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/adminlib.php');
+require_once($CFG->libdir . '/statslib.php');
+
+/**
+ * Test functions that affect daily stats
+ */
+class statslib_daily_testcase extends advanced_testcase {
+    /** The student role ID **/
+    const STID = 5;
+
+    /** The day to use for testing **/
+    const DAY = 1272672000;
+
+    /** The timezone to use for testing **/
+    const TIMEZONE = 0;
+
+    /** @var array The list of temporary tables created for the statistic calculations **/
+    protected $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily');
+
+    /** @var array The replacements to be used when loading XML files **/
+    protected $replacements = null;
+
+    /**
+     * Set up the database for tests
+     *
+     * This function is needed so that daily_log_provider has the before-test set up from setUp()
+     */
+    public function setUpDB() {
+        global $DB;
+
+        if ($DB->record_exists('user', array('username' => 'user1'))) {
+            return;
+        }
+
+        $datagen = self::getDataGenerator();
+
+        $user1   = $datagen->create_user(array('username'=>'user1'));
+        $user2   = $datagen->create_user(array('username'=>'user2'));
+
+        $course1 = $datagen->create_course(array('shortname'=>'course1'));
+
+        $success = enrol_try_internal_enrol($course1->id, $user1->id, 5);
+
+        if (! $success) {
+            trigger_error('User enrollment failed', E_USER_ERROR);
+        }
+
+        $context = context_system::instance();
+        role_assign(self::STID, $user2->id, $context->id);
+
+        $this->generate_replacement_list();
+    }
+
+    /**
+     * Setup function
+     *   - Allow changes to CFG->debug for testing purposes.
+     */
+    protected function setUp() {
+        global $CFG;
+        parent::setUp();
+
+        // Settings to force statistic to run during testing
+        $CFG->timezone                = self::TIMEZONE;
+        $CFG->statsfirstrun           = 'all';
+        $CFG->statslastdaily          = 0;
+        $CFG->statslastexecution      = 0;
+
+        // Figure out the broken day start so I can figure out when to the start time should be
+        $time   = time();
+        $offset = get_timezone_offset($CFG->timezone);
+        $stime  = $time + $offset;
+        $stime  = intval($stime / (60*60*24)) * 60*60*24;
+        $stime -= $offset;
+
+        $shour  = intval(($time - $stime) / (60*60));
+
+        $CFG->statsruntimestarthour   = $shour;
+        $CFG->statsruntimestartminute = 0;
+
+        $this->setUpDB();
+
+        $this->resetAfterTest(true);
+    }
+
+    /**
+     * Function to setup database.
+     *
+     * @param array $dataset An array of tables including the log table.
+     */
+    protected function prepare_db($dataset, $tables) {
+        global $DB;
+
+        foreach ($tables as $tablename) {
+            $DB->delete_records($tablename);
+
+            foreach ($dataset as $name => $table) {
+
+                if ($tablename == $name) {
+
+                    $rows = $table->getRowCount();
+
+                    for ($i = 0; $i < $rows; $i++) {
+                        $row = $table->getRow($i);
+
+                        $DB->insert_record($tablename, $row, false, true);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Load dataset from XML file
+     *
+     * @param string $file The name of the file to load
+     */
+    protected function generate_replacement_list() {
+        global $CFG, $DB;
+
+        if ($this->replacements !== null) {
+            return;
+        }
+
+        $CFG->timezone = self::TIMEZONE;
+
+        $guest = $DB->get_record('user', array('id' => $CFG->siteguest));
+        $user1 = $DB->get_record('user', array('username' => 'user1'));
+        $user2 = $DB->get_record('user', array('username' => 'user2'));
+
+        if (($guest === false) || ($user1 === false) || ($user2 === false)) {
+            trigger_error('User setup incomplete', E_USER_ERROR);
+        }
+
+        $site    = $DB->get_record('course', array('id' => SITEID));
+        $course1 = $DB->get_record('course', array('shortname' => 'course1'));
+
+        if (($site === false) || ($course1 === false)) {
+            trigger_error('Course setup incomplete', E_USER_ERROR);
+        }
+
+        $offset = get_timezone_offset($CFG->timezone);
+
+        $start      = stats_get_base_daily(self::DAY + 3600);
+        $startnolog = stats_get_base_daily(stats_get_start_from('daily'));
+        $gr         = get_guest_role();
+
+        $this->replacements = array(
+            // Start and end times
+            '[start_0]'          => $start -  14410,  // 4 hours before
+            '[start_1]'          => $start +  14410,  // 4 hours after
+            '[start_2]'          => $start +  14420,
+            '[start_3]'          => $start +  14430,
+            '[start_4]'          => $start + 100800, // 28 hours after
+            '[end]'              => stats_get_next_day_start($start),
+            '[end_no_logs]'      => stats_get_next_day_start($startnolog),
+
+            // User ids
+            '[guest_id]'         => $guest->id,
+            '[user1_id]'         => $user1->id,
+            '[user2_id]'         => $user2->id,
+
+            // Course ids
+            '[course1_id]'       => $course1->id,
+            '[site_id]'          => SITEID,
+
+            // Role ids
+            '[frontpage_roleid]' => (int) $CFG->defaultfrontpageroleid,
+            '[guest_roleid]'     => $gr->id,
+            '[student_roleid]'   => self::STID,
+        );
+    }
+
+    /**
+     * Load dataset from XML file
+     *
+     * @param string $file The name of the file to load
+     */
+    protected function load_xml_data_file($file) {
+        static $replacements = null;
+
+        $raw   = $this->createXMLDataSet($file);
+        $clean = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($raw);
+
+        foreach ($this->replacements as $placeholder => $value) {
+            $clean->addFullReplacement($placeholder, $value);
+        }
+
+        $logs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean);
+        $logs->addIncludeTables(array('log'));
+
+        $stats = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean);
+        $stats->addIncludeTables(array('stats_daily', 'stats_user_daily'));
+
+        return array($logs, $stats);
+    }
+
+    /**
+     * Provides the log data for test_statslib_cron_daily
+     */
+    public function daily_log_provider() {
+        global $CFG, $DB;
+
+        $this->setUpDB();
+
+        $tests = array('00', '01', '02', '03', '04', '05', '06', '07', '08');
+
+        $dataset = array();
+
+        foreach ($tests as $test) {
+            $dataset[] = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test{$test}.xml");
+        }
+
+        return $dataset;
+    }
+
+    /**
+     * Compare the expected stats to those in the database.
+     *
+     * @param array $stats An array of arrays of arrays of both types of stats
+     */
+    protected function verify_stats($expected) {
+        global $DB;
+
+        // Note: We can not use $this->assertDataSetEqual($expected, $actual) because there's no
+        //       $this->getConnection() in advanced_testcase.
+
+        foreach ($expected as $type => $table) {
+            $records = $DB->get_records($type);
+
+            $rows = $table->getRowCount();
+
+            $this->assertEquals($rows, sizeof($records),
+                    'Incorrect number of results returned for '. $type);
+
+            for ($i = 0; $i < $rows; $i++) {
+                $row   = $table->getRow($i);
+                $found = 0;
+
+                foreach ($records as $key => $record) {
+                    $record = (array) $record;
+                    unset($record['id']);
+                    $diff = array_merge(array_diff_assoc($row, $record),
+                            array_diff_assoc($record, $row));
+
+                    if (empty($diff)) {
+                        $found = $key;
+                        break;
+                    }
+                }
+                $this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true)
+                                        ." was not found in $type ". var_export($records, true));
+                unset($records[$found]);
+            }
+        }
+    }
+
+    /**
+     * Test progress output when debug is on
+     */
+    public function test_statslib_progress_debug() {
+        global $CFG;
+
+        $CFG->debug = DEBUG_ALL;
+        $this->expectOutputString('1:0 ');
+        stats_progress('init');
+        stats_progress('1');
+    }
+
+    /**
+     * Test progress output when debug is off
+     */
+    public function test_statslib_progress_no_debug() {
+        global $CFG;
+
+        $CFG->debug = DEBUG_NONE;
+        $this->expectOutputString('.');
+        stats_progress('init');
+        stats_progress('1');
+    }
+
+    /**
+     * Test the function that gets the start date from the config
+     */
+    public function test_statslib_get_start_from() {
+        global $CFG, $DB;
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml");
+        $time = time();
+        $DB->delete_records('log');
+
+        // Don't ask.  I don't think get_timezone_offset works correctly.
+        $day = self::DAY - get_timezone_offset($CFG->timezone);
+
+        $CFG->statsfirstrun = 'all';
+        // Allow 1 second difference in case we cross a second boundary.
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'All start time');
+
+        $this->prepare_db($dataset[0], array('log'));
+        $records = $DB->get_records('log');
+
+        $this->assertEquals($day + 14410, stats_get_start_from('daily'), 'Log entry start');
+
+        $CFG->statsfirstrun = 'none';
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'None start time');
+
+        $CFG->statsfirstrun = 14515200;
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (14515200)), 'Specified start time');
+
+        $this->prepare_db($dataset[1], array('stats_daily'));
+        $this->assertEquals($day + (24 * 3600), stats_get_start_from('daily'), 'Daily stats start time');
+    }
+
+    /**
+     * Test the function that calculates the start of the day
+     *
+     * NOTE: I don't think this is the way this function should work.
+     *       This test documents the current functionality.
+     */
+    public function test_statslib_get_base_daily() {
+        global $CFG;
+
+        for ($x = 0; $x < 24; $x += 1) {
+            $CFG->timezone = $x;
+
+            $start = 1272672000 - ($x * 3600);
+            if ($x >= 20) {
+                $start += (24 * 3600);
+            }
+
+            $this->assertEquals($start, stats_get_base_daily(1272686410), "Timezone $x check");
+        }
+    }
+
+    /**
+     * Test the function that gets the start of the next day
+     */
+    public function test_statslib_get_next_day_start() {
+        global $CFG;
+
+        $CFG->timezone = 0;
+        $this->assertEquals(1272758400, stats_get_next_day_start(1272686410));
+    }
+
+    /**
+     * Test the function that gets the action names
+     *
+     * Note: The function results depend on installed modules.  The hard coded lists are the
+     *       defaults for a new Moodle 2.3 install.
+     */
+    public function test_statslib_get_action_names() {
+        $basepostactions = array (
+            0 => 'add',
+            1 => 'delete',
+            2 => 'edit',
+            3 => 'add mod',
+            4 => 'delete mod',
+            5 => 'edit sectionenrol',
+            6 => 'loginas',
+            7 => 'new',
+            8 => 'unenrol',
+            9 => 'update',
+            10 => 'update mod',
+            11 => 'upload',
+            12 => 'submit',
+            13 => 'submit for grading',
+            14 => 'talk',
+            15 => 'choose',
+            16 => 'choose again',
+            17 => 'record delete',
+            18 => 'add discussion',
+            19 => 'add post',
+            20 => 'delete discussion',
+            21 => 'delete post',
+            22 => 'move discussion',
+            23 => 'prune post',
+            24 => 'update post',
+            25 => 'add category',
+            26 => 'add entry',
+            27 => 'approve entry',
+            28 => 'delete category',
+            29 => 'delete entry',
+            30 => 'edit category',
+            31 => 'update entry',
+            32 => 'end',
+            33 => 'start',
+            34 => 'attempt',
+            35 => 'close attempt',
+            36 => 'preview',
+            37 => 'editquestions',
+            38 => 'delete attempt',
+            39 => 'manualgrade',
+        );
+
+         $baseviewactions = array (
+            0 => 'view',
+            1 => 'view all',
+            2 => 'history',
+            3 => 'view submission',
+            4 => 'view feedback',
+            5 => 'print',
+            6 => 'report',
+            7 => 'view discussion',
+            8 => 'search',
+            9 => 'forum',
+            10 => 'forums',
+            11 => 'subscribers',
+            12 => 'view forum',
+            13 => 'view entry',
+            14 => 'review',
+            15 => 'pre-view',
+            16 => 'download',
+            17 => 'view form',
+            18 => 'view graph',
+            19 => 'view report',
+        );
+
+        $postactions = stats_get_action_names('post');
+
+        foreach ($basepostactions as $action) {
+            $this->assertContains($action, $postactions);
+        }
+
+        $viewactions = stats_get_action_names('view');
+
+        foreach ($baseviewactions as $action) {
+            $this->assertContains($action, $viewactions);
+        }
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     */
+    public function test_statslib_temp_table_create_and_drop() {
+        global $DB;
+
+        foreach ($this->tables as $table) {
+            $this->assertFalse($DB->get_manager()->table_exists($table));
+        }
+
+        stats_temp_table_create();
+
+        foreach ($this->tables as $table) {
+            $this->assertTrue($DB->get_manager()->table_exists($table));
+        }
+
+        stats_temp_table_drop();
+
+        foreach ($this->tables as $table) {
+            $this->assertFalse($DB->get_manager()->table_exists($table));
+        }
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_fill() {
+        global $CFG, $DB;
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml");
+
+        $this->prepare_db($dataset[0], array('log'));
+
+        $start = self::DAY - get_timezone_offset($CFG->timezone);
+        $end   = $start + (24 * 3600);
+
+        stats_temp_table_create();
+        stats_temp_table_fill($start, $end);
+
+        $this->assertEquals(1, $DB->count_records('temp_log1'));
+        $this->assertEquals(1, $DB->count_records('temp_log2'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_setup() {
+        global $DB;
+
+        $logs = array();
+        $this->prepare_db($logs, array('log'));
+
+        stats_temp_table_create();
+        stats_temp_table_setup();
+
+        $this->assertEquals(1, $DB->count_records('temp_enroled'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the function that clean out the temporary tables.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_clean() {
+        global $DB;
+
+        $rows = array(
+            'temp_log1'             => array('id' => 1, 'course' => 1),
+            'temp_log2'             => array('id' => 1, 'course' => 1),
+            'temp_stats_daily'      => array('id' => 1, 'courseid' => 1),
+            'temp_stats_user_daily' => array('id' => 1, 'courseid' => 1),
+        );
+
+        stats_temp_table_create();
+
+        foreach ($rows as $table => $row) {
+            $DB->insert_record_raw($table, $row);
+            $this->assertEquals(1, $DB->count_records($table));
+        }
+
+        stats_temp_table_clean();
+
+        foreach ($rows as $table => $row) {
+            $this->assertEquals(0, $DB->count_records($table));
+        }
+
+        $this->assertEquals(1, $DB->count_records('stats_daily'));
+        $this->assertEquals(1, $DB->count_records('stats_user_daily'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the daily stats function
+     *
+     * @depends test_statslib_get_base_daily
+     * @depends test_statslib_get_next_day_start
+     * @depends test_statslib_get_start_from
+     * @depends test_statslib_temp_table_create_and_drop
+     * @depends test_statslib_temp_table_setup
+     * @depends test_statslib_temp_table_fill
+     * @dataProvider daily_log_provider
+     */
+    public function test_statslib_cron_daily($logs, $stats) {
+        global $CFG, $DB;
+
+        $CFG->debug = DEBUG_NONE;
+
+        $this->prepare_db($logs, array('log'));
+
+        // Stats cron daily uses mtrace, turn on buffering to silence output.
+        ob_start();
+        stats_cron_daily(1);
+        ob_end_clean();
+
+        $this->verify_stats($stats);
+    }
+
+    /**
+     * Test the daily stats function
+     * @depends test_statslib_get_base_daily
+     * @depends test_statslib_get_next_day_start
+     */
+    public function test_statslib_cron_daily_no_default_profile_id() {
+        global $CFG, $DB;
+        $CFG->defaultfrontpageroleid = 0;
+
+        $course1  = $DB->get_record('course', array('shortname' => 'course1'));
+        $guestid  = $CFG->siteguest;
+        $start    = stats_get_base_daily(1272758400);
+        $end      = stats_get_next_day_start($start);
+        $fpid     = (int) $CFG->defaultfrontpageroleid;
+        $gr       = get_guest_role();
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test10.xml");
+
+        $CFG->debug = DEBUG_NONE;
+
+        $this->prepare_db($dataset[0], array('log'));
+
+        // Stats cron daily uses mtrace, turn on buffering to silence output.
+        ob_start();
+        stats_cron_daily($maxdays=1);
+        ob_end_clean();
+
+        $this->verify_stats($dataset[1]);
+    }
+}
index 6be03c9..8bdf838 100644 (file)
@@ -1849,6 +1849,10 @@ function send_headers($contenttype, $cacheable = true) {
     @header('Content-Script-Type: text/javascript');
     @header('Content-Style-Type: text/css');
 
+    if (empty($CFG->additionalhtmlhead) or stripos($CFG->additionalhtmlhead, 'X-UA-Compatible') === false) {
+        @header('X-UA-Compatible: IE=edge');
+    }
+
     if ($cacheable) {
         // Allow caching on "back" (but not on normal clicks)
         @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
index 53291dc..80675c6 100644 (file)
@@ -317,6 +317,22 @@ class assign_grading_table extends table_sql implements renderable {
         }
     }
 
+    /**
+     * Before adding each row to the table make sure rownum is incremented
+     *
+     * @param array $row row of data from db used to make one row of the table.
+     * @return array one row for the table
+     */
+    function format_row($row) {
+        if ($this->rownum < 0) {
+            $this->rownum = $this->currpage * $this->pagesize;
+        } else {
+            $this->rownum += 1;
+        }
+
+        return parent::format_row($row);
+    }
+
     /**
      * Add a column with an ID that uniquely identifies this user in this assignment
      *
@@ -491,7 +507,7 @@ class assign_grading_table extends table_sql implements renderable {
 
 
     /**
-     * Format a user picture for display (and update rownum as a sideeffect)
+     * Format a user picture for display
      *
      * @param stdClass $row
      * @return string
@@ -686,11 +702,6 @@ class assign_grading_table extends table_sql implements renderable {
      */
     function col_userid(stdClass $row) {
         $edit = '';
-        if ($this->rownum < 0) {
-            $this->rownum = $this->currpage * $this->pagesize;
-        } else {
-            $this->rownum += 1;
-        }
 
         $actions = array();
 
index 3c55cca..8d7b28d 100644 (file)
@@ -1026,6 +1026,8 @@ function quiz_process_options($quiz) {
         $quiz->feedbackboundaries[-1] = $quiz->grade + 1;
         $quiz->feedbackboundaries[$numboundaries] = 0;
         $quiz->feedbackboundarycount = $numboundaries;
+    } else {
+        $quiz->feedbackboundarycount = -1;
     }
 
     // Combing the individual settings into the review columns.
diff --git a/mod/quiz/tests/generator/lib.php b/mod/quiz/tests/generator/lib.php
new file mode 100644 (file)
index 0000000..24ed645
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Quiz module test data generator class
+ *
+ * @package mod_quiz
+ * @copyright 2012 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_quiz_generator extends phpunit_module_generator {
+
+    /**
+     * Create new quiz module instance.
+     * @param array|stdClass $record
+     * @param array $options (mostly course_module properties)
+     * @return stdClass activity record with extra cmid field
+     */
+    public function create_instance($record = null, array $options = null) {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/quiz/locallib.php");
+
+        $this->instancecount++;
+        $i = $this->instancecount;
+
+        $record = (object)(array)$record;
+        $options = (array)$options;
+
+        if (empty($record->course)) {
+            throw new coding_exception('module generator requires $record->course');
+        }
+        if (isset($options['idnumber'])) {
+            $record->cmidnumber = $options['idnumber'];
+        } else {
+            $record->cmidnumber = '';
+        }
+
+        $alwaysvisible = mod_quiz_display_options::DURING | mod_quiz_display_options::IMMEDIATELY_AFTER |
+                mod_quiz_display_options::LATER_WHILE_OPEN | mod_quiz_display_options::AFTER_CLOSE;
+
+        $defaultquizsettings = array(
+            'name'                   => get_string('pluginname', 'data').' '.$i,
+            'intro'                  => 'Test quiz ' . $i,
+            'introformat'            => FORMAT_MOODLE,
+            'timeopen'               => 0,
+            'timeclose'              => 0,
+            'preferredbehaviour'     => 'deferredfeedback',
+            'attempts'               => 0,
+            'attemptonlast'          => 0,
+            'grademethod'            => QUIZ_GRADEHIGHEST,
+            'decimalpoints'          => 2,
+            'questiondecimalpoints'  => -1,
+            'reviewattempt'          => $alwaysvisible,
+            'reviewcorrectness'      => $alwaysvisible,
+            'reviewmarks'            => $alwaysvisible,
+            'reviewspecificfeedback' => $alwaysvisible,
+            'reviewgeneralfeedback'  => $alwaysvisible,
+            'reviewrightanswer'      => $alwaysvisible,
+            'reviewoverallfeedback'  => $alwaysvisible,
+            'questionsperpage'       => 1,
+            'shufflequestions'       => 0,
+            'shuffleanswers'         => 1,
+            'questions'              => '',
+            'sumgrades'              => 0,
+            'grade'                  => 0,
+            'timecreated'            => time(),
+            'timemodified'           => time(),
+            'timelimit'              => 0,
+            'overduehandling'        => 'autoabandon',
+            'graceperiod'            => 86400,
+            'quizpassword'           => '',
+            'subnet'                 => '',
+            'browsersecurity'        => '',
+            'delay1'                 => 0,
+            'delay2'                 => 0,
+            'showuserpicture'        => 0,
+            'showblocks'             => 0,
+            'navmethod'              => QUIZ_NAVMETHOD_FREE,
+        );
+
+        foreach ($defaultquizsettings as $name => $value) {
+            if (!isset($record->{$name})) {
+                $record->{$name} = $value;
+            }
+        }
+
+        $record->coursemodule = $this->precreate_course_module($record->course, $options);
+        $id = quiz_add_instance($record);
+        return $this->post_add_instance($id, $record->coursemodule);
+    }
+}
index eb1f1b5..ec35cc6 100644 (file)
@@ -82,12 +82,6 @@ if (confirm_sesskey() && (!empty($scoid))) {
                     }
                 }
             }
-            // Log every datamodel update requested
-            if (substr($element, 0, 15) == 'adl.nav.request' || substr($element, 0, 3) == 'cmi') {
-                if (scorm_debugging($scorm)) {
-                    add_to_log($course->id, 'scorm', 'trk: scoid/'.$scoid.' at: '.$attempt, 'view.php?id='.$cm->id, "$element => $value", $cm->id);
-                }
-            }
         }
     }
     if ($result) {
index ea1fbb6..028ca08 100644 (file)
@@ -256,4 +256,4 @@ $string['thanksforanswers'] = 'Thanks for answering this survey, {$a}';
 $string['time'] = 'Time';
 $string['viewsurveyresponses'] = 'View {$a} survey responses';
 $string['notyetanswered'] = 'Not yet answered';
-$string['allquestionrequireanswer'] = 'All questions are required and must be answered';
+$string['allquestionrequireanswer'] = 'All questions are required and must be answered.';
index 21675bd..5e1d515 100644 (file)
@@ -86,7 +86,7 @@ $string['configgradedecimals'] = 'Default number of digits that should be shown
 $string['configgradinggrade'] = 'Default maximum grade for assessment in workshops';
 $string['configmaxbytes'] = 'Default maximum submission file size for all workshops on the site (subject to course limits and other local settings)';
 $string['configstrategy'] = 'Default grading strategy for workshops';
-$string['createsubmission'] = 'Submit';
+$string['createsubmission'] = 'Start preparing your submission';
 $string['daysago'] = '{$a} days ago';
 $string['daysleft'] = '{$a} days left';
 $string['daystoday'] = 'today';
index c197ab9..e577b35 100644 (file)
Binary files a/pix/help.png and b/pix/help.png differ
index 0d403ab..ae84f9c 100644 (file)
@@ -9,8 +9,8 @@
         xml:space="preserve">\r
 <defs>\r
 </defs>\r
-<path style="fill:#999999;" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8C16,3.6,12.4,0,8,0z M7.9,13.1\r
-       c-0.8,0-1.5-0.7-1.5-1.5S7,10,7.9,10c0.8,0,1.5,0.7,1.5,1.5S8.7,13.1,7.9,13.1z M9.9,7.5C8.5,8,9.3,9.1,8,9.1C7.4,9.1,7,8.8,7,8.2\r
-       c0-1.2,1.2-1.8,1.2-3c0-0.3-0.1-0.7-0.5-0.7c-0.4,0-0.4,0.4-0.4,0.7C7.2,6.1,6.7,6.6,5.8,6.6c-0.6,0-1-0.4-1-1C4.7,4.5,5.9,3,8.1,3\r
-       c1.4,0,3.4,1,3.4,2.6C11.5,6.5,10.9,7.1,9.9,7.5z"/>\r
+<path style="fill:#999999;" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8C16,3.6,12.4,0,8,0z M8,14c-3.3,0-6-2.7-6-6\r
+       s2.7-6,6-6s6,2.7,6,6S11.3,14,8,14z M5.8,6.6c-0.6,0-1-0.4-1-1C4.7,4.5,5.9,3,8.1,3c1.4,0,3.4,1,3.4,2.6c0,0.8-0.5,1.5-1.5,1.9\r
+       C8.5,8,9.3,9.1,8,9.1C7.4,9.1,7,8.8,7,8.2c0-1.2,1.2-1.8,1.2-3c0-0.3-0.1-0.7-0.5-0.7c-0.4,0-0.4,0.4-0.4,0.7\r
+       C7.2,6.1,6.7,6.6,5.8,6.6z M7.9,13.1c-0.8,0-1.5-0.7-1.5-1.5S7,10,7.9,10c0.8,0,1.5,0.7,1.5,1.5S8.7,13.1,7.9,13.1z"/>\r
 </svg>\r
diff --git a/pix/i/filter.png b/pix/i/filter.png
new file mode 100644 (file)
index 0000000..f3d70ab
Binary files /dev/null and b/pix/i/filter.png differ
diff --git a/pix/i/filter.svg b/pix/i/filter.svg
new file mode 100644 (file)
index 0000000..649e674
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="16px" height="16px" viewBox="-0.1 -0.1 16 16"\r
+        style="overflow:visible;enable-background:new -0.1 -0.1 16 16;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M14.3,0h-3.4c-1.7,0-4.4,0-6,0H1.5c-1.6,0-2,1-0.9,2.1l5.2,5.1c0,0,0,0,0,0v4.2\r
+       c0,0.3,0.2,0.7,0.4,0.9l3.3,3.3c0.2,0.2,0.4,0.1,0.4-0.1v-8c0.1-0.1,0.2-0.1,0.2-0.2l5-5.1C16.3,1,15.9,0,14.3,0z"/>\r
+</svg>\r
diff --git a/pix/i/info.png b/pix/i/info.png
new file mode 100644 (file)
index 0000000..4047841
Binary files /dev/null and b/pix/i/info.png differ
diff --git a/pix/i/info.svg b/pix/i/info.svg
new file mode 100644 (file)
index 0000000..f322a5c
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="16px" height="16px" viewBox="0 0 16 16" style="overflow:visible;enable-background:new 0 0 16 16;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8c4.4,0,8-3.6,8-8C16,3.6,12.4,0,8,0z M8,14c-3.3,0-6-2.7-6-6\r
+       c0-3.3,2.7-6,6-6c3.3,0,6,2.7,6,6C14,11.3,11.3,14,8,14z M9.2,12c0,0.5-0.5,1-1,1H7.8c-0.5,0-1-0.5-1-1V7.4c0-0.5,0.5-1,1-1h0.5\r
+       c0.5,0,1,0.5,1,1V12z M9.2,4.2c0,0.7-0.6,1.2-1.2,1.2c-0.7,0-1.2-0.6-1.2-1.2C6.8,3.5,7.3,3,8,3C8.7,3,9.2,3.5,9.2,4.2z"/>\r
+</svg>\r
diff --git a/pix/i/item.png b/pix/i/item.png
new file mode 100644 (file)
index 0000000..25ce189
Binary files /dev/null and b/pix/i/item.png differ
diff --git a/pix/i/item.svg b/pix/i/item.svg
new file mode 100644 (file)
index 0000000..22132cd
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -3 12 12" style="overflow:visible;enable-background:new -3 -3 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M5,0H1C0.5,0,0,0.5,0,1v4c0,0.5,0.5,1,1,1h4c0.5,0,1-0.5,1-1V1C6,0.5,5.5,0,5,0z M4,3.5\r
+       C4,3.8,3.8,4,3.5,4h-1C2.2,4,2,3.8,2,3.5v-1C2,2.2,2.2,2,2.5,2h1C3.8,2,4,2.2,4,2.5V3.5z"/>\r
+</svg>\r
index 1d110bb..0bc08cc 100644 (file)
Binary files a/pix/i/navigationitem.png and b/pix/i/navigationitem.png differ
diff --git a/pix/i/navigationitem.svg b/pix/i/navigationitem.svg
new file mode 100644 (file)
index 0000000..7fd7231
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -3 12 12" style="overflow:visible;enable-background:new -3 -3 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M6,5c0,0.5-0.5,1-1,1H1C0.5,6,0,5.5,0,5V1c0-0.5,0.5-1,1-1h4c0.5,0,1,0.5,1,1V5z"/>\r
+</svg>\r
index 9ae6838..f52d9b6 100644 (file)
Binary files a/pix/i/outcomes.png and b/pix/i/outcomes.png differ
index 050f749..ac42bc2 100644 (file)
@@ -9,7 +9,7 @@
         xml:space="preserve">\r
 <defs>\r
 </defs>\r
-<path style="fill:#999999;" d="M6.5,9.2l6-2.5c0.6,1.5,0.8,3.1,0,5L6.5,9.2L6.5,9.2z M12.5,11.7C12.5,11.7,12.5,11.7,12.5,11.7\r
-       l-12-5c0,0,0,0,0,0C-0.9,10,0.7,13.8,4,15.2C7.3,16.6,11.1,15,12.5,11.7z M9.8,6l6-2.5C15.1,2,13.9,0.7,12.2,0L9.8,6z M9,3.2\r
-       C5.3,1.7,2,3,0.5,6.7l6,2.5L9,3.2z"/>\r
+<path style="fill:#999999;" d="M12.2,0c1.6,0.7,2.9,2,3.5,3.5L9.8,6L12.2,0z M12.5,6.7l-6,2.5l0,0l0,0l6,2.5c0,0,0,0,0,0h0\r
+       C13.3,9.8,13.1,8.2,12.5,6.7z M12.5,11.7C12.5,11.7,12.5,11.7,12.5,11.7l-12-5c0,0,0,0,0,0C-0.9,10,0.7,13.8,4,15.2\r
+       C7.3,16.6,11.1,15,12.5,11.7z M9,3.2C5.3,1.7,2,3,0.5,6.7l6,2.5L9,3.2z"/>\r
 </svg>\r
index c982c86..e000c67 100644 (file)
Binary files a/pix/t/block_to_dock.png and b/pix/t/block_to_dock.png differ
diff --git a/pix/t/block_to_dock.svg b/pix/t/block_to_dock.svg
new file mode 100644 (file)
index 0000000..46ceadb
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M4,6.6l0.7-0.7c0,0,0,0,0,0l1.8-1.8\r
+       c0.2-0.2,0.5-0.2,0.7,0l0.7,0.7c0.2,0.2,0.2,0.5,0,0.7L6.4,7l1.5,1.5c0.2,0.2,0.2,0.5,0,0.7L7.2,9.9c-0.2,0.2-0.5,0.2-0.7,0L4,7.3\r
+       C3.8,7.1,3.8,6.8,4,6.6z"/>\r
+</svg>\r
diff --git a/pix/t/block_to_dock_rtl.png b/pix/t/block_to_dock_rtl.png
new file mode 100644 (file)
index 0000000..ff1141b
Binary files /dev/null and b/pix/t/block_to_dock_rtl.png differ
diff --git a/pix/t/block_to_dock_rtl.svg b/pix/t/block_to_dock_rtl.svg
new file mode 100644 (file)
index 0000000..9aefc7f
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M8.1,7.4L7.4,8c0,0,0,0,0,0L5.6,9.9\r
+       c-0.2,0.2-0.5,0.2-0.7,0L4.2,9.2C4,9,4,8.6,4.2,8.4L5.6,7L4.2,5.5C4,5.3,4,5,4.2,4.8l0.7-0.7c0.2-0.2,0.5-0.2,0.7,0l2.5,2.5\r
+       C8.3,6.8,8.3,7.2,8.1,7.4z"/>\r
+</svg>\r
index 3d17189..12da3ad 100644 (file)
Binary files a/pix/t/collapsed.png and b/pix/t/collapsed.png differ
diff --git a/pix/t/collapsed.svg b/pix/t/collapsed.svg
new file mode 100644 (file)
index 0000000..e07f426
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -0.1 12 12"\r
+        style="overflow:visible;enable-background:new -3 -0.1 12 12;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M0.7,0.2C0.3-0.2,0,0,0,0.5v10.8c0,0.5,0.3,0.7,0.7,0.3l5-5c0.4-0.4,0.4-1,0-1.4L0.7,0.2z"/>\r
+</svg>\r
index 8c6ee9e..fb07d0d 100644 (file)
Binary files a/pix/t/collapsed_empty.png and b/pix/t/collapsed_empty.png differ
diff --git a/pix/t/collapsed_empty.svg b/pix/t/collapsed_empty.svg
new file mode 100644 (file)
index 0000000..8faab58
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -0.1 12 12"\r
+        style="overflow:visible;enable-background:new -3 -0.1 12 12;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M5.7,5.2l-5-5C0.3-0.2,0,0,0,0.5v10.8c0,0.5,0.3,0.7,0.7,0.3l5-5C6.1,6.2,6.1,5.6,5.7,5.2z M4.3,6.6\r
+       L1.7,9.2C1.3,9.6,1,9.5,1,8.9v-6c0-0.5,0.3-0.7,0.7-0.3l2.6,2.6C4.7,5.6,4.7,6.2,4.3,6.6z"/>\r
+</svg>\r
index ca2f4a9..af665f2 100644 (file)
Binary files a/pix/t/collapsed_empty_rtl.png and b/pix/t/collapsed_empty_rtl.png differ
diff --git a/pix/t/collapsed_empty_rtl.svg b/pix/t/collapsed_empty_rtl.svg
new file mode 100644 (file)
index 0000000..911ab3a
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -0.1 12 12"\r
+        style="overflow:visible;enable-background:new -3 -0.1 12 12;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M0.3,6.6l5,5C5.7,12,6,11.9,6,11.3V0.5C6,0,5.7-0.2,5.3,0.2l-5,5C-0.1,5.6-0.1,6.2,0.3,6.6z M1.7,5.2\r
+       l2.6-2.6C4.7,2.2,5,2.4,5,2.9v6c0,0.5-0.3,0.7-0.7,0.3L1.7,6.6C1.3,6.2,1.3,5.6,1.7,5.2z"/>\r
+</svg>\r
index d848b00..d354c65 100644 (file)
Binary files a/pix/t/collapsed_rtl.png and b/pix/t/collapsed_rtl.png differ
diff --git a/pix/t/collapsed_rtl.svg b/pix/t/collapsed_rtl.svg
new file mode 100644 (file)
index 0000000..76f93a0
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-3 -0.1 12 12"\r
+        style="overflow:visible;enable-background:new -3 -0.1 12 12;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M5.3,0.2C5.7-0.2,6,0,6,0.5v10.8c0,0.5-0.3,0.7-0.7,0.3l-5-5c-0.4-0.4-0.4-1,0-1.4L5.3,0.2z"/>\r
+</svg>\r
index 380f4ac..ff1141b 100644 (file)
Binary files a/pix/t/dock_to_block.png and b/pix/t/dock_to_block.png differ
diff --git a/pix/t/dock_to_block.svg b/pix/t/dock_to_block.svg
new file mode 100644 (file)
index 0000000..9aefc7f
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M8.1,7.4L7.4,8c0,0,0,0,0,0L5.6,9.9\r
+       c-0.2,0.2-0.5,0.2-0.7,0L4.2,9.2C4,9,4,8.6,4.2,8.4L5.6,7L4.2,5.5C4,5.3,4,5,4.2,4.8l0.7-0.7c0.2-0.2,0.5-0.2,0.7,0l2.5,2.5\r
+       C8.3,6.8,8.3,7.2,8.1,7.4z"/>\r
+</svg>\r
diff --git a/pix/t/dock_to_block_rtl.png b/pix/t/dock_to_block_rtl.png
new file mode 100644 (file)
index 0000000..6cb0867
Binary files /dev/null and b/pix/t/dock_to_block_rtl.png differ
diff --git a/pix/t/dock_to_block_rtl.svg b/pix/t/dock_to_block_rtl.svg
new file mode 100644 (file)
index 0000000..46ceadb
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M4,6.6l0.7-0.7c0,0,0,0,0,0l1.8-1.8\r
+       c0.2-0.2,0.5-0.2,0.7,0l0.7,0.7c0.2,0.2,0.2,0.5,0,0.7L6.4,7l1.5,1.5c0.2,0.2,0.2,0.5,0,0.7L7.2,9.9c-0.2,0.2-0.5,0.2-0.7,0L4,7.3\r
+       C3.8,7.1,3.8,6.8,4,6.6z"/>\r
+</svg>\r
index 946c455..198f97b 100644 (file)
Binary files a/pix/t/dockclose.png and b/pix/t/dockclose.png differ
diff --git a/pix/t/dockclose.svg b/pix/t/dockclose.svg
new file mode 100644 (file)
index 0000000..0384103
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M8.8,8.4C9,8.6,9,8.9,8.8,9.1L8.1,9.8\r
+       C7.9,10,7.6,10,7.4,9.8L6,8.4L4.6,9.8C4.4,10,4.1,10,3.9,9.8L3.2,9.1C3,8.9,3,8.6,3.2,8.4L4.6,7L3.2,5.5C3,5.3,3,5,3.2,4.8l0.7-0.7\r
+       c0.2-0.2,0.5-0.2,0.7,0L6,5.6l1.4-1.4C7.6,4,8,4,8.1,4.1l0.7,0.7C9,5,9,5.4,8.9,5.6L7.4,7L8.8,8.4z"/>\r
+</svg>\r
index 59f4ef2..bd7ed3e 100644 (file)
Binary files a/pix/t/expanded.png and b/pix/t/expanded.png differ
diff --git a/pix/t/expanded.svg b/pix/t/expanded.svg
new file mode 100644 (file)
index 0000000..6e96c04
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="-0.1 -3 12 12"\r
+        style="overflow:visible;enable-background:new -0.1 -3 12 12;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M0.5,0C0,0-0.2,0.3,0.2,0.7l5,5c0.4,0.4,1,0.4,1.4,0l5-5C12,0.3,11.9,0,11.3,0H0.5z"/>\r
+</svg>\r
diff --git a/pix/t/switch_minus.png b/pix/t/switch_minus.png
new file mode 100644 (file)
index 0000000..c72d81b
Binary files /dev/null and b/pix/t/switch_minus.png differ
diff --git a/pix/t/switch_minus.svg b/pix/t/switch_minus.svg
new file mode 100644 (file)
index 0000000..36e8f0d
--- /dev/null
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M9,7.5C9,7.8,8.8,8,8.5,8h-5C3.2,8,3,7.8,3,7.5v-1\r
+       C3,6.2,3.2,6,3.5,6h5C8.8,6,9,6.2,9,6.5V7.5z"/>\r
+</svg>\r
diff --git a/pix/t/switch_plus.png b/pix/t/switch_plus.png
new file mode 100644 (file)
index 0000000..42633ee
Binary files /dev/null and b/pix/t/switch_plus.png differ
diff --git a/pix/t/switch_plus.svg b/pix/t/switch_plus.svg
new file mode 100644 (file)
index 0000000..a22931b
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In  -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+       <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+        xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+        x="0px" y="0px" width="12px" height="12px" viewBox="0 0 12 12" style="overflow:visible;enable-background:new 0 0 12 12;"\r
+        xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M11,0H1C0.5,0,0,0.5,0,1v10c0,0.5,0.5,1,1,1h10c0.5,0,1-0.5,1-1V1C12,0.5,11.5,0,11,0z M11,10\r
+       c0,0.5-0.5,1-1,1H2c-0.5,0-1-0.5-1-1V4c0-0.5,0.5-1,1-1h8c0.5,0,1,0.5,1,1V10z M9,6.5v1C9,7.8,8.8,8,8.5,8H7v1.5\r
+       C7,9.8,6.8,10,6.5,10h-1C5.2,10,5,9.8,5,9.5V8H3.5C3.2,8,3,7.8,3,7.5v-1C3,6.2,3.2,6,3.5,6H5V4.5C5,4.2,5.2,4,5.5,4h1\r
+       C6.8,4,7,4.2,7,4.5V6h1.5C8.8,6,9,6.2,9,6.5z"/>\r
+</svg>\r
index 71cf131..f1d9533 100644 (file)
@@ -137,7 +137,7 @@ abstract class user_selector_base {
      * Clear the list of excluded user ids.
      */
     public function clear_exclusions() {
-        $exclude = array();
+        $this->exclude = array();
     }
 
     /**
index 7ed60eb..36fd328 100644 (file)
@@ -30,7 +30,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012110900.00;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012111200.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes