Merge branch 'wip-MDL-35770-master' of git://github.com/marinaglancy/moodle
authorDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 08:26:26 +0000 (16:26 +0800)
committerDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 08:26:26 +0000 (16:26 +0800)
172 files changed:
admin/courseformats.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/format/topics/styles.css
course/format/weeks/styles.css
course/index.php
course/lib.php
course/renderer.php
course/tests/courselib_test.php
course/yui/dragdrop/dragdrop.js
course/yui/toolboxes/toolboxes.js
enrol/renderer.php
grade/edit/tree/calculation.php
grade/edit/tree/grade.php
grade/lib.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/grade/grade_item.php
lib/grade/tests/grade_item_test.php
lib/gradelib.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/gradelib_test.php [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/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/standard/style/blocks.css
theme/standard/style/course.css
theme/standard/style/dock.css
theme/upgrade.txt
user/selector/lib.php
version.php

index 11a5eab..a8377de 100644 (file)
@@ -115,7 +115,7 @@ switch ($action) {
                 $message = get_string('formatuninstallconfirm', 'admin', $format->displayname);
             }
             $deleteurl->param('confirm', 1);
-            echo $OUTPUT->confirm($message, $deleteurl, $return);            
+            echo $OUTPUT->confirm($message, $deleteurl, $return);
         } else {
             $a = new stdClass();
             $a->plugin = $format->displayname;
@@ -128,4 +128,4 @@ switch ($action) {
         echo $OUTPUT->footer();
         exit;
 }
-redirect($return);
\ No newline at end of file
+redirect($return);
index 4d9365c..b7fa57e 100644 (file)
@@ -312,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..6488847 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,13 +104,38 @@ class calendar_addsubscription_form extends moodleform {
      */
     public function validation($data, $files) {
         $errors = parent::validation($data, $files);
+
         if (empty($data['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');
             }
+        } else if (!empty($data['url'])) {
+            if (clean_param($data['url'], PARAM_URL) !== $data['url']) {
+                $errors['url']  = get_string('invalidurl', 'error');
+            }
         }
         return $errors;
     }
+
+    public function definition_after_data() {
+        $mform =& $this->_form;
+
+        $mform->applyFilter('url', 'calendar_addsubscription_form::strip_webcal');
+    }
+
+    /**
+     * Replace webcal:// urls with http:// as
+     * curl does not understand this protocol
+     *
+     * @param string @url url to examine
+     * @return string url with webcal:// replaced
+     */
+    public static function strip_webcal($url) {
+        if (strpos($url, 'webcal://') === 0) {
+            $url = str_replace('webcal://', 'http://', $url);
+        }
+        return $url;
+    }
 }
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 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 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 5d396e2..7d2ed99 100644 (file)
@@ -1273,7 +1273,16 @@ function set_section_visible($courseid, $sectionnumber, $visibility) {
         if (!empty($section->sequence)) {
             $modules = explode(",", $section->sequence);
             foreach ($modules as $moduleid) {
-                set_coursemodule_visible($moduleid, $visibility, true);
+                if ($cm = $DB->get_record('course_modules', array('id' => $moduleid), 'visible, visibleold')) {
+                    if ($visibility) {
+                        // As we unhide the section, we use the previously saved visibility stored in visibleold.
+                        set_coursemodule_visible($moduleid, $cm->visibleold);
+                    } else {
+                        // We hide the section, so we hide the module but we store the original state in visibleold.
+                        set_coursemodule_visible($moduleid, 0);
+                        $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id' => $moduleid));
+                    }
+                }
             }
         }
         rebuild_course_cache($courseid, true);
@@ -1490,9 +1499,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) . '" ';
@@ -1510,7 +1519,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>';
 
@@ -1578,7 +1587,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();
             }
@@ -2807,16 +2815,26 @@ function set_coursemodule_idnumber($id, $idnumber) {
 }
 
 /**
-* $prevstateoverrides = true will set the visibility of the course module
-* to what is defined in visibleold. This enables us to remember the current
-* visibility when making a whole section hidden, so that when we toggle
-* that section back to visible, we are able to return the visibility of
-* the course module back to what it was originally.
-*/
-function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
+ * Set the visibility of a module and inherent properties.
+ *
+ * From 2.4 the parameter $prevstateoverrides has been removed, the logic it triggered
+ * has been moved to {@link set_section_visible()} which was the only place from which
+ * the parameter was used.
+ *
+ * @param int $id of the module
+ * @param int $visible state of the module
+ * @return bool false when the module was not found, true otherwise
+ */
+function set_coursemodule_visible($id, $visible) {
     global $DB, $CFG;
     require_once($CFG->libdir.'/gradelib.php');
 
+    // Trigger developer's attention when using the previously removed argument.
+    if (func_num_args() > 2) {
+        debugging('Wrong number of arguments passed to set_coursemodule_visible(), $prevstateoverrides
+            has been removed.', DEBUG_DEVELOPER);
+    }
+
     if (!$cm = $DB->get_record('course_modules', array('id'=>$id))) {
         return false;
     }
@@ -2841,17 +2859,14 @@ function set_coursemodule_visible($id, $visible, $prevstateoverrides=false) {
         }
     }
 
-    if ($prevstateoverrides) {
-        if ($visible == '0') {
-            // Remember the current visible state so we can toggle this back.
-            $DB->set_field('course_modules', 'visibleold', $cm->visible, array('id'=>$id));
-        } else {
-            // Get the previous saved visible states.
-            $DB->set_field('course_modules', 'visible', $cm->visibleold, array('id'=>$id));
-        }
-    } else {
-        $DB->set_field("course_modules", "visible", $visible, array("id"=>$id));
-    }
+    // Updating visible and visibleold to keep them in sync. Only changing a section visibility will
+    // affect visibleold to allow for an original visibility restore. See set_section_visible().
+    $cminfo = new stdClass();
+    $cminfo->id = $id;
+    $cminfo->visible = $visible;
+    $cminfo->visibleold = $visible;
+    $DB->update_record('course_modules', $cminfo);
+
     rebuild_course_cache($cm->course, true);
     return true;
 }
@@ -3317,7 +3332,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)
         );
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 c964414..a59351b 100644 (file)
@@ -314,4 +314,125 @@ class courselib_testcase extends advanced_testcase {
         $this->assertTrue(empty($modinfo->sections[0]));
         $this->assertFalse(empty($modinfo->sections[3]));
     }
+
+    public function test_module_visibility() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        // Create course and modules.
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
+        $modules = compact('forum', 'assign');
+
+        // Hiding the modules.
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 0);
+            $this->check_module_visibility($mod, 0, 0);
+        }
+
+        // Showing the modules.
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 1);
+            $this->check_module_visibility($mod, 1, 1);
+        }
+    }
+
+    public function test_section_visibility() {
+        $this->setAdminUser();
+        $this->resetAfterTest(true);
+
+        // Create course.
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
+
+        // Testing an empty section.
+        $sectionnumber = 1;
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+
+        // Testing a section with visible modules.
+        $sectionnumber = 2;
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+                array('section' => $sectionnumber));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
+                'course' => $course->id), array('section' => $sectionnumber));
+        $modules = compact('forum', 'assign');
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 1);
+        }
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 1, 1);
+        }
+
+        // Testing a section with hidden modules, which should stay hidden.
+        $sectionnumber = 3;
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+                array('section' => $sectionnumber));
+        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
+                'course' => $course->id), array('section' => $sectionnumber));
+        $modules = compact('forum', 'assign');
+        foreach ($modules as $mod) {
+            set_coursemodule_visible($mod->cmid, 0);
+            $this->check_module_visibility($mod, 0, 0);
+        }
+        set_section_visible($course->id, $sectionnumber, 0);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 0);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 0);
+        }
+        set_section_visible($course->id, $sectionnumber, 1);
+        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
+        $this->assertEquals($section_info->visible, 1);
+        foreach ($modules as $mod) {
+            $this->check_module_visibility($mod, 0, 0);
+        }
+    }
+
+    /**
+     * Helper function to assert that a module has correctly been made visible, or hidden.
+     *
+     * @param stdClass $mod module information
+     * @param int $visibility the current state of the module
+     * @param int $visibleold the current state of the visibleold property
+     * @return void
+     */
+    public function check_module_visibility($mod, $visibility, $visibleold) {
+        global $DB;
+        $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
+        $this->assertEquals($visibility, $cm->visible);
+        $this->assertEquals($visibleold, $cm->visibleold);
+
+        // Check the module grade items.
+        $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
+                'iteminstance' => $cm->instance, 'courseid' => $cm->course));
+        if ($grade_items) {
+            foreach ($grade_items as $grade_item) {
+                if ($visibility) {
+                    $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
+                } else {
+                    $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
+                }
+            }
+        }
+
+        // Check the events visibility.
+        if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
+            foreach ($events as $event) {
+                $calevent = new calendar_event($event);
+                $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
+            }
+        }
+    }
+
 }
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 ff89761..21e2d7d 100644 (file)
@@ -48,7 +48,7 @@ require_capability('moodle/grade:manage', $context);
 
 // default return url
 $gpr = new grade_plugin_return();
-$returnurl = $gpr->get_return_url($CFG->wwwroot.'/grade/report.php?id='.$course->id);
+$returnurl = $gpr->get_return_url($CFG->wwwroot.'/grade/report/index.php?id='.$course->id);
 
 if (!$grade_item = grade_item::fetch(array('id'=>$id, 'courseid'=>$course->id))) {
     print_error('invaliditemid');
index c3c8a0a..8850622 100644 (file)
@@ -57,7 +57,7 @@ if (!has_capability('moodle/grade:manage', $context)) {
 
 // default return url
 $gpr = new grade_plugin_return();
-$returnurl = $gpr->get_return_url($CFG->wwwroot.'/grade/report.php?id='.$course->id);
+$returnurl = $gpr->get_return_url($CFG->wwwroot.'/grade/report/index.php?id='.$course->id);
 
 // security checks!
 if (!empty($id)) {
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 9ed45e1..e5b4760 100644 (file)
@@ -5299,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'));
     }
 
     /**
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..5bd64ec 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>
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 12afe84..8378b78 100644 (file)
@@ -1423,30 +1423,33 @@ class grade_item extends grade_object {
      * Refetch grades from modules, plugins.
      *
      * @param int $userid optional, limit the refetch to a single user
+     * @return bool Returns true on success or if there is nothing to do
      */
     public function refresh_grades($userid=0) {
         global $DB;
         if ($this->itemtype == 'mod') {
             if ($this->is_outcome_item()) {
                 //nothing to do
-                return;
+                return true;
             }
 
             if (!$activity = $DB->get_record($this->itemmodule, array('id' => $this->iteminstance))) {
                 debugging("Can not find $this->itemmodule activity with id $this->iteminstance");
-                return;
+                return false;
             }
 
             if (!$cm = get_coursemodule_from_instance($this->itemmodule, $activity->id, $this->courseid)) {
                 debugging('Can not find course module');
-                return;
+                return false;
             }
 
             $activity->modname    = $this->itemmodule;
             $activity->cmidnumber = $cm->idnumber;
 
-            grade_update_mod_grades($activity);
+            return grade_update_mod_grades($activity, $userid);
         }
+
+        return true;
     }
 
     /**
index dab82c2..35d527b 100644 (file)
@@ -58,6 +58,7 @@ class grade_item_testcase extends grade_base_testcase {
         $this->sub_test_grade_item_is_course_item();
         $this->sub_test_grade_item_fetch_course_item();
         $this->sub_test_grade_item_depends_on();
+        $this->sub_test_refresh_grades();
         $this->sub_test_grade_item_is_calculated();
         $this->sub_test_grade_item_set_calculation();
         $this->sub_test_grade_item_get_calculation();
@@ -483,6 +484,18 @@ class grade_item_testcase extends grade_base_testcase {
         $this->assertEquals($res, $deps);
     }
 
+    protected function sub_test_refresh_grades() {
+        // Testing with the grade item for a mod_assignment instance.
+        $grade_item = new grade_item($this->grade_items[0], false);
+        $this->assertTrue(method_exists($grade_item, 'refresh_grades'));
+        $this->assertTrue($grade_item->refresh_grades());
+
+        // Break the grade item and check error handling.
+        $grade_item->iteminstance = 123456789;
+        $this->assertFalse($grade_item->refresh_grades());
+        $this->assertDebuggingCalled();
+    }
+
     protected function sub_test_grade_item_is_calculated() {
         $grade_item = new grade_item($this->grade_items[1], false);
         $this->assertTrue(method_exists($grade_item, 'is_calculated'));
index f83cffd..f8742c2 100644 (file)
@@ -1190,7 +1190,7 @@ function grade_update_mod_grades($modinstance, $userid=0) {
         $updategradesfunc($modinstance, $userid);
 
     } else {
-        // mudule does not support grading??
+        // Module does not support grading?
     }
 
     return true;
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 f11821f..77e9883 100644 (file)
@@ -8544,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;
 
 
@@ -8832,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 c38de61..45d9618 100644 (file)
@@ -346,12 +346,6 @@ class core_renderer extends renderer_base {
     public function standard_head_html() {
         global $CFG, $SESSION;
         $output = '';
-        if ($this->page->theme->doctype === 'html5' or $this->page->theme->doctype === 'xhtml5') {
-            // Make sure we set 'X-UA-Compatible' only if script did not request something else (such as MDL-29213).
-            if (empty($CFG->additionalhtmlhead) or stripos($CFG->additionalhtmlhead, 'X-UA-Compatible') === false) {
-                $output .= '<meta http-equiv="X-UA-Compatible" content="IE=edge" />' . "\n";
-            }
-        }
         $output .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />' . "\n";
         $output .= '<meta name="keywords" content="moodle, ' . $this->page->title . '" />' . "\n";
         if (!$this->page->cacheable) {
index 6f15d95..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) {
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..51d7b7c 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
-              GROUP BY timeend, courseid, userid
-                HAVING count(statsreads) > 0";
+        // 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)
 
-        if ($logspresent and !$DB->execute($sql)) {
+                SELECT 'logins', $nextmidnight AS timeend, ".SITEID." AS courseid,
+                        userid, COUNT(id) AS statsreads
+                  FROM {temp_log1} l
+                 WHERE action = 'login'
+              GROUP BY userid
+                HAVING COUNT(id) > 0";
+
+        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,355 @@ 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";
-
-        if (!$DB->execute($sql, array('courselevel'=>CONTEXT_COURSE))) {
+                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 (!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, course";
+
+        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 c.id";
+
+        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
+
+              GROUP BY 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 courseid
                 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
+
+              GROUP BY 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
                 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 stattype, timeend, courseid, $guestrole AS roleid,
+                       SUM(statsreads) AS stat1, SUM(statswrites) AS stat2
                   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
+                    SELECT sud.stattype, sud.timeend, sud.courseid,
+                           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, nroleid
-                HAVING SUM(statsreads) > 0 OR SUM(statswrites) > 0";
+                 GROUP BY stattype, timeend, courseid
+                 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 +686,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 +699,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 +718,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 +733,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 +748,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 +763,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 +828,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 +844,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 +859,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 +874,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 +889,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 +904,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 +1436,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 +1536,181 @@ 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';
+    $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);
+            $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/gradelib_test.php b/lib/tests/gradelib_test.php
new file mode 100644 (file)
index 0000000..14173cf
--- /dev/null
@@ -0,0 +1,59 @@
+<?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/>.
+
+/**
+ * Unit tests for /lib/gradelib.php.
+ *
+ * @package   core_grade
+ * @category  phpunit
+ * @copyright 2012 Andrew Davis
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->libdir . '/gradelib.php');
+
+class gradelib_testcase extends advanced_testcase {
+
+    public function test_grade_update_mod_grades() {
+
+        $this->resetAfterTest(true);
+
+        // Create a broken module instance.
+        $modinstance = new stdClass();
+        $modinstance->modname = 'doesntexist';
+
+        $this->assertFalse(grade_update_mod_grades($modinstance));
+        // A debug message should have been generated.
+        $this->assertDebuggingCalled();
+
+        // Create a course and instance of mod_assign.
+        $course = $this->getDataGenerator()->create_course();
+
+        $assigndata['course'] = $course->id;
+        $assigndata['name'] = 'lightwork assignment';
+        $modinstance = self::getDataGenerator()->create_module('assign', $assigndata);
+
+        // grade_update_mod_grades() requires 2 additional properties, cmidnumber and modname.
+        $cm = get_coursemodule_from_instance('assign', $modinstance->id, 0, false, MUST_EXIST);
+        $modinstance->cmidnumber = $cm->id;
+        $modinstance->modname = 'assign';
+
+        $this->assertTrue(grade_update_mod_grades($modinstance));
+    }
+}
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..2e79f80
--- /dev/null
@@ -0,0 +1,617 @@
+<?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, $output = '') {
+        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();
+
+            $message = 'Incorrect number of results returned for '. $type;
+
+            if ($output != '') {
+                $message .= "\nCron output:\n$output";
+            }
+
+            $this->assertEquals($rows, sizeof($records), $message);
+
+            for ($i = 0; $i < $rows; $i++) {
+                $row   = $table->getRow($i);
+                $found = 0;
+
+                foreach ($records as $key => $record) {
+                    $record = (array) $record;
+                    unset($record['id']);
+                    $diff = array_merge(array_diff_assoc($row, $record),
+                            array_diff_assoc($record, $row));
+
+                    if (empty($diff)) {
+                        $found = $key;
+                        break;
+                    }
+                }
+
+                $this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true)
+                                        ." was not found in $type ". var_export($records, true));
+                unset($records[$found]);
+            }
+        }
+    }
+
+    /**
+     * Test progress output when debug is on
+     */
+    public function test_statslib_progress_debug() {
+        global $CFG;
+
+        $CFG->debug = DEBUG_ALL;
+        $this->expectOutputString('1:0 ');
+        stats_progress('init');
+        stats_progress('1');
+    }
+
+    /**
+     * Test progress output when debug is off
+     */
+    public function test_statslib_progress_no_debug() {
+        global $CFG;
+
+        $CFG->debug = DEBUG_NONE;
+        $this->expectOutputString('.');
+        stats_progress('init');
+        stats_progress('1');
+    }
+
+    /**
+     * Test the function that gets the start date from the config
+     */
+    public function test_statslib_get_start_from() {
+        global $CFG, $DB;
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml");
+        $time = time();
+        $DB->delete_records('log');
+
+        // Don't ask.  I don't think get_timezone_offset works correctly.
+        $day = self::DAY - get_timezone_offset($CFG->timezone);
+
+        $CFG->statsfirstrun = 'all';
+        // Allow 1 second difference in case we cross a second boundary.
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'All start time');
+
+        $this->prepare_db($dataset[0], array('log'));
+        $records = $DB->get_records('log');
+
+        $this->assertEquals($day + 14410, stats_get_start_from('daily'), 'Log entry start');
+
+        $CFG->statsfirstrun = 'none';
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'None start time');
+
+        $CFG->statsfirstrun = 14515200;
+        $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (14515200)), 'Specified start time');
+
+        $this->prepare_db($dataset[1], array('stats_daily'));
+        $this->assertEquals($day + (24 * 3600), stats_get_start_from('daily'), 'Daily stats start time');
+    }
+
+    /**
+     * Test the function that calculates the start of the day
+     *
+     * NOTE: I don't think this is the way this function should work.
+     *       This test documents the current functionality.
+     */
+    public function test_statslib_get_base_daily() {
+        global $CFG;
+
+        for ($x = 0; $x < 24; $x += 1) {
+            $CFG->timezone = $x;
+
+            $start = 1272672000 - ($x * 3600);
+            if ($x >= 20) {
+                $start += (24 * 3600);
+            }
+
+            $this->assertEquals($start, stats_get_base_daily(1272686410), "Timezone $x check");
+        }
+    }
+
+    /**
+     * Test the function that gets the start of the next day
+     */
+    public function test_statslib_get_next_day_start() {
+        global $CFG;
+
+        $CFG->timezone = 0;
+        $this->assertEquals(1272758400, stats_get_next_day_start(1272686410));
+    }
+
+    /**
+     * Test the function that gets the action names
+     *
+     * Note: The function results depend on installed modules.  The hard coded lists are the
+     *       defaults for a new Moodle 2.3 install.
+     */
+    public function test_statslib_get_action_names() {
+        $basepostactions = array (
+            0 => 'add',
+            1 => 'delete',
+            2 => 'edit',
+            3 => 'add mod',
+            4 => 'delete mod',
+            5 => 'edit sectionenrol',
+            6 => 'loginas',
+            7 => 'new',
+            8 => 'unenrol',
+            9 => 'update',
+            10 => 'update mod',
+            11 => 'upload',
+            12 => 'submit',
+            13 => 'submit for grading',
+            14 => 'talk',
+            15 => 'choose',
+            16 => 'choose again',
+            17 => 'record delete',
+            18 => 'add discussion',
+            19 => 'add post',
+            20 => 'delete discussion',
+            21 => 'delete post',
+            22 => 'move discussion',
+            23 => 'prune post',
+            24 => 'update post',
+            25 => 'add category',
+            26 => 'add entry',
+            27 => 'approve entry',
+            28 => 'delete category',
+            29 => 'delete entry',
+            30 => 'edit category',
+            31 => 'update entry',
+            32 => 'end',
+            33 => 'start',
+            34 => 'attempt',
+            35 => 'close attempt',
+            36 => 'preview',
+            37 => 'editquestions',
+            38 => 'delete attempt',
+            39 => 'manualgrade',
+        );
+
+         $baseviewactions = array (
+            0 => 'view',
+            1 => 'view all',
+            2 => 'history',
+            3 => 'view submission',
+            4 => 'view feedback',
+            5 => 'print',
+            6 => 'report',
+            7 => 'view discussion',
+            8 => 'search',
+            9 => 'forum',
+            10 => 'forums',
+            11 => 'subscribers',
+            12 => 'view forum',
+            13 => 'view entry',
+            14 => 'review',
+            15 => 'pre-view',
+            16 => 'download',
+            17 => 'view form',
+            18 => 'view graph',
+            19 => 'view report',
+        );
+
+        $postactions = stats_get_action_names('post');
+
+        foreach ($basepostactions as $action) {
+            $this->assertContains($action, $postactions);
+        }
+
+        $viewactions = stats_get_action_names('view');
+
+        foreach ($baseviewactions as $action) {
+            $this->assertContains($action, $viewactions);
+        }
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     */
+    public function test_statslib_temp_table_create_and_drop() {
+        global $DB;
+
+        foreach ($this->tables as $table) {
+            $this->assertFalse($DB->get_manager()->table_exists($table));
+        }
+
+        stats_temp_table_create();
+
+        foreach ($this->tables as $table) {
+            $this->assertTrue($DB->get_manager()->table_exists($table));
+        }
+
+        stats_temp_table_drop();
+
+        foreach ($this->tables as $table) {
+            $this->assertFalse($DB->get_manager()->table_exists($table));
+        }
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_fill() {
+        global $CFG, $DB;
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml");
+
+        $this->prepare_db($dataset[0], array('log'));
+
+        $start = self::DAY - get_timezone_offset($CFG->timezone);
+        $end   = $start + (24 * 3600);
+
+        stats_temp_table_create();
+        stats_temp_table_fill($start, $end);
+
+        $this->assertEquals(1, $DB->count_records('temp_log1'));
+        $this->assertEquals(1, $DB->count_records('temp_log2'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the temporary table creation and deletion.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_setup() {
+        global $DB;
+
+        $logs = array();
+        $this->prepare_db($logs, array('log'));
+
+        stats_temp_table_create();
+        stats_temp_table_setup();
+
+        $this->assertEquals(1, $DB->count_records('temp_enroled'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the function that clean out the temporary tables.
+     *
+     * @depends test_statslib_temp_table_create_and_drop
+     */
+    public function test_statslib_temp_table_clean() {
+        global $DB;
+
+        $rows = array(
+            'temp_log1'             => array('id' => 1, 'course' => 1),
+            'temp_log2'             => array('id' => 1, 'course' => 1),
+            'temp_stats_daily'      => array('id' => 1, 'courseid' => 1),
+            'temp_stats_user_daily' => array('id' => 1, 'courseid' => 1),
+        );
+
+        stats_temp_table_create();
+
+        foreach ($rows as $table => $row) {
+            $DB->insert_record_raw($table, $row);
+            $this->assertEquals(1, $DB->count_records($table));
+        }
+
+        stats_temp_table_clean();
+
+        foreach ($rows as $table => $row) {
+            $this->assertEquals(0, $DB->count_records($table));
+        }
+
+        $this->assertEquals(1, $DB->count_records('stats_daily'));
+        $this->assertEquals(1, $DB->count_records('stats_user_daily'));
+
+        stats_temp_table_drop();
+    }
+
+    /**
+     * Test the daily stats function
+     *
+     * @depends test_statslib_get_base_daily
+     * @depends test_statslib_get_next_day_start
+     * @depends test_statslib_get_start_from
+     * @depends test_statslib_temp_table_create_and_drop
+     * @depends test_statslib_temp_table_setup
+     * @depends test_statslib_temp_table_fill
+     * @dataProvider daily_log_provider
+     */
+    public function test_statslib_cron_daily($logs, $stats) {
+        global $CFG, $DB;
+
+        $this->prepare_db($logs, array('log'));
+
+        // Stats cron daily uses mtrace, turn on buffering to silence output.
+        ob_start();
+        stats_cron_daily(1);
+        $output = ob_get_contents();
+        ob_end_clean();
+
+        $this->verify_stats($stats, $output);
+    }
+
+    /**
+     * Test the daily stats function
+     * @depends test_statslib_get_base_daily
+     * @depends test_statslib_get_next_day_start
+     */
+    public function test_statslib_cron_daily_no_default_profile_id() {
+        global $CFG, $DB;
+        $CFG->defaultfrontpageroleid = 0;
+
+        $course1  = $DB->get_record('course', array('shortname' => 'course1'));
+        $guestid  = $CFG->siteguest;
+        $start    = stats_get_base_daily(1272758400);
+        $end      = stats_get_next_day_start($start);
+        $fpid     = (int) $CFG->defaultfrontpageroleid;
+        $gr       = get_guest_role();
+
+        $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test10.xml");
+
+        $this->prepare_db($dataset[0], array('log'));
+
+        // Stats cron daily uses mtrace, turn on buffering to silence output.
+        ob_start();
+        stats_cron_daily($maxdays=1);
+        $output = ob_get_contents();
+        ob_end_clean();
+
+        $this->verify_stats($dataset[1], $output);
+    }
+}
index 6be03c9..25b7bb9 100644 (file)
@@ -1840,7 +1840,26 @@ function get_html_lang($dir = false) {
 
 /**
  * Send the HTTP headers that Moodle requires.
- * @param $cacheable Can this page be cached on back?
+ *
+ * There is a backwards compatibility hack for legacy code
+ * that needs to add custom IE compatibility directive.
+ *
+ * Example:
+ * <code>
+ * if (!isset($CFG->additionalhtmlhead)) {
+ *     $CFG->additionalhtmlhead = '';
+ * }
+ * $CFG->additionalhtmlhead .= '<meta http-equiv="X-UA-Compatible" content="IE=8" />';
+ * header('X-UA-Compatible: IE=8');
+ * echo $OUTPUT->header();
+ * </code>
+ *
+ * Please note the $CFG->additionalhtmlhead alone might not work,
+ * you should send the IE compatibility header() too.
+ *
+ * @param string $contenttype
+ * @param bool $cacheable Can this page be cached on back?
+ * @return void, sends HTTP headers
  */
 function send_headers($contenttype, $cacheable = true) {
     global $CFG;
@@ -1849,6 +1868,10 @@ function send_headers($contenttype, $cacheable = true) {
     @header('Content-Script-Type: text/javascript');
     @header('Content-Style-Type: text/css');
 
+    if (empty($CFG->additionalhtmlhead) or stripos($CFG->additionalhtmlhead, 'X-UA-Compatible') === false) {
+        @header('X-UA-Compatible: IE=edge');
+    }
+
     if ($cacheable) {
         // Allow caching on "back" (but not on normal clicks)
         @header('Cache-Control: private, pre-check=0, post-check=0, max-age=0');
index c46205b..6d7fd45 100644 (file)
@@ -1,5 +1,9 @@
 YUI.add('moodle-core-dragdrop', function(Y) {
-    var MOVEICON = {'pix':"i/move_2d",'component':'moodle'};
+    var MOVEICON = {
+        pix: "i/move_2d",
+        largepix: "i/dragdrop",
+        component: 'moodle'
+    };
 
    /*
     * General DRAGDROP class, this should not be used directly,
@@ -31,11 +35,15 @@ YUI.add('moodle-core-dragdrop', function(Y) {
             Y.DD.DDM.on('drag:dropmiss', this.global_drag_dropmiss, this);
         },
 
-        get_drag_handle: function(title, classname, iconclass) {
+        get_drag_handle: function(title, classname, iconclass, large) {
+            var iconname = MOVEICON.pix;
+            if (large) {
+                iconname = MOVEICON.largepix;
+            }
             var dragicon = Y.Node.create('<img />')
                 .setStyle('cursor', 'move')
                 .setAttrs({
-                    'src' : M.util.image_url(MOVEICON.pix, MOVEICON.component),
+                    'src' : M.util.image_url(iconname, MOVEICON.component),
                     'alt' : title
                 });
             if (iconclass) {
index 53291dc..80675c6 100644 (file)
@@ -317,6 +317,22 @@ class assign_grading_table extends table_sql implements renderable {
         }
     }
 
+    /**
+     * Before adding each row to the table make sure rownum is incremented
+     *
+     * @param array $row row of data from db used to make one row of the table.
+     * @return array one row for the table
+     */
+    function format_row($row) {
+        if ($this->rownum < 0) {
+            $this->rownum = $this->currpage * $this->pagesize;
+        } else {
+            $this->rownum += 1;
+        }
+
+        return parent::format_row($row);
+    }
+
     /**
      * Add a column with an ID that uniquely identifies this user in this assignment
      *
@@ -491,7 +507,7 @@ class assign_grading_table extends table_sql implements renderable {
 
 
     /**
-     * Format a user picture for display (and update rownum as a sideeffect)
+     * Format a user picture for display
      *
      * @param stdClass $row
      * @return string
@@ -686,11 +702,6 @@ class assign_grading_table extends table_sql implements renderable {
      */
     function col_userid(stdClass $row) {
         $edit = '';
-        if ($this->rownum < 0) {
-            $this->rownum = $this->currpage * $this->pagesize;
-        } else {
-            $this->rownum += 1;
-        }
 
         $actions = array();
 
index fa346b9..ed272cf 100644 (file)
@@ -1228,7 +1228,7 @@ function chat_extend_navigation($navigation, $course, $module, $cm) {
         $links[] = new action_link($url, get_string('noframesjs', 'message'), $action);
 
         foreach ($links as $link) {
-            $navigation->add($link->text, $link, navigation_node::TYPE_SETTING, null ,null, new pix_icon('c/group' , ''));
+            $navigation->add($link->text, $link, navigation_node::TYPE_SETTING, null ,null, new pix_icon('i/group' , ''));
         }
     }
 
@@ -1237,7 +1237,7 @@ function chat_extend_navigation($navigation, $course, $module, $cm) {
         $users = $navigation->add(get_string('currentusers', 'chat'));
         foreach ($chatusers as $chatuser) {
             $userlink = new moodle_url('/user/view.php', array('id'=>$chatuser->id,'course'=>$course->id));
-            $users->add(fullname($chatuser).' '.format_time(time() - $chatuser->lastmessageping), $userlink, navigation_node::TYPE_USER, null, null, new pix_icon('c/user', ''));
+            $users->add(fullname($chatuser).' '.format_time(time() - $chatuser->lastmessageping), $userlink, navigation_node::TYPE_USER, null, null, new pix_icon('i/user', ''));
         }
     }
 }
diff --git a/mod/chat/upgrade.txt b/mod/chat/upgrade.txt
new file mode 100644 (file)
index 0000000..2bda899
--- /dev/null
@@ -0,0 +1,8 @@
+This files describes API changes in /mod/chat/*,
+information provided here is intended especially for developers.
+
+
+=== 2.4 ===
+
+optional - no changes needed:
+* previous references to icons c/user and c/group have been renamed to i/user and i/group.
index 887f9cb..12e8209 100644 (file)
@@ -269,12 +269,12 @@ switch ($mode) {
                 list($sort, $sortparams) = users_order_by_sql('u');
                 $params = array_merge($params, $sortparams);
                 if (!empty($cm->groupingid)) {
-                    $params["groupinid"] = $cm->groupingid;
+                    $params["groupingid"] = $cm->groupingid;
                     $sql = "SELECT DISTINCT $ufields
                             FROM {lesson_attempts} a
                                 INNER JOIN {user} u ON u.id = a.userid
                                 INNER JOIN {groups_members} gm ON gm.userid = u.id
-                                INNER JOIN {groupings_groups} gg ON gm.groupid = :groupinid
+                                INNER JOIN {groupings_groups} gg ON gm.groupid = gg.groupid AND gg.groupingid = :groupingid
                             WHERE a.lessonid = :lessonid
                             ORDER BY $sort";
                 } else {
diff --git a/mod/lti/tests/generator/lib.php b/mod/lti/tests/generator/lib.php
new file mode 100644 (file)
index 0000000..b3d3480
--- /dev/null
@@ -0,0 +1,101 @@
+<?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/>.
+
+/**
+ * mod_lti data generator
+ *
+ * @package    mod_lti
+ * @category   phpunit
+ * @copyright  Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
+ * @author     Mark Nielsen
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Assignment module PHPUnit data generator class
+ *
+ * @package    mod_lti
+ * @category   phpunit
+ * @copyright  Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
+ * @author     Mark Nielsen
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_lti_generator extends phpunit_module_generator {
+
+    /**
+     * Create new lti module instance
+     *
+     * @param array|stdClass $record
+     * @param array $options
+     * @throws coding_exception
+     * @return stdClass activity record with extra cmid field
+     */
+    public function create_instance($record = null, array $options = null) {
+        global $CFG;
+
+        require_once("$CFG->dirroot/mod/lti/lib.php");
+
+        $this->instancecount++;
+        $i = $this->instancecount;
+
+        $record  = (object) (array) $record;
+        $options = (array) $options;
+
+        if (empty($record->course)) {
+           &