Merge branch 'MDL-36417-master' of git://github.com/FMCorz/moodle
authorDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 02:39:08 +0000 (10:39 +0800)
committerDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 02:39:08 +0000 (10:39 +0800)
180 files changed:
admin/courseformats.php [new file with mode: 0644]
admin/settings/courses.php
admin/settings/plugins.php
backup/moodle2/backup_stepslib.php
blocks/activity_modules/block_activity_modules.php
blocks/community/block_community.php
blocks/course_list/block_course_list.php
blocks/dock.js
blocks/navigation/styles.css
blocks/participants/block_participants.php
blocks/private_files/renderer.php
blocks/settings/styles.css
blocks/site_main_menu/block_site_main_menu.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
calendar/managesubscriptions_form.php
cohort/assign.php
course/dndupload.js
course/edit_form.php
course/format/formatlegacy.php
course/format/lib.php
course/format/topics/styles.css
course/format/upgrade.txt
course/format/weeks/styles.css
course/index.php
course/lib.php
course/renderer.php
course/yui/dragdrop/dragdrop.js
course/yui/toolboxes/toolboxes.js
enrol/renderer.php
grade/lib.php
lang/en/admin.php
lang/en/error.php
lang/en/moodle.php
lib/adminlib.php
lib/blocklib.php
lib/csslib.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/enrollib.php
lib/googleapi.php
lib/installlib.php
lib/javascript-static.js
lib/jslib.php
lib/messagelib.php
lib/minify/lib/JSMin.php [deleted file]
lib/minify/readme_moodle.txt
lib/modinfolib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.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
lib/yui/dragdrop/dragdrop.js
mod/assign/gradingtable.php
mod/chat/lib.php
mod/chat/upgrade.txt [new file with mode: 0644]
mod/lesson/essay.php
mod/lti/tests/generator/lib.php [new file with mode: 0644]
mod/lti/tests/generator_test.php [new file with mode: 0644]
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/url/lib.php
mod/url/locallib.php
mod/workshop/lang/en/workshop.php
pix/help.png
pix/help.svg
pix/i/assignroles.png [new file with mode: 0644]
pix/i/assignroles.svg [new file with mode: 0644]
pix/i/dragdrop.png [new file with mode: 0644]
pix/i/dragdrop.svg [new file with mode: 0644]
pix/i/export.png [new file with mode: 0644]
pix/i/export.svg [new file with mode: 0644]
pix/i/filter.png [new file with mode: 0644]
pix/i/filter.svg [new file with mode: 0644]
pix/i/import.png [new file with mode: 0644]
pix/i/import.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/i/switchrole.png [new file with mode: 0644]
pix/i/switchrole.svg [new file with mode: 0644]
pix/t/assignroles.png [moved from pix/i/roles.png with 100% similarity]
pix/t/assignroles.svg [moved from pix/i/roles.svg with 100% similarity]
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]
theme/afterburner/config.php
theme/afterburner/layout/default.php
theme/anomaly/config.php
theme/anomaly/layout/general.php
theme/anomaly/layout/report.php
theme/arialist/config.php
theme/arialist/layout/general.php
theme/arialist/layout/report.php
theme/base/config.php
theme/base/layout/general.php
theme/base/layout/report.php
theme/base/style/blocks.css
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/dock.css
theme/binarius/config.php
theme/binarius/layout/general.php
theme/binarius/layout/report.php
theme/boxxie/config.php
theme/boxxie/layout/general.php
theme/brick/config.php
theme/brick/layout/general.php
theme/canvas/config.php
theme/canvas/layout/general.php
theme/canvas/layout/report.php
theme/standard/style/blocks.css
theme/standard/style/core.css
theme/standard/style/course.css
theme/standard/style/dock.css
theme/upgrade.txt
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 0a391b0..7383658 100644 (file)
@@ -53,10 +53,10 @@ class block_activity_modules extends block_list {
 
         foreach ($modfullnames as $modname => $modfullname) {
             if ($modname === 'resources') {
-                $icon = $OUTPUT->pix_icon(file_extension_icon('.htm'), '', 'moodle', array('class' => 'icon')). '&nbsp;';
+                $icon = $OUTPUT->pix_icon('icon', '', 'mod_page', array('class' => 'icon'));
                 $this->content->items[] = '<a href="'.$CFG->wwwroot.'/course/resources.php?id='.$course->id.'">'.$icon.$modfullname.'</a>';
             } else {
-                $icon = '<img src="'.$OUTPUT->pix_url('icon', $modname) . '" class="icon" alt="" />&nbsp;';
+                $icon = '<img src="'.$OUTPUT->pix_url('icon', $modname) . '" class="icon" alt="" />';
                 $this->content->items[] = '<a href="'.$CFG->wwwroot.'/mod/'.$modname.'/index.php?id='.$course->id.'">'.$icon.$modfullname.'</a>';
             }
         }
index c211342..3702f11 100644 (file)
@@ -72,7 +72,7 @@ class block_community extends block_list {
                     'class' => 'icon', 'alt' => get_string('addcourse', 'block_community')));
         $addcourseurl = new moodle_url('/blocks/community/communitycourse.php',
                         array('add' => true, 'courseid' => $this->page->course->id));
-        $searchlink = html_writer::tag('a', $icon . '&nbsp;' . get_string('addcourse', 'block_community'),
+        $searchlink = html_writer::tag('a', $icon . get_string('addcourse', 'block_community'),
                         array('href' => $addcourseurl->out(false)));
         $this->content->items[] = $searchlink;
 
index 2e6fa7c..540c49a 100644 (file)
@@ -23,7 +23,7 @@ class block_course_list extends block_list {
         $this->content->icons = array();
         $this->content->footer = '';
 
-        $icon  = '<img src="' . $OUTPUT->pix_url('i/course') . '" class="icon" alt="" />&nbsp;';
+        $icon  = '<img src="' . $OUTPUT->pix_url('i/course') . '" class="icon" alt="" />';
 
         $adminseesall = true;
         if (isset($CFG->block_course_list_adminview)) {
@@ -109,7 +109,7 @@ class block_course_list extends block_list {
             return;
         }
 
-        $icon = '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="" />&nbsp;';
+        $icon = '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="" />';
 
         // shortcut - the rest is only for logged in users!
         if (!isloggedin() || isguestuser()) {
index 34a859d..ca685d5 100644 (file)
@@ -532,10 +532,16 @@ M.core_dock.fixTitleOrientation = function(item, title, text) {
         'position' : 'relative',
         'fontSize' : fontsize,
         'width' : width,
-        'top' : width/2,
-        'right' : width/2 - height
+        'top' : width/2
     });
 
+    // Positioning is different when in RTL mode.
+    if (Y.one(document.body).hasClass('dir-rtl')) {
+        title.setStyle('left', width/2 - height);
+    } else {
+        title.setStyle('right', width/2 - height);
+    }
+
     // Rotate the text
     title.setStyles({
         'transform' : transform,
@@ -922,7 +928,7 @@ M.core_dock.genericblock.prototype = {
             }, this);
             // Add a close icon
             // Must set the image src seperatly of we get an error with XML strict headers
-            var closeicon = Y.Node.create('<span class="hidepanelicon" tabindex="0"><img alt="" style="width:11px;height:11px;cursor:pointer;" /></span>');
+            var closeicon = Y.Node.create('<span class="hidepanelicon" tabindex="0"><img alt="" /></span>');
             closeicon.one('img').setAttribute('src', M.util.image_url('t/dockclose', 'moodle'));
             closeicon.on('forceclose|click', this.hide, this);
             closeicon.on('dock:actionkey',this.hide, this, {actions:{enter:true,toggle:true}});
index 6da9053..6d08f5e 100644 (file)
@@ -1,19 +1,23 @@
 /** General display rules **/
 .block_navigation .block_tree {margin:5px;padding-left:0px;overflow:visible;}
 .block_navigation .block_tree li {margin:3px;list-style: none;padding:0;}
-.block_navigation .block_tree li.item_with_icon > p {position:relative;}
-.block_navigation .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:3px}
+.block_navigation .block_tree li.item_with_icon > p {position:relative; padding-left: 21px;}
+.block_navigation .block_tree li.item_with_icon > p img,
+.block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
+.block_navigation .block_tree li > p.hasicon img {vertical-align:middle;position:absolute;left:0;top:-1px;width:16px;height:16px;}
 .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left:16px;}
-.block_navigation .block_tree li.item_with_icon.contains_branch .tree_item {padding-left:34px;}
+.block_navigation .block_tree .type_activity > p.branch.hasicon,
+.block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-left:37px;}
 
 .block_navigation .block_tree li ul {padding-left:0;margin:0;}
 .block_navigation .block_tree li.depth_2 ul {padding-left:16px;margin:0;}
-.block_navigation .block_tree .tree_item {padding-left: 18px;margin:3px 0px;text-align:left;}
+.block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
+.block_navigation .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
-.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
+.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 0;background-repeat: no-repeat;}
 .block_navigation .block_tree .tree_item.branch.navigation_node {background-image:none;padding-left:0;}
 .block_navigation .block_tree .type_activity > .tree_item.branch {background-image:none;position:relative;}
-.block_navigation .block_tree .type_activity > .tree_item.branch img {position:absolute;left:0;}
+.block_navigation .block_tree .type_activity > .tree_item.branch img {left: 16px;}
 .block_navigation .block_tree .root_node.leaf {padding-left:0px;}
 .block_navigation .block_tree .active_tree_node {font-weight:bold;}
 .block_navigation .block_tree .depth_1.current_branch ul {font-weight:normal;}
@@ -21,8 +25,9 @@
 .dock .block_navigation .tree_item {white-space: nowrap;}
 
 .jsenabled .block_navigation .block_tree .tree_item.branch {cursor:pointer;}
-.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0% 5%;background-repeat: no-repeat;}
+.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 0;background-repeat: no-repeat;}
 .jsenabled .block_navigation .block_tree .collapsed ul {display: none;}
+.jsenabled .block_navigation .block_tree .type_activity > .tree_item.branch {background-image: url([[pix:t/expanded]]);}
 .jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
 .jsenabled .block_navigation .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
 
@@ -35,8 +40,9 @@
 .ie6 .block_navigation .block_tree .tree_item {width:100%;}
 
 /** Overide for RTL layout **/
-.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left:0;padding-right: 7px;}
-.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 18px;text-align:right;}
+.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left:0;padding-right: 16px; padding-left: 0;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
+.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 21px;text-align:right;}
 
 .dir-rtl .block_navigation .block_tree .tree_item.branch {background-position: center right;}
 
 .dir-rtl .block_navigation .block_tree .root_node.leaf {padding-right:0;}
 
 .dir-rtl .block_navigation .block_tree li.item_with_icon > p img,
-.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right:0;left:auto;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
+.dir-rtl .block_navigation .block_tree li > p.hasicon img {left:auto; right:0;}
+.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left: auto; right:16px;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.branch.hasicon,
+.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-right:37px; padding-left: 0;}
+.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right: 16px; left: auto;}
 
 .jsenabled.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
 .jsenabled.dir-rtl .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
index c46a5aa..82889f8 100644 (file)
@@ -37,7 +37,7 @@ class block_participants extends block_list {
             }
         }
 
-        $icon = '<img src="'.$OUTPUT->pix_url('i/users') . '" class="icon" alt="" />&nbsp;';
+        $icon = '<img src="'.$OUTPUT->pix_url('i/users') . '" class="icon" alt="" />';
         $this->content->items[] = '<a title="'.get_string('listofallpeople').'" href="'.
                                   $CFG->wwwroot.'/user/index.php?contextid='.$currentcontext->id.'">'.$icon.get_string('participants').'</a>';
 
index e931219..f84d2d7 100644 (file)
@@ -66,13 +66,13 @@ class block_private_files_renderer extends plugin_renderer_base {
         $result = '<ul>';
         foreach ($dir['subdirs'] as $subdir) {
             $image = $this->output->pix_icon(file_folder_icon(), $subdir['dirname'], 'moodle', array('class'=>'icon'));
-            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.$image.' '.s($subdir['dirname']).'</div> '.$this->htmllize_tree($tree, $subdir).'</li>';
+            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.$image.s($subdir['dirname']).'</div> '.$this->htmllize_tree($tree, $subdir).'</li>';
         }
         foreach ($dir['files'] as $file) {
             $url = file_encode_url("$CFG->wwwroot/pluginfile.php", '/'.$tree->context->id.'/user/private'.$file->get_filepath().$file->get_filename(), true);
             $filename = $file->get_filename();
             $image = $this->output->pix_icon(file_file_icon($file), $filename, 'moodle', array('class'=>'icon'));
-            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.html_writer::link($url, $image.'&nbsp;'.$filename).'</div></li>';
+            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.html_writer::link($url, $image.$filename).'</div></li>';
         }
         $result .= '</ul>';
 
index cfa9ab8..2c6e451 100644 (file)
@@ -6,12 +6,12 @@
 /** General display rules **/
 .block_settings .block_tree {margin:5px;padding-left:0px;overflow:visible;}
 .block_settings .block_tree li {margin:0;list-style: none;}
-.block_settings .block_tree li ul {padding-left:16px;margin:0;}
+.block_settings .block_tree li ul {padding-left:18px;margin:0;}
 
 .block_settings .block_tree li.item_with_icon > p {position:relative;}
-.block_settings .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:-1px}
+.block_settings .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:-1px; width: 16px; height: 16px;}
 
-.block_settings .block_tree .tree_item {padding-left: 18px;margin:3px 0px;text-align:left;}
+.block_settings .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
 .block_settings .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
 .block_settings .block_tree .root_node.leaf {padding-left:0px;}
 
 /** Overide for RTL layout **/
 .dir-rtl .block_settings .block_tree {padding-right:0px;}
-.dir-rtl .block_settings .block_tree li ul {padding-left:0;padding-right: 7px;}
-.dir-rtl .block_settings .block_tree li.item_with_icon > p img,
-.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {left:auto;right:0;}
-.dir-rtl .block_settings .block_tree .tree_item {padding-right: 18px;text-align:right;}
+.dir-rtl .block_settings .block_tree li ul {padding-left:0;padding-right: 18px;}
+.dir-rtl .block_settings .block_tree .tree_item {padding-right: 21px; padding-left: 0; text-align:right;}
 .dir-rtl .block_settings .block_tree .tree_item.branch {background-position: center right;}
 .dir-rtl .block_settings .block_tree .root_node.leaf {padding-right:0px;}
+.dir-rtl .block_settings .block_tree li.item_with_icon > p img { right: 0; left: auto;}
 
 .jsenabled.dir-rtl .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
-.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
\ No newline at end of file
+.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
index 39a550e..d4374bc 100644 (file)
@@ -50,7 +50,7 @@ class block_site_main_menu extends block_list {
                     } else {
                         $linkcss = $cm->visible ? '' : ' class="dimmed" ';
                         //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
+                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
                         $this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
                                 ' href="' . $url . '">' . $icon . $instancename . '</a>';
                     }
@@ -120,7 +120,7 @@ class block_site_main_menu extends block_list {
                         $this->content->icons[] = '';
                     } else {
                         //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
+                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
                         $this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
                             ' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
                     }
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..d44dc8c 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;
     }
 
@@ -264,7 +264,7 @@ class cachestore_dummy implements cache_store {
     public static function initialise_test_instance(cache_definition $definition) {
         $cache = new cachestore_dummy('Dummy store test');
         $cache->initialise($definition);
-        return $cache;;
+        return $cache;
     }
 
     /**
@@ -274,4 +274,4 @@ class cachestore_dummy implements cache_store {
     public function my_name() {
         return $this->name;
     }
-}
\ No newline at end of file
+}
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..1931bdb 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;
     }
 
@@ -239,7 +239,7 @@ class cachestore_mongodb implements cache_store {
      * @return bool
      */
     public function supports_native_ttl() {
-        return false;;
+        return false;
     }
 
     /**
@@ -517,4 +517,4 @@ class cachestore_mongodb implements cache_store {
     public function my_name() {
         return $this->name;
     }
-}
\ No newline at end of file
+}
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..7a7bd5a 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;
     }
 
@@ -360,7 +360,7 @@ class cachestore_static extends static_data_store implements cache_store, cache_
         // Do something here perhaps.
         $cache = new cachestore_static('Static store');
         $cache->initialise($definition);
-        return $cache;;
+        return $cache;
     }
 
     /**
@@ -422,4 +422,4 @@ abstract class static_data_store {
             self::$staticstore[$id] = array();
         }
     }
-}
\ No newline at end of file
+}
index 12c3135..50a0903 100644 (file)
@@ -58,7 +58,8 @@ class calendar_addsubscription_form extends moodleform {
 
         // URL.
         $mform->addElement('text', 'url', get_string('importfromurl', 'calendar'), array('maxsize' => '255', 'size' => '50'));
-        $mform->setType('url', PARAM_URL);
+        // Cannot set as PARAM_URL since we need to allow webcal:// protocol.
+        $mform->setType('url', PARAM_RAW);
 
         // Import file
         $mform->addElement('filepicker', 'importfile', get_string('importfromfile', 'calendar'));
@@ -103,12 +104,21 @@ class calendar_addsubscription_form extends moodleform {
      */
     public function validation($data, $files) {
         $errors = parent::validation($data, $files);
-        if (empty($data['url']) && empty($data['importfile'])) {
+        $url = $data['url'];
+        if (empty($url) && empty($data['importfile'])) {
             if (!empty($data['importfrom']) && $data['importfrom'] == CALENDAR_IMPORT_FROM_FILE) {
                 $errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
             } else {
                 $errors['url'] = get_string('errorrequiredurlorfile', 'calendar');
             }
+        } elseif (!empty($url)) {
+            // Url is webcal protocol which is not accepted by PARAM_URL.
+            if (stripos($url, "webcal://") === 0) {
+                $url = substr($url, strlen("webcal://"));
+            }
+            if (clean_param($url, PARAM_URL) !== $url) {
+                $errors['url']  = get_string('invalidurl', 'error');
+            }
         }
         return $errors;
     }
index 0d6c83a..2da394d 100644 (file)
@@ -68,8 +68,8 @@ echo $OUTPUT->heading(get_string('assignto', 'cohort', format_string($cohort->na
 echo $OUTPUT->notification(get_string('removeuserwarning', 'core_cohort'));
 
 // Get the user_selector we will need.
-$potentialuserselector = new cohort_candidate_selector('addselect', array('cohortid'=>$cohort->id));
-$existinguserselector = new cohort_existing_selector('removeselect', array('cohortid'=>$cohort->id));
+$potentialuserselector = new cohort_candidate_selector('addselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
+$existinguserselector = new cohort_existing_selector('removeselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
 
 // Process incoming user assignments to the cohort
 
index 2dcaa13..e1d1b11 100644 (file)
@@ -524,6 +524,7 @@ M.course_dndupload = {
         preview.li.appendChild(preview.div);
 
         preview.icon.src = M.util.image_url('t/addfile');
+        preview.icon.className = 'icon';
         preview.div.appendChild(preview.icon);
 
         preview.div.appendChild(document.createTextNode(' '));
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 234f2e4..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;
     }
 
@@ -785,6 +793,73 @@ abstract class format_base {
      */
     public function page_set_cm(moodle_page $page) {
     }
+
+    /**
+     * Course-specific information to be output on any course page (usually above navigation bar)
+     *
+     * Example of usage:
+     * define
+     * class format_FORMATNAME_XXX implements renderable {}
+     *
+     * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:
+     * class format_FORMATNAME_renderer extends plugin_renderer_base {
+     *     protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {
+     *         return html_writer::tag('div', 'This is my header/footer');
+     *     }
+     * }
+     *
+     * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from
+     * plugin renderer will be called
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_header() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output on any course page (usually in the beginning of
+     * standard footer)
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_footer() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output immediately above content on any course page
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_content_header() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output immediately below content on any course page
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_content_footer() {
+        return null;
+    }
+
+    /**
+     * Returns instance of page renderer used by this plugin
+     *
+     * @param moodle_page $page
+     * @return renderer_base
+     */
+    public function get_renderer(moodle_page $page) {
+        return $page->get_renderer('format_'. $this->get_format());
+    }
 }
 
 /**
index 7f0426e..3460a0a 100644 (file)
@@ -1,6 +1,7 @@
 .course-content ul.topics {margin:0;}
 .course-content ul.topics li.section {list-style: none;margin:5px 0 0 0;padding:0;}
 .course-content ul.topics li.section .content {margin:0 40px;}
-.course-content ul.topics li.section .left {width:40px;float:left;text-align:center;}
+.course-content ul.topics li.section .left {width:40px;float:left;text-align:center;padding-top: 4px;}
 .course-content ul.topics li.section .right {width:40px;float:right;text-align:center;padding-top: 4px;}
-.jumpmenu {text-align:center;}
\ No newline at end of file
+.course-content ul.topics li.section .left .section-handle img.icon { padding:0; vertical-align: baseline; }
+.jumpmenu {text-align:center;}
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 fc87f3c..17cf4cd 100644 (file)
@@ -1,6 +1,7 @@
 .course-content ul.weeks {margin:0;}
 .course-content ul.weeks li.section {list-style: none;margin:5px 0 0 0;padding:0;}
 .course-content ul.weeks li.section .content {margin:0 40px;}
-.course-content ul.weeks li.section .left {width:40px;float:left;text-align:center;}
+.course-content ul.weeks li.section .left {width:40px;float:left;text-align:center;padding-top: 4px;}
 .course-content ul.weeks li.section .right {width:40px;float:right;text-align:center;padding-top: 4px;}
+.course-content ul.weeks li.section .left .section-handle img.icon { padding:0; vertical-align: baseline; }
 .jumpmenu {text-align:center;}
\ No newline at end of file
index 66435ef..209e280 100644 (file)
@@ -254,7 +254,7 @@ $parentlist = array();
 $displaylist[0] = get_string('top');
 make_categories_list($displaylist, $parentlist);
 
-echo '<table class="generalbox editcourse boxaligncenter"><tr class="header">';
+echo '<table class="generaltable editcourse boxaligncenter"><tr class="header">';
 echo '<th class="header" scope="col">'.$strcategories.'</th>';
 echo '<th class="header" scope="col">'.$strcourses.'</th>';
 echo '<th class="header" scope="col">'.$stredit.'</th>';
index fc53eaf..8626b42 100644 (file)
@@ -1498,9 +1498,9 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     $accesstext = '';
                 }
                 if ($linkclasses) {
-                    $linkcss = 'class="' . trim($linkclasses) . '" ';
+                    $linkcss = 'class="activityinstance ' . trim($linkclasses) . '" ';
                 } else {
-                    $linkcss = '';
+                    $linkcss = 'class="activityinstance"';
                 }
                 if ($textclasses) {
                     $textcss = 'class="' . trim($textclasses) . '" ';
@@ -1518,7 +1518,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     // Display link itself
                     echo '<a ' . $linkcss . $mod->extra . $onclick .
                             ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
-                            '" class="activityicon" alt="' . $mod->modfullname . '" /> ' .
+                            '" class="iconlarge activityicon" alt="' . $mod->modfullname . '" />' .
                             $accesstext . '<span class="instancename">' .
                             $instancename . $altname . '</span></a>';
 
@@ -1586,7 +1586,6 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                 } else {
                     $mod->groupmode = false;
                 }
-                echo '&nbsp;&nbsp;';
                 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
                 echo $mod->get_after_edit_icons();
             }
@@ -3332,7 +3331,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     if (has_capability('moodle/role:assign', $modcontext)){
         $actions[] = new action_link(
             new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
-            new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
             array('class' => 'editing_assign', 'title' => $str->assign)
         );
@@ -4528,6 +4527,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 069dbcb..96b8f1b 100644 (file)
@@ -87,8 +87,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
 
                     if ((movedown || moveup) && cssleft) {
                         cssleft.setStyle('cursor', 'move');
-                        cssleft.appendChild(Y.Node.create('<br />'));
-                        cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE));
+                        cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
 
                         if (moveup) {
                             moveup.remove();
@@ -267,7 +266,8 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                 });
                 del.dd.plug(Y.Plugin.DDProxy, {
                     // Don't move the node at the end of the drag
-                    moveOnEnd: false
+                    moveOnEnd: false,
+                    cloneNode: true
                 });
                 del.dd.plug(Y.Plugin.DDConstrained, {
                     // Keep it inside the .course-content
index b3eb066..eca0983 100644 (file)
@@ -488,6 +488,7 @@ YUI.add('moodle-course-toolboxes', function(Y) {
         edit_resource_title : function(e) {
             // Get the element we're working on
             var element = e.target.ancestor(CSS.ACTIVITYLI);
+            var elementdiv = element.one('div');
             var instancename  = element.one(CSS.INSTANCENAME);
             var currenttitle = instancename.get('firstChild');
             var oldtitle = currenttitle.get('data');
@@ -522,19 +523,19 @@ YUI.add('moodle-course-toolboxes', function(Y) {
                 })
                 .addClass('titleeditor');
             var editform = Y.Node.create('<form />')
-                .setStyle('padding', '0')
-                .setStyle('display', 'inline')
+                .addClass('activityinstance')
                 .setAttribute('action', '#');
-
             var editinstructions = Y.Node.create('<span />')
                 .addClass('editinstructions')
                 .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+            var activityicon = element.one('img.activityicon').cloneNode();
 
             // Clear the existing content and put the editor in
             currenttitle.set('data', '');
+            editform.appendChild(activityicon);
             editform.appendChild(editor);
             anchor.replace(editform);
-            element.appendChild(editinstructions);
+            elementdiv.appendChild(editinstructions);
             e.preventDefault();
 
             // Focus and select the editor text
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 67e9f70..645d73a 100644 (file)
@@ -2300,14 +2300,14 @@ function grade_extend_settings($plugininfo, $courseid) {
     if ($imports = grade_helper::get_plugins_import($courseid)) {
         $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
         foreach ($imports as $import) {
-            $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/restore', ''));
+            $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', ''));
         }
     }
 
     if ($exports = grade_helper::get_plugins_export($courseid)) {
         $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
         foreach ($exports as $export) {
-            $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/backup', ''));
+            $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', ''));
         }
     }
 
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 c28c919..e566933 100644 (file)
@@ -1067,7 +1067,7 @@ class block_manager {
 
             $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin .
                     '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($return),
-                    'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role'), 'class' => 'editing_roles');
+                    'icon' => 't/assignroles', 'caption' => get_string('assignroles', 'role'), 'class' => 'editing_roles');
         }
 
         return $controls;
index d160359..548f854 100644 (file)
@@ -251,6 +251,8 @@ function css_minify_css($files) {
     Minify::setCache(null, false);
 
     $options = array(
+        // JSMin is not GNU GPL compatible, use the plus version instead.
+        'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
         'bubbleCssImports' => false,
         // Don't gzip content we just want text for storage
         'encodeOutput' => false,
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 a045d77..835a07a 100644 (file)
@@ -444,7 +444,7 @@ function enrol_add_course_navigation(navigation_node $coursenode, $course) {
         if ($course->id == SITEID or (!empty($CFG->adminsassignrolesincourse) and is_siteadmin())) {
             if (has_capability('moodle/role:assign', $coursecontext)) {
                 $url = new moodle_url('/admin/roles/assign.php', array('contextid'=>$coursecontext->id));
-                $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
+                $permissionsnode->add(get_string('assignedroles', 'role'), $url, navigation_node::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
             }
         }
         // Check role permissions
@@ -459,7 +459,7 @@ function enrol_add_course_navigation(navigation_node $coursenode, $course) {
         //TODO, create some new UI for role assignments at course level
         if (has_capability('moodle/role:assign', $coursecontext)) {
             $url = new moodle_url('/enrol/otherusers.php', array('id'=>$course->id));
-            $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/roles', ''));
+            $usersnode->add(get_string('notenrolledusers', 'enrol'), $url, navigation_node::TYPE_SETTING, null, 'otherusers', new pix_icon('i/assignroles', ''));
         }
     }
 
index 3ace654..f9fe137 100644 (file)
@@ -317,11 +317,14 @@ class google_picasa {
      * @return mixes $files Array in the format get_listing uses for folders
      */
     public function get_albums() {
+        $files = array();
         $content = $this->googleoauth->get(self::LIST_ALBUMS_URL);
-        $xml = new SimpleXMLElement($content);
 
-        $files = array();
+        if (empty($content)) {
+            return $files;
+        }
 
+        $xml = new SimpleXMLElement($content);
         foreach ($xml->entry as $album) {
             $gphoto = $album->children('http://schemas.google.com/photos/2007');
 
@@ -338,7 +341,6 @@ class google_picasa {
                 'thumbnail_height' => 160,
                 'children' => array(),
             );
-
         }
 
         return $files;
@@ -352,12 +354,14 @@ class google_picasa {
      * @return mixed $files A list of files for the file picker
      */
     public function get_photo_details($rawxml) {
+        $files = array();
+        if (empty($rawxml)) {
+            return $files;
+        }
 
         $xml = new SimpleXMLElement($rawxml);
         $this->lastalbumname = (string)$xml->title;
 
-        $files = array();
-
         foreach ($xml->entry as $photo) {
             $gphoto = $photo->children('http://schemas.google.com/photos/2007');
 
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 077693d..fa0dfc0 100644 (file)
@@ -871,7 +871,7 @@ M.util.add_lightbox = function(Y, node) {
         'top' : 0,
         'left' : 0,
         'backgroundColor' : 'white',
-        'text-align' : 'center'
+        'textAlign' : 'center'
     })
     .setAttribute('class', 'lightbox')
     .hide();
index ee905f1..1d20bbd 100644 (file)
@@ -114,6 +114,8 @@ function js_minify($files) {
     Minify::setCache(null, false);
 
     $options = array(
+        // JSMin is not GNU GPL compatible, use the plus version instead.
+        'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
         'bubbleCssImports' => false,
         // Don't gzip content we just want text for storage
         'encodeOutput' => false,
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;
         }
     }
 
diff --git a/lib/minify/lib/JSMin.php b/lib/minify/lib/JSMin.php
deleted file mode 100644 (file)
index b6879f3..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-<?php
-/**
- * JSMin.php - modified PHP implementation of Douglas Crockford's JSMin.
- *
- * <code>
- * $minifiedJs = JSMin::minify($js);
- * </code>
- *
- * This is a modified port of jsmin.c. Improvements:
- * 
- * Does not choke on some regexp literals containing quote characters. E.g. /'/
- * 
- * Spaces are preserved after some add/sub operators, so they are not mistakenly 
- * converted to post-inc/dec. E.g. a + ++b -> a+ ++b
- *
- * Preserves multi-line comments that begin with /*!
- * 
- * PHP 5 or higher is required.
- *
- * Permission is hereby granted to use this version of the library under the
- * same terms as jsmin.c, which has the following license:
- *
- * --
- * Copyright (c) 2002 Douglas Crockford  (www.crockford.com)
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy of
- * this software and associated documentation files (the "Software"), to deal in
- * the Software without restriction, including without limitation the rights to
- * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
- * of the Software, and to permit persons to whom the Software is furnished to do
- * so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * The Software shall be used for Good, not Evil.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- * --
- *
- * @package JSMin
- * @author Ryan Grove <ryan@wonko.com> (PHP port)
- * @author Steve Clay <steve@mrclay.org> (modifications + cleanup)
- * @author Andrea Giammarchi <http://www.3site.eu> (spaceBeforeRegExp)
- * @copyright 2002 Douglas Crockford <douglas@crockford.com> (jsmin.c)
- * @copyright 2008 Ryan Grove <ryan@wonko.com> (PHP port)
- * @license http://opensource.org/licenses/mit-license.php MIT License
- * @link http://code.google.com/p/jsmin-php/
- */
-
-class JSMin {
-    const ORD_LF            = 10;
-    const ORD_SPACE         = 32;
-    const ACTION_KEEP_A     = 1;
-    const ACTION_DELETE_A   = 2;
-    const ACTION_DELETE_A_B = 3;
-
-    protected $a           = "\n";
-    protected $b           = '';
-    protected $input       = '';
-    protected $inputIndex  = 0;
-    protected $inputLength = 0;
-    protected $lookAhead   = null;
-    protected $output      = '';
-    protected $lastByteOut  = '';
-
-    /**
-     * Minify Javascript.
-     *
-     * @param string $js Javascript to be minified
-     *
-     * @return string
-     */
-    public static function minify($js)
-    {
-        $jsmin = new JSMin($js);
-        return $jsmin->min();
-    }
-
-    /**
-     * @param string $input
-     */
-    public function __construct($input)
-    {
-        $this->input = $input;
-    }
-
-    /**
-     * Perform minification, return result
-     *
-     * @return string
-     */
-    public function min()
-    {
-        if ($this->output !== '') { // min already run
-            return $this->output;
-        }
-
-        $mbIntEnc = null;
-        if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) {
-            $mbIntEnc = mb_internal_encoding();
-            mb_internal_encoding('8bit');
-        }
-        $this->input = str_replace("\r\n", "\n", $this->input);
-        $this->inputLength = strlen($this->input);
-
-        $this->action(self::ACTION_DELETE_A_B);
-
-        while ($this->a !== null) {
-            // determine next command
-            $command = self::ACTION_KEEP_A; // default
-            if ($this->a === ' ') {
-                if (($this->lastByteOut === '+' || $this->lastByteOut === '-') 
-                    && ($this->b === $this->lastByteOut)) {
-                    // Don't delete this space. If we do, the addition/subtraction
-                    // could be parsed as a post-increment
-                } elseif (! $this->isAlphaNum($this->b)) {
-                    $command = self::ACTION_DELETE_A;
-                }
-            } elseif ($this->a === "\n") {
-                if ($this->b === ' ') {
-                    $command = self::ACTION_DELETE_A_B;
-                // in case of mbstring.func_overload & 2, must check for null b,
-                // otherwise mb_strpos will give WARNING
-                } elseif ($this->b === null
-                          || (false === strpos('{[(+-', $this->b)
-                              && ! $this->isAlphaNum($this->b))) {
-                    $command = self::ACTION_DELETE_A;
-                }
-            } elseif (! $this->isAlphaNum($this->a)) {
-                if ($this->b === ' '
-                    || ($this->b === "\n" 
-                        && (false === strpos('}])+-"\'', $this->a)))) {
-                    $command = self::ACTION_DELETE_A_B;
-                }
-            }
-            $this->action($command);
-        }
-        $this->output = trim($this->output);
-
-        if ($mbIntEnc !== null) {
-            mb_internal_encoding($mbIntEnc);
-        }
-        return $this->output;
-    }
-
-    /**
-     * ACTION_KEEP_A = Output A. Copy B to A. Get the next B.
-     * ACTION_DELETE_A = Copy B to A. Get the next B.
-     * ACTION_DELETE_A_B = Get the next B.
-     *
-     * @param int $command
-     * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException
-     */
-    protected function action($command)
-    {
-        if ($command === self::ACTION_DELETE_A_B 
-            && $this->b === ' '
-            && ($this->a === '+' || $this->a === '-')) {
-            // Note: we're at an addition/substraction operator; the inputIndex
-            // will certainly be a valid index
-            if ($this->input[$this->inputIndex] === $this->a) {
-                // This is "+ +" or "- -". Don't delete the space.
-                $command = self::ACTION_KEEP_A;
-            }
-        }
-        switch ($command) {
-            case self::ACTION_KEEP_A:
-                $this->output .= $this->a;
-                $this->lastByteOut = $this->a;
-                
-                // fallthrough
-            case self::ACTION_DELETE_A:
-                $this->a = $this->b;
-                if ($this->a === "'" || $this->a === '"') { // string literal
-                    $str = $this->a; // in case needed for exception
-                    while (true) {
-                        $this->output .= $this->a;
-                        $this->lastByteOut = $this->a;
-                        
-                        $this->a       = $this->get();
-                        if ($this->a === $this->b) { // end quote
-                            break;
-                        }
-                        if (ord($this->a) <= self::ORD_LF) {
-                            throw new JSMin_UnterminatedStringException(
-                                "JSMin: Unterminated String at byte "
-                                . $this->inputIndex . ": {$str}");
-                        }
-                        $str .= $this->a;
-                        if ($this->a === '\\') {
-                            $this->output .= $this->a;
-                            $this->lastByteOut = $this->a;
-                            
-                            $this->a       = $this->get();
-                            $str .= $this->a;
-                        }
-                    }
-                }
-                // fallthrough
-            case self::ACTION_DELETE_A_B:
-                $this->b = $this->next();
-                if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal
-                    $this->output .= $this->a . $this->b;
-                    $pattern = '/'; // in case needed for exception
-                    while (true) {
-                        $this->a = $this->get();
-                        $pattern .= $this->a;
-                        if ($this->a === '/') { // end pattern
-                            break; // while (true)
-                        } elseif ($this->a === '\\') {
-                            $this->output .= $this->a;
-                            $this->a       = $this->get();
-                            $pattern      .= $this->a;
-                        } elseif (ord($this->a) <= self::ORD_LF) {
-                            throw new JSMin_UnterminatedRegExpException(
-                                "JSMin: Unterminated RegExp at byte "
-                                . $this->inputIndex .": {$pattern}");
-                        }
-                        $this->output .= $this->a;
-                        $this->lastByteOut = $this->a;
-                    }
-                    $this->b = $this->next();
-                }
-            // end case ACTION_DELETE_A_B
-        }
-    }
-
-    /**
-     * @return bool
-     */
-    protected function isRegexpLiteral()
-    {
-        if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing
-            return true;
-        }
-        if (' ' === $this->a) {
-            $length = strlen($this->output);
-            if ($length < 2) { // weird edge case
-                return true;
-            }
-            // you can't divide a keyword
-            if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) {
-                if ($this->output === $m[0]) { // odd but could happen
-                    return true;
-                }
-                // make sure it's a keyword, not end of an identifier
-                $charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1);
-                if (! $this->isAlphaNum($charBeforeKeyword)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Get next char. Convert ctrl char to space.
-     *
-     * @return string
-     */
-    protected function get()
-    {
-        $c = $this->lookAhead;
-        $this->lookAhead = null;
-        if ($c === null) {
-            if ($this->inputIndex < $this->inputLength) {
-                $c = $this->input[$this->inputIndex];
-                $this->inputIndex += 1;
-            } else {
-                return null;
-            }
-        }
-        if ($c === "\r" || $c === "\n") {
-            return "\n";
-        }
-        if (ord($c) < self::ORD_SPACE) { // control char
-            return ' ';
-        }
-        return $c;
-    }
-
-    /**
-     * Get next char. If is ctrl character, translate to a space or newline.
-     *
-     * @return string
-     */
-    protected function peek()
-    {
-        $this->lookAhead = $this->get();
-        return $this->lookAhead;
-    }
-
-    /**
-     * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII?
-     *
-     * @param string $c
-     *
-     * @return bool
-     */
-    protected function isAlphaNum($c)
-    {
-        return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126);
-    }
-
-    /**
-     * @return string
-     */
-    protected function singleLineComment()
-    {
-        $comment = '';
-        while (true) {
-            $get = $this->get();
-            $comment .= $get;
-            if (ord($get) <= self::ORD_LF) { // EOL reached
-                // if IE conditional comment
-                if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
-                    return "/{$comment}";
-                }
-                return $get;
-            }
-        }
-    }
-
-    /**
-     * @return string
-     * @throws JSMin_UnterminatedCommentException
-     */
-    protected function multipleLineComment()
-    {
-        $this->get();
-        $comment = '';
-        while (true) {
-            $get = $this->get();
-            if ($get === '*') {
-                if ($this->peek() === '/') { // end of comment reached
-                    $this->get();
-                    // if comment preserved by YUI Compressor
-                    if (0 === strpos($comment, '!')) {
-                        return "\n/*!" . substr($comment, 1) . "*/\n";
-                    }
-                    // if IE conditional comment
-                    if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) {
-                        return "/*{$comment}*/";
-                    }
-                    return ' ';
-                }
-            } elseif ($get === null) {
-                throw new JSMin_UnterminatedCommentException(
-                    "JSMin: Unterminated comment at byte "
-                    . $this->inputIndex . ": /*{$comment}");
-            }
-            $comment .= $get;
-        }
-    }
-
-    /**
-     * Get the next character, skipping over comments.
-     * Some comments may be preserved.
-     *
-     * @return string
-     */
-    protected function next()
-    {
-        $get = $this->get();
-        if ($get !== '/') {
-            return $get;
-        }
-        switch ($this->peek()) {
-            case '/': return $this->singleLineComment();
-            case '*': return $this->multipleLineComment();
-            default: return $get;
-        }
-    }
-}
-
-class JSMin_UnterminatedStringException extends Exception {}
-class JSMin_UnterminatedCommentException extends Exception {}
-class JSMin_UnterminatedRegExpException extends Exception {}
index 3424587..9ba344d 100644 (file)
@@ -15,3 +15,4 @@ Changes:
  * Removed /builder/* - Not needed
  * Removed .htaccess - Not needed
  * Changed config.php - added moodle specific settings
+ * Removed lib/JSMin.php which is not GNU GPL compatible.
index 884d72a..ffe26a7 100644 (file)
@@ -1267,9 +1267,13 @@ class cm_info extends stdClass {
  * Returns reference to full info about modules in course (including visibility).
  * Cached and as fast as possible (0 or 1 db query).
  *
+ * use get_fast_modinfo($courseid, 0, true) to reset the static cache for particular course
+ * use get_fast_modinfo(0, 0, true) to reset the static cache for all courses
+ *
  * @uses MAX_MODINFO_CACHE_SIZE
- * @param int|stdClass $courseorid object or 'reset' string to reset caches, modinfo may be updated in db
- * @param int $userid Set 0 (default) for current user
+ * @param int|stdClass $courseorid object from DB table 'course' or just a course id
+ * @param int $userid User id to populate 'uservisible' attributes of modules and sections.
+ *     Set to 0 for current user (default)
  * @param bool $resetonly whether we want to get modinfo or just reset the cache
  * @return course_modinfo|null Module information for course, or null if resetting
  */
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 0f8df49..0fd2496 100644 (file)
@@ -3527,7 +3527,7 @@ class settings_navigation extends navigation_node {
         // Import data from other courses
         if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) {
             $url = new moodle_url('/backup/import.php', array('id'=>$course->id));
-            $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/restore', ''));
+            $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', ''));
         }
 
         // Publish course on a hub
@@ -3597,7 +3597,7 @@ class settings_navigation extends navigation_node {
             $returnurl->param('sesskey', sesskey());
             foreach ($roles as $key => $name) {
                 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>$returnurl->out(false)));
-                $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
+                $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/switchrole', ''));
             }
         }
         // Return we are done
@@ -4064,7 +4064,7 @@ class settings_navigation extends navigation_node {
         // Assign local roles
         if (has_capability('moodle/role:assign', $this->context)) {
             $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
-            $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
+            $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
         }
 
         // Override roles
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 6239615..c38de61 100644 (file)
@@ -759,6 +759,23 @@ class core_renderer extends renderer_base {
             $header = $this->doctype() . $header;
         }
 
+        // If this theme version is below 2.4 release and this is a course view page
+        if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
+                $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+            // check if course content header/footer have not been output during render of theme layout
+            $coursecontentheader = $this->course_content_header(true);
+            $coursecontentfooter = $this->course_content_footer(true);
+            if (!empty($coursecontentheader)) {
+                // display debug message and add header and footer right above and below main content
+                // Please note that course header and footer (to be displayed above and below the whole page)
+                // are not displayed in this case at all.
+                // Besides the content header and footer are not displayed on any other course page
+                debugging('The current theme is not optimised for 2.4, the course-specific header and footer defined in course format will not be output', DEBUG_DEVELOPER);
+                $header .= $coursecontentheader;
+                $footer = $coursecontentfooter. $footer;
+            }
+        }
+
         send_headers($this->contenttype, $this->page->cacheable);
 
         $this->opencontainers->push('header/footer', $footer);
@@ -845,6 +862,100 @@ class core_renderer extends renderer_base {
         return $this->opencontainers->pop_all_but_last($shouldbenone);
     }
 
+    /**
+     * Returns course-specific information to be output immediately above content on any course page
+     * (for the current course)
+     *
+     * @param bool $onlyifnotcalledbefore output content only if it has not been output before
+     * @return string
+     */
+    public function course_content_header($onlyifnotcalledbefore = false) {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        static $functioncalled = false;
+        if ($functioncalled && $onlyifnotcalledbefore) {
+            // we have already output the content header
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $functioncalled = true;
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_content_header()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output immediately below content on any course page
+     * (for the current course)
+     *
+     * @param bool $onlyifnotcalledbefore output content only if it has not been output before
+     * @return string
+     */
+    public function course_content_footer($onlyifnotcalledbefore = false) {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        static $functioncalled = false;
+        if ($functioncalled && $onlyifnotcalledbefore) {
+            // we have already output the content footer
+            return '';
+        }
+        $functioncalled = true;
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_content_footer()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output on any course page in the header area
+     * (for the current course)
+     *
+     * @return string
+     */
+    public function course_header() {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_header()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output on any course page in the footer area
+     * (for the current course)
+     *
+     * @return string
+     */
+    public function course_footer() {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_footer()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
     /**
      * Returns lang menu or '', this method also checks forcing of languages in courses.
      *
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 =>&