Merge branch 'MDL-36331_redirect' of git://github.com/andyjdavis/moodle
authorDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 06:40:42 +0000 (14:40 +0800)
committerDan Poltawski <dan@moodle.com>
Thu, 15 Nov 2012 06:40:42 +0000 (14:40 +0800)
200 files changed:
admin/courseformats.php [new file with mode: 0644]
admin/settings/courses.php
admin/settings/plugins.php
backup/moodle2/backup_stepslib.php
blocks/activity_modules/block_activity_modules.php
blocks/community/block_community.php
blocks/course_list/block_course_list.php
blocks/dock.js
blocks/navigation/styles.css
blocks/participants/block_participants.php
blocks/private_files/renderer.php
blocks/settings/styles.css
blocks/site_main_menu/block_site_main_menu.php
cache/classes/definition.php
cache/classes/dummystore.php
cache/classes/interfaces.php
cache/classes/loaders.php
cache/locallib.php
cache/stores/file/lib.php
cache/stores/memcache/lib.php
cache/stores/memcached/lib.php
cache/stores/mongodb/lib.php
cache/stores/session/lib.php
cache/stores/static/lib.php
calendar/managesubscriptions_form.php
cohort/assign.php
course/dndupload.js
course/edit_form.php
course/format/formatlegacy.php
course/format/lib.php
course/format/renderer.php
course/format/scorm/config.php [deleted file]
course/format/scorm/lib.php
course/format/scorm/version.php
course/format/social/config.php [deleted file]
course/format/social/lib.php
course/format/social/version.php
course/format/topics/config.php [deleted file]
course/format/topics/lib.php
course/format/topics/styles.css
course/format/topics/version.php
course/format/upgrade.txt
course/format/weeks/config.php [deleted file]
course/format/weeks/lib.php
course/format/weeks/renderer.php
course/format/weeks/styles.css
course/format/weeks/version.php
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/lib.php
lang/en/admin.php
lang/en/error.php
lang/en/moodle.php
lib/adminlib.php
lib/blocklib.php
lib/csslib.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.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/config.php
theme/base/layout/general.php
theme/base/layout/report.php
theme/base/style/blocks.css
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/dock.css
theme/binarius/config.php
theme/binarius/layout/general.php
theme/binarius/layout/report.php
theme/boxxie/config.php
theme/boxxie/layout/general.php
theme/brick/config.php
theme/brick/layout/general.php
theme/canvas/config.php
theme/canvas/layout/general.php
theme/canvas/layout/report.php
theme/standard/style/blocks.css
theme/standard/style/core.css
theme/standard/style/course.css
theme/standard/style/dock.css
theme/upgrade.txt
user/selector/lib.php
version.php

diff --git a/admin/courseformats.php b/admin/courseformats.php
new file mode 100644 (file)
index 0000000..a8377de
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Allows the admin to enable, disable and uninstall course formats
+ *
+ * @package    core_admin
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once('../config.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/pluginlib.php');
+
+$action  = required_param('action', PARAM_ALPHANUMEXT);
+$formatname   = required_param('format', PARAM_PLUGIN);
+$confirm = optional_param('confirm', 0, PARAM_BOOL);
+
+$syscontext = context_system::instance();
+$PAGE->set_url('/admin/courseformats.php');
+$PAGE->set_context($syscontext);
+
+require_login();
+require_capability('moodle/site:config', $syscontext);
+require_sesskey();
+
+$return = new moodle_url('/admin/settings.php', array('section' => 'manageformats'));
+
+$allplugins = plugin_manager::instance()->get_plugins();
+$formatplugins = $allplugins['format'];
+$sortorder = array_flip(array_keys($formatplugins));
+
+if (!isset($formatplugins[$formatname])) {
+    print_error('courseformatnotfound', 'error', $return, $formatname);
+}
+
+switch ($action) {
+    case 'disable':
+        if ($formatplugins[$formatname]->is_enabled()) {
+            if (get_config('moodlecourse', 'format') === $formatname) {
+                print_error('cannotdisableformat', 'error', $return);
+            }
+            set_config('disabled', 1, 'format_'. $formatname);
+        }
+        break;
+    case 'enable':
+        if (!$formatplugins[$formatname]->is_enabled()) {
+            unset_config('disabled', 'format_'. $formatname);
+        }
+        break;
+    case 'up':
+        if ($sortorder[$formatname]) {
+            $currentindex = $sortorder[$formatname];
+            $seq = array_keys($formatplugins);
+            $seq[$currentindex] = $seq[$currentindex-1];
+            $seq[$currentindex-1] = $formatname;
+            set_config('format_plugins_sortorder', implode(',', $seq));
+        }
+        break;
+    case 'down':
+        if ($sortorder[$formatname] < count($sortorder)-1) {
+            $currentindex = $sortorder[$formatname];
+            $seq = array_keys($formatplugins);
+            $seq[$currentindex] = $seq[$currentindex+1];
+            $seq[$currentindex+1] = $formatname;
+            set_config('format_plugins_sortorder', implode(',', $seq));
+        }
+        break;
+    case 'uninstall':
+        echo $OUTPUT->header();
+        echo $OUTPUT->heading(get_string('courseformats', 'moodle'));
+
+        $coursecount = $DB->count_records('course', array('format' => $formatname));
+        if ($coursecount) {
+            // Check that default format is set. It will be used to convert courses
+            // using this format
+            $defaultformat = get_config('moodlecourse', 'format');
+            $defaultformat = $formatplugins[get_config('moodlecourse', 'format')];
+            if (!$defaultformat) {
+                echo $OUTPUT->error_text(get_string('defaultformatnotset', 'admin'));
+                echo $OUTPUT->footer();
+                exit;
+            }
+        }
+
+        $format = $formatplugins[$formatname];
+        $deleteurl = $format->get_uninstall_url();
+        if (!$deleteurl) {
+            // somebody was trying to cheat and type non-existing link
+            echo $OUTPUT->error_text(get_string('cannotuninstall', 'admin', $format->displayname));
+            echo $OUTPUT->footer();
+            exit;
+        }
+
+        if (!$confirm) {
+            if ($coursecount) {
+                $message = get_string('formatuninstallwithcourses', 'admin',
+                        (object)array('count' => $coursecount, 'format' => $format->displayname,
+                            'defaultformat' => $defaultformat->displayname));
+            } else {
+                $message = get_string('formatuninstallconfirm', 'admin', $format->displayname);
+            }
+            $deleteurl->param('confirm', 1);
+            echo $OUTPUT->confirm($message, $deleteurl, $return);
+        } else {
+            $a = new stdClass();
+            $a->plugin = $format->displayname;
+            $a->directory = $format->rootdir;
+            uninstall_plugin('format', $formatname);
+            echo $OUTPUT->notification(get_string('formatuninstalled', 'admin', $a), 'notifysuccess');
+            echo $OUTPUT->continue_button($return);
+        }
+
+        echo $OUTPUT->footer();
+        exit;
+}
+redirect($return);
index 363bfb1..b68a10a 100644 (file)
@@ -15,9 +15,10 @@ if ($hassiteconfig
 /// NOTE: these settings must be applied after all other settings because they depend on them
     ///main course settings
     $temp = new admin_settingpage('coursesettings', new lang_string('coursesettings'));
-    $courseformats = get_plugin_list('format');
+    require_once($CFG->dirroot.'/course/lib.php');
+    $courseformats = get_sorted_course_formats(true);
     $formcourseformats = array();
-    foreach ($courseformats as $courseformat => $courseformatdir) {
+    foreach ($courseformats as $courseformat) {
         $formcourseformats[$courseformat] = new lang_string('pluginname', "format_$courseformat");
     }
     $temp->add(new admin_setting_configselect('moodlecourse/format', new lang_string('format'), new lang_string('coursehelpformat'), 'weeks',$formcourseformats));
index 8d29028..b7fa57e 100644 (file)
@@ -20,6 +20,15 @@ if ($hassiteconfig) {
     // hidden script for converting journals to online assignments (or something like that) linked from elsewhere
     $ADMIN->add('modsettings', new admin_externalpage('oacleanup', 'Online Assignment Cleanup', $CFG->wwwroot.'/'.$CFG->admin.'/oacleanup.php', 'moodle/site:config', true));
 
+    // course formats
+    $ADMIN->add('modules', new admin_category('formatsettings', new lang_string('courseformats')));
+    $temp = new admin_settingpage('manageformats', new lang_string('manageformats', 'core_admin'));
+    $temp->add(new admin_setting_manageformats());
+    $ADMIN->add('formatsettings', $temp);
+    foreach ($allplugins['format'] as $format) {
+        $format->load_settings($ADMIN, 'formatsettings', $hassiteconfig);
+    }
+
     // blocks
     $ADMIN->add('modules', new admin_category('blocksettings', new lang_string('blocks')));
     $ADMIN->add('blocksettings', new admin_page_manageblocks());
@@ -303,6 +312,10 @@ if ($hassiteconfig) {
 
 // Question type settings
 if ($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) {
+    if (!$hassiteconfig) {
+        require_once("$CFG->libdir/pluginlib.php");
+        $allplugins = plugin_manager::instance()->get_plugins();
+    }
     // Question behaviour settings.
     $ADMIN->add('modules', new admin_category('qbehavioursettings', new lang_string('questionbehaviours', 'admin')));
     $ADMIN->add('qbehavioursettings', new admin_page_manageqbehaviours());
index a82cd02..f2ba2af 100644 (file)
@@ -912,7 +912,7 @@ class backup_gradebook_structure_step extends backup_structure_step {
         $grade_category   = new backup_nested_element('grade_category', array('id'), array(
                 //'courseid',
                 'parent', 'depth', 'path', 'fullname', 'aggregation', 'keephigh',
-                'dropload', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
+                'droplow', 'aggregateonlygraded', 'aggregateoutcomes', 'aggregatesubcats',
                 'timecreated', 'timemodified', 'hidden'));
 
         $letters = new backup_nested_element('grade_letters');
index 0a391b0..7383658 100644 (file)
@@ -53,10 +53,10 @@ class block_activity_modules extends block_list {
 
         foreach ($modfullnames as $modname => $modfullname) {
             if ($modname === 'resources') {
-                $icon = $OUTPUT->pix_icon(file_extension_icon('.htm'), '', 'moodle', array('class' => 'icon')). '&nbsp;';
+                $icon = $OUTPUT->pix_icon('icon', '', 'mod_page', array('class' => 'icon'));
                 $this->content->items[] = '<a href="'.$CFG->wwwroot.'/course/resources.php?id='.$course->id.'">'.$icon.$modfullname.'</a>';
             } else {
-                $icon = '<img src="'.$OUTPUT->pix_url('icon', $modname) . '" class="icon" alt="" />&nbsp;';
+                $icon = '<img src="'.$OUTPUT->pix_url('icon', $modname) . '" class="icon" alt="" />';
                 $this->content->items[] = '<a href="'.$CFG->wwwroot.'/mod/'.$modname.'/index.php?id='.$course->id.'">'.$icon.$modfullname.'</a>';
             }
         }
index c211342..3702f11 100644 (file)
@@ -72,7 +72,7 @@ class block_community extends block_list {
                     'class' => 'icon', 'alt' => get_string('addcourse', 'block_community')));
         $addcourseurl = new moodle_url('/blocks/community/communitycourse.php',
                         array('add' => true, 'courseid' => $this->page->course->id));
-        $searchlink = html_writer::tag('a', $icon . '&nbsp;' . get_string('addcourse', 'block_community'),
+        $searchlink = html_writer::tag('a', $icon . get_string('addcourse', 'block_community'),
                         array('href' => $addcourseurl->out(false)));
         $this->content->items[] = $searchlink;
 
index 2e6fa7c..540c49a 100644 (file)
@@ -23,7 +23,7 @@ class block_course_list extends block_list {
         $this->content->icons = array();
         $this->content->footer = '';
 
-        $icon  = '<img src="' . $OUTPUT->pix_url('i/course') . '" class="icon" alt="" />&nbsp;';
+        $icon  = '<img src="' . $OUTPUT->pix_url('i/course') . '" class="icon" alt="" />';
 
         $adminseesall = true;
         if (isset($CFG->block_course_list_adminview)) {
@@ -109,7 +109,7 @@ class block_course_list extends block_list {
             return;
         }
 
-        $icon = '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="" />&nbsp;';
+        $icon = '<img src="'.$OUTPUT->pix_url('i/mnethost') . '" class="icon" alt="" />';
 
         // shortcut - the rest is only for logged in users!
         if (!isloggedin() || isguestuser()) {
index 34a859d..ca685d5 100644 (file)
@@ -532,10 +532,16 @@ M.core_dock.fixTitleOrientation = function(item, title, text) {
         'position' : 'relative',
         'fontSize' : fontsize,
         'width' : width,
-        'top' : width/2,
-        'right' : width/2 - height
+        'top' : width/2
     });
 
+    // Positioning is different when in RTL mode.
+    if (Y.one(document.body).hasClass('dir-rtl')) {
+        title.setStyle('left', width/2 - height);
+    } else {
+        title.setStyle('right', width/2 - height);
+    }
+
     // Rotate the text
     title.setStyles({
         'transform' : transform,
@@ -922,7 +928,7 @@ M.core_dock.genericblock.prototype = {
             }, this);
             // Add a close icon
             // Must set the image src seperatly of we get an error with XML strict headers
-            var closeicon = Y.Node.create('<span class="hidepanelicon" tabindex="0"><img alt="" style="width:11px;height:11px;cursor:pointer;" /></span>');
+            var closeicon = Y.Node.create('<span class="hidepanelicon" tabindex="0"><img alt="" /></span>');
             closeicon.one('img').setAttribute('src', M.util.image_url('t/dockclose', 'moodle'));
             closeicon.on('forceclose|click', this.hide, this);
             closeicon.on('dock:actionkey',this.hide, this, {actions:{enter:true,toggle:true}});
index 6da9053..6d08f5e 100644 (file)
@@ -1,19 +1,23 @@
 /** General display rules **/
 .block_navigation .block_tree {margin:5px;padding-left:0px;overflow:visible;}
 .block_navigation .block_tree li {margin:3px;list-style: none;padding:0;}
-.block_navigation .block_tree li.item_with_icon > p {position:relative;}
-.block_navigation .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:3px}
+.block_navigation .block_tree li.item_with_icon > p {position:relative; padding-left: 21px;}
+.block_navigation .block_tree li.item_with_icon > p img,
+.block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
+.block_navigation .block_tree li > p.hasicon img {vertical-align:middle;position:absolute;left:0;top:-1px;width:16px;height:16px;}
 .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left:16px;}
-.block_navigation .block_tree li.item_with_icon.contains_branch .tree_item {padding-left:34px;}
+.block_navigation .block_tree .type_activity > p.branch.hasicon,
+.block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-left:37px;}
 
 .block_navigation .block_tree li ul {padding-left:0;margin:0;}
 .block_navigation .block_tree li.depth_2 ul {padding-left:16px;margin:0;}
-.block_navigation .block_tree .tree_item {padding-left: 18px;margin:3px 0px;text-align:left;}
+.block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
+.block_navigation .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
-.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
+.block_navigation .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 0;background-repeat: no-repeat;}
 .block_navigation .block_tree .tree_item.branch.navigation_node {background-image:none;padding-left:0;}
 .block_navigation .block_tree .type_activity > .tree_item.branch {background-image:none;position:relative;}
-.block_navigation .block_tree .type_activity > .tree_item.branch img {position:absolute;left:0;}
+.block_navigation .block_tree .type_activity > .tree_item.branch img {left: 16px;}
 .block_navigation .block_tree .root_node.leaf {padding-left:0px;}
 .block_navigation .block_tree .active_tree_node {font-weight:bold;}
 .block_navigation .block_tree .depth_1.current_branch ul {font-weight:normal;}
@@ -21,8 +25,9 @@
 .dock .block_navigation .tree_item {white-space: nowrap;}
 
 .jsenabled .block_navigation .block_tree .tree_item.branch {cursor:pointer;}
-.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0% 5%;background-repeat: no-repeat;}
+.jsenabled .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 0;background-repeat: no-repeat;}
 .jsenabled .block_navigation .block_tree .collapsed ul {display: none;}
+.jsenabled .block_navigation .block_tree .type_activity > .tree_item.branch {background-image: url([[pix:t/expanded]]);}
 .jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
 .jsenabled .block_navigation .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
 
@@ -35,8 +40,9 @@
 .ie6 .block_navigation .block_tree .tree_item {width:100%;}
 
 /** Overide for RTL layout **/
-.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left:0;padding-right: 7px;}
-.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 18px;text-align:right;}
+.dir-rtl .block_navigation .block_tree li.depth_2 ul {padding-left:0;padding-right: 16px; padding-left: 0;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.branch.hasicon.active_tree_node,
+.dir-rtl .block_navigation .block_tree .tree_item {padding-right: 21px;text-align:right;}
 
 .dir-rtl .block_navigation .block_tree .tree_item.branch {background-position: center right;}
 
 .dir-rtl .block_navigation .block_tree .root_node.leaf {padding-right:0;}
 
 .dir-rtl .block_navigation .block_tree li.item_with_icon > p img,
-.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right:0;left:auto;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.tree_item.active_tree_node img,
+.dir-rtl .block_navigation .block_tree li > p.hasicon img {left:auto; right:0;}
+.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > p img {left: auto; right:16px;}
+.dir-rtl .block_navigation .block_tree .type_activity > p.branch.hasicon,
+.dir-rtl .block_navigation .block_tree li.item_with_icon.contains_branch > .tree_item {padding-right:37px; padding-left: 0;}
+.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {right: 16px; left: auto;}
 
 .jsenabled.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
 .jsenabled.dir-rtl .block_navigation .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
index c46a5aa..82889f8 100644 (file)
@@ -37,7 +37,7 @@ class block_participants extends block_list {
             }
         }
 
-        $icon = '<img src="'.$OUTPUT->pix_url('i/users') . '" class="icon" alt="" />&nbsp;';
+        $icon = '<img src="'.$OUTPUT->pix_url('i/users') . '" class="icon" alt="" />';
         $this->content->items[] = '<a title="'.get_string('listofallpeople').'" href="'.
                                   $CFG->wwwroot.'/user/index.php?contextid='.$currentcontext->id.'">'.$icon.get_string('participants').'</a>';
 
index e931219..f84d2d7 100644 (file)
@@ -66,13 +66,13 @@ class block_private_files_renderer extends plugin_renderer_base {
         $result = '<ul>';
         foreach ($dir['subdirs'] as $subdir) {
             $image = $this->output->pix_icon(file_folder_icon(), $subdir['dirname'], 'moodle', array('class'=>'icon'));
-            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.$image.' '.s($subdir['dirname']).'</div> '.$this->htmllize_tree($tree, $subdir).'</li>';
+            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.$image.s($subdir['dirname']).'</div> '.$this->htmllize_tree($tree, $subdir).'</li>';
         }
         foreach ($dir['files'] as $file) {
             $url = file_encode_url("$CFG->wwwroot/pluginfile.php", '/'.$tree->context->id.'/user/private'.$file->get_filepath().$file->get_filename(), true);
             $filename = $file->get_filename();
             $image = $this->output->pix_icon(file_file_icon($file), $filename, 'moodle', array('class'=>'icon'));
-            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.html_writer::link($url, $image.'&nbsp;'.$filename).'</div></li>';
+            $result .= '<li yuiConfig=\''.json_encode($yuiconfig).'\'><div>'.html_writer::link($url, $image.$filename).'</div></li>';
         }
         $result .= '</ul>';
 
index cfa9ab8..2c6e451 100644 (file)
@@ -6,12 +6,12 @@
 /** General display rules **/
 .block_settings .block_tree {margin:5px;padding-left:0px;overflow:visible;}
 .block_settings .block_tree li {margin:0;list-style: none;}
-.block_settings .block_tree li ul {padding-left:16px;margin:0;}
+.block_settings .block_tree li ul {padding-left:18px;margin:0;}
 
 .block_settings .block_tree li.item_with_icon > p {position:relative;}
-.block_settings .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:-1px}
+.block_settings .block_tree li.item_with_icon > p img {vertical-align:middle;position:absolute;left:0;top:-1px; width: 16px; height: 16px;}
 
-.block_settings .block_tree .tree_item {padding-left: 18px;margin:3px 0px;text-align:left;}
+.block_settings .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
 .block_settings .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
 .block_settings .block_tree .root_node.leaf {padding-left:0px;}
 
 /** Overide for RTL layout **/
 .dir-rtl .block_settings .block_tree {padding-right:0px;}
-.dir-rtl .block_settings .block_tree li ul {padding-left:0;padding-right: 7px;}
-.dir-rtl .block_settings .block_tree li.item_with_icon > p img,
-.dir-rtl .block_navigation .block_tree .type_activity > .tree_item.branch img {left:auto;right:0;}
-.dir-rtl .block_settings .block_tree .tree_item {padding-right: 18px;text-align:right;}
+.dir-rtl .block_settings .block_tree li ul {padding-left:0;padding-right: 18px;}
+.dir-rtl .block_settings .block_tree .tree_item {padding-right: 21px; padding-left: 0; text-align:right;}
 .dir-rtl .block_settings .block_tree .tree_item.branch {background-position: center right;}
 .dir-rtl .block_settings .block_tree .root_node.leaf {padding-right:0px;}
+.dir-rtl .block_settings .block_tree li.item_with_icon > p img { right: 0; left: auto;}
 
 .jsenabled.dir-rtl .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
-.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
\ No newline at end of file
+.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
index 39a550e..d4374bc 100644 (file)
@@ -50,7 +50,7 @@ class block_site_main_menu extends block_list {
                     } else {
                         $linkcss = $cm->visible ? '' : ' class="dimmed" ';
                         //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />&nbsp;';
+                        $icon = '<img src="' . $cm->get_icon_url() . '" class="icon" alt="" />';
                         $this->content->items[] = '<a title="'.$cm->modplural.'" '.$linkcss.' '.$cm->extra.
                                 ' href="' . $url . '">' . $icon . $instancename . '</a>';
                     }
@@ -120,7 +120,7 @@ class block_site_main_menu extends block_list {
                         $this->content->icons[] = '';
                     } else {
                         //Accessibility: incidental image - should be empty Alt text
-                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />&nbsp;';
+                        $icon = '<img src="' . $mod->get_icon_url() . '" class="icon" alt="" />';
                         $this->content->items[] = '<a title="' . $mod->modfullname . '" ' . $linkcss . ' ' . $mod->extra .
                             ' href="' . $url . '">' . $icon . $instancename . '</a>' . $editbuttons;
                     }
index d02e082..fd93324 100644 (file)
@@ -290,10 +290,10 @@ class cache_definition {
             throw new coding_exception('You must provide a mode when creating a cache definition');
         }
         if (!array_key_exists('component', $definition)) {
-            throw new coding_exception('You must provide a mode when creating a cache definition');
+            throw new coding_exception('You must provide a component when creating a cache definition');
         }
         if (!array_key_exists('area', $definition)) {
-            throw new coding_exception('You must provide a mode when creating a cache definition');
+            throw new coding_exception('You must provide an area when creating a cache definition');
         }
         $mode = (int)$definition['mode'];
         $component = (string)$definition['component'];
@@ -638,7 +638,7 @@ class cache_definition {
      */
     public function get_data_source() {
         if (!$this->has_data_source()) {
-            throw new coding_exception('This cache does not use a datasource.');
+            throw new coding_exception('This cache does not use a data source.');
         }
         return forward_static_call(array($this->datasource, 'get_instance_for_cache'), $this);
     }
index 870d960..d44dc8c 100644 (file)
@@ -147,7 +147,7 @@ class cachestore_dummy implements cache_store {
      * Returns true if this store supports multiple identifiers.
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
@@ -264,7 +264,7 @@ class cachestore_dummy implements cache_store {
     public static function initialise_test_instance(cache_definition $definition) {
         $cache = new cachestore_dummy('Dummy store test');
         $cache->initialise($definition);
-        return $cache;;
+        return $cache;
     }
 
     /**
@@ -274,4 +274,4 @@ class cachestore_dummy implements cache_store {
     public function my_name() {
         return $this->name;
     }
-}
\ No newline at end of file
+}
index 1f96fa0..983bc6b 100644 (file)
@@ -194,8 +194,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if the lock could be acquired, false otherwise.
@@ -207,8 +207,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if this code has the lock, false if there is a lock but this code doesn't have it,
@@ -221,8 +221,8 @@ interface cache_loader_with_locking {
      *
      * Please note that this happens automatically if the cache definition requires locking.
      * it is still made a public method so that adhoc caches can use it if they choose.
-     * However this doesn't guarantee consistent access. It will become the reponsiblity of the calling code to ensure locks
-     * are acquired, checked, and released.
+     * However this doesn't guarantee consistent access. It will become the responsibility of the calling code to ensure
+     * locks are acquired, checked, and released.
      *
      * @param string|int $key
      * @return bool True if the lock has been released, false if there was a problem releasing the lock.
@@ -314,7 +314,7 @@ interface cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers();
+    public function supports_multiple_identifiers();
 
     /**
      * Returns true if this cache store instance promotes data guarantee.
index d0b3a4d..c837cd8 100644 (file)
@@ -18,7 +18,7 @@
  * Cache loaders
  *
  * This file is part of Moodle's cache API, affectionately called MUC.
- * It contains the components that are requried in order to use caching.
+ * It contains the components that are required in order to use caching.
  *
  * @package    core
  * @category   cache
@@ -795,7 +795,7 @@ class cache implements cache_loader {
      */
     protected function parse_key($key) {
         // First up if the store supports multiple keys we'll go with that.
-        if ($this->store->supports_multiple_indentifiers()) {
+        if ($this->store->supports_multiple_identifiers()) {
             $result = $this->definition->generate_multi_key_parts();
             $result['key'] = $key;
             return $result;
index fc927e0..e8e43a3 100644 (file)
@@ -518,7 +518,7 @@ abstract class cache_administration_helper extends cache_helper {
                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
                 ),
                 'supports' => array(
-                    'multipleidentifiers' => $store->supports_multiple_indentifiers(),
+                    'multipleidentifiers' => $store->supports_multiple_identifiers(),
                     'dataguarantee' => $store->supports_data_guarantee(),
                     'nativettl' => $store->supports_native_ttl(),
                     'nativelocking' => ($store instanceof cache_is_lockable),
index 0aa3f48..0a18b3a 100644 (file)
@@ -209,7 +209,7 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
@@ -255,8 +255,11 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      * Pre-scan the cache to see which keys are present.
      */
     protected function prescan_keys() {
-        foreach (glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT) as $filename) {
-            $this->keys[basename($filename)] = filemtime($filename);
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
+                $this->keys[basename($filename)] = filemtime($filename);
+            }
         }
     }
 
@@ -510,8 +513,11 @@ class cachestore_file implements cache_store, cache_is_key_aware {
      * @return boolean True on success. False otherwise.
      */
     public function purge() {
-        foreach (glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT) as $filename) {
-            @unlink($filename);
+        $files = glob($this->glob_keys_pattern(), GLOB_MARK | GLOB_NOSORT);
+        if (is_array($files)) {
+            foreach ($files as $filename) {
+                @unlink($filename);
+            }
         }
         $this->keys = array();
         return true;
index d684403..d62fac7 100644 (file)
@@ -178,7 +178,7 @@ class cachestore_memcache implements cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 527a5ca..419e640 100644 (file)
@@ -204,7 +204,7 @@ class cachestore_memcached implements cache_store {
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 4bd4229..1931bdb 100644 (file)
@@ -230,7 +230,7 @@ class cachestore_mongodb implements cache_store {
      * Returns true if this store is making use of multiple identifiers.
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return $this->extendedmode;
     }
 
@@ -239,7 +239,7 @@ class cachestore_mongodb implements cache_store {
      * @return bool
      */
     public function supports_native_ttl() {
-        return false;;
+        return false;
     }
 
     /**
@@ -517,4 +517,4 @@ class cachestore_mongodb implements cache_store {
     public function my_name() {
         return $this->name;
     }
-}
\ No newline at end of file
+}
index bd9d0a0..ba82c17 100644 (file)
@@ -127,7 +127,7 @@ class cachestore_session extends session_data_store implements cache_store, cach
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
index 8e0f41c..7a7bd5a 100644 (file)
@@ -127,7 +127,7 @@ class cachestore_static extends static_data_store implements cache_store, cache_
      *
      * @return bool
      */
-    public function supports_multiple_indentifiers() {
+    public function supports_multiple_identifiers() {
         return false;
     }
 
@@ -360,7 +360,7 @@ class cachestore_static extends static_data_store implements cache_store, cache_
         // Do something here perhaps.
         $cache = new cachestore_static('Static store');
         $cache->initialise($definition);
-        return $cache;;
+        return $cache;
     }
 
     /**
@@ -422,4 +422,4 @@ abstract class static_data_store {
             self::$staticstore[$id] = array();
         }
     }
-}
\ No newline at end of file
+}
index 12c3135..50a0903 100644 (file)
@@ -58,7 +58,8 @@ class calendar_addsubscription_form extends moodleform {
 
         // URL.
         $mform->addElement('text', 'url', get_string('importfromurl', 'calendar'), array('maxsize' => '255', 'size' => '50'));
-        $mform->setType('url', PARAM_URL);
+        // Cannot set as PARAM_URL since we need to allow webcal:// protocol.
+        $mform->setType('url', PARAM_RAW);
 
         // Import file
         $mform->addElement('filepicker', 'importfile', get_string('importfromfile', 'calendar'));
@@ -103,12 +104,21 @@ class calendar_addsubscription_form extends moodleform {
      */
     public function validation($data, $files) {
         $errors = parent::validation($data, $files);
-        if (empty($data['url']) && empty($data['importfile'])) {
+        $url = $data['url'];
+        if (empty($url) && empty($data['importfile'])) {
             if (!empty($data['importfrom']) && $data['importfrom'] == CALENDAR_IMPORT_FROM_FILE) {
                 $errors['importfile'] = get_string('errorrequiredurlorfile', 'calendar');
             } else {
                 $errors['url'] = get_string('errorrequiredurlorfile', 'calendar');
             }
+        } elseif (!empty($url)) {
+            // Url is webcal protocol which is not accepted by PARAM_URL.
+            if (stripos($url, "webcal://") === 0) {
+                $url = substr($url, strlen("webcal://"));
+            }
+            if (clean_param($url, PARAM_URL) !== $url) {
+                $errors['url']  = get_string('invalidurl', 'error');
+            }
         }
         return $errors;
     }
index 0d6c83a..2da394d 100644 (file)
@@ -68,8 +68,8 @@ echo $OUTPUT->heading(get_string('assignto', 'cohort', format_string($cohort->na
 echo $OUTPUT->notification(get_string('removeuserwarning', 'core_cohort'));
 
 // Get the user_selector we will need.
-$potentialuserselector = new cohort_candidate_selector('addselect', array('cohortid'=>$cohort->id));
-$existinguserselector = new cohort_existing_selector('removeselect', array('cohortid'=>$cohort->id));
+$potentialuserselector = new cohort_candidate_selector('addselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
+$existinguserselector = new cohort_existing_selector('removeselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
 
 // Process incoming user assignments to the cohort
 
index 2dcaa13..e1d1b11 100644 (file)
@@ -524,6 +524,7 @@ M.course_dndupload = {
         preview.li.appendChild(preview.div);
 
         preview.icon.src = M.util.image_url('t/addfile');
+        preview.icon.className = 'icon';
         preview.div.appendChild(preview.icon);
 
         preview.div.appendChild(document.createTextNode(' '));
index 70b709a..34dbe12 100644 (file)
@@ -113,11 +113,20 @@ class course_edit_form extends moodleform {
             $mform->hardFreeze('summary_editor');
         }
 
-        $courseformats = get_plugin_list('format');
+        $courseformats = get_sorted_course_formats(true);
         $formcourseformats = array();
-        foreach ($courseformats as $courseformat => $formatdir) {
+        foreach ($courseformats as $courseformat) {
             $formcourseformats[$courseformat] = get_string('pluginname', "format_$courseformat");
         }
+        if (isset($course->format)) {
+            $course->format = course_get_format($course)->get_format(); // replace with default if not found
+            if (!in_array($course->format, $courseformats)) {
+                // this format is disabled. Still display it in the dropdown
+                $formcourseformats[$course->format] = get_string('withdisablednote', 'moodle',
+                        get_string('pluginname', 'format_'.$course->format));
+            }
+        }
+
         $mform->addElement('select', 'format', get_string('format'), $formcourseformats);
         $mform->addHelpButton('format', 'format');
         $mform->setDefault('format', $courseconfig->format);
index 838c6fc..82af237 100644 (file)
@@ -339,9 +339,21 @@ class format_legacy extends format_base {
         if ($oldcourse !== null) {
             $data = (array)$data;
             $oldcourse = (array)$oldcourse;
-            foreach ($this->course_format_options() as $key => $unused) {
-                if (array_key_exists($key, $oldcourse) && !array_key_exists($key, $data)) {
-                    $data[$key] = $oldcourse[$key];
+            $options = $this->course_format_options();
+            foreach ($options as $key => $unused) {
+                if (!array_key_exists($key, $data)) {
+                    if (array_key_exists($key, $oldcourse)) {
+                        $data[$key] = $oldcourse[$key];
+                    } else if ($key === 'numsections') {
+                        // If previous format does not have the field 'numsections' and this one does,
+                        // and $data['numsections'] is not set fill it with the maximum section number from the DB
+                        $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                            WHERE course = ?', array($this->courseid));
+                        if ($maxsection) {
+                            // If there are no sections, or just default 0-section, 'numsections' will be set to default
+                            $data['numsections'] = $maxsection;
+                        }
+                    }
                 }
             }
         }
index 234f2e4..f0ccf9b 100644 (file)
@@ -97,13 +97,21 @@ abstract class format_base {
         if ($format === 'site') {
             return $format;
         }
-        $plugins = get_plugin_list('format'); // TODO MDL-35260 filter only enabled
-        if (isset($plugins[$format])) {
+        $plugins = get_sorted_course_formats();
+        if (in_array($format, $plugins)) {
             return $format;
         }
         // Else return default format
-        $defaultformat = reset($plugins); // TODO MDL-35260 get default format from config
-        debugging('Format plugin format_'.$format.' is not found or is not enabled. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
+        $defaultformat = get_config('moodlecourse', 'format');
+        if (!in_array($defaultformat, $plugins)) {
+            // when default format is not set correctly, use the first available format
+            $defaultformat = reset($plugins);
+        }
+        static $warningprinted = array();
+        if (empty($warningprinted[$format])) {
+            debugging('Format plugin format_'.$format.' is not found. Using default format_'.$defaultformat, DEBUG_DEVELOPER);
+            $warningprinted[$format] = true;
+        }
         return $defaultformat;
     }
 
@@ -785,6 +793,90 @@ abstract class format_base {
      */
     public function page_set_cm(moodle_page $page) {
     }
+
+    /**
+     * Course-specific information to be output on any course page (usually above navigation bar)
+     *
+     * Example of usage:
+     * define
+     * class format_FORMATNAME_XXX implements renderable {}
+     *
+     * create format renderer in course/format/FORMATNAME/renderer.php, define rendering function:
+     * class format_FORMATNAME_renderer extends plugin_renderer_base {
+     *     protected function render_format_FORMATNAME_XXX(format_FORMATNAME_XXX $xxx) {
+     *         return html_writer::tag('div', 'This is my header/footer');
+     *     }
+     * }
+     *
+     * Return instance of format_FORMATNAME_XXX in this function, the appropriate method from
+     * plugin renderer will be called
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_header() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output on any course page (usually in the beginning of
+     * standard footer)
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_footer() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output immediately above content on any course page
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_content_header() {
+        return null;
+    }
+
+    /**
+     * Course-specific information to be output immediately below content on any course page
+     *
+     * See {@link format_base::course_header()} for usage
+     *
+     * @return null|renderable null for no output or object with data for plugin renderer
+     */
+    public function course_content_footer() {
+        return null;
+    }
+
+    /**
+     * Returns instance of page renderer used by this plugin
+     *
+     * @param moodle_page $page
+     * @return renderer_base
+     */
+    public function get_renderer(moodle_page $page) {
+        return $page->get_renderer('format_'. $this->get_format());
+    }
+
+    /**
+     * Returns true if the specified section is current
+     *
+     * By default we analyze $course->marker
+     *
+     * @param int|stdClass|section_info $section
+     * @return bool
+     */
+    public function is_section_current($section) {
+        if (is_object($section)) {
+            $sectionnum = $section->section;
+        } else {
+            $sectionnum = $section;
+        }
+        return ($sectionnum && ($course = $this->get_course()) && $course->marker == $sectionnum);
+    }
 }
 
 /**
index ea83ace..1856c62 100644 (file)
@@ -108,7 +108,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
 
         if ($section->section != 0) {
             // Only in the non-general sections.
-            if ($this->is_section_current($section, $course)) {
+            if (course_get_format($course)->is_section_current($section)) {
                 $o = get_accesshide(get_string('currentsection', 'format_'.$course->format));
             }
         }
@@ -137,7 +137,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             // Only in the non-general sections.
             if (!$section->visible) {
                 $sectionstyle = ' hidden';
-            } else if ($this->is_section_current($section, $course)) {
+            } else if (course_get_format($course)->is_section_current($section)) {
                 $sectionstyle = ' current';
             }
         }
@@ -282,7 +282,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         if (!$section->visible) {
             $classattr .= ' hidden';
             $linkclasses .= ' dimmed_text';
-        } else if ($this->is_section_current($section, $course)) {
+        } else if (course_get_format($course)->is_section_current($section)) {
             $classattr .= ' current';
         }
 
@@ -760,14 +760,18 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
     }
 
     /**
-     * Is the section passed in the current section? (Note this isn't strictly
-     * a renderering method, but neater here).
+     * Is the section passed in the current section?
+     *
+     * @deprecated since 2.4
+     * @see format_base::is_section_current()
      *
      * @param stdClass $course The course entry from DB
      * @param stdClass $section The course_section entry from the DB
      * @return bool true if the section is current
      */
-    protected function is_section_current($section, $course) {
-        return ($course->marker == $section->section);
+    protected final function is_section_current($section, $course) {
+        debugging('Function format_section_renderer_base::is_section_current() is deprecated. '.
+                'Use course_get_format($course)->is_section_current($section) instead', DEBUG_DEVELOPER);
+        return course_get_format($course)->is_section_current($section);
     }
 }
diff --git a/course/format/scorm/config.php b/course/format/scorm/config.php
deleted file mode 100644 (file)
index 13bd6d6..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-//
-// Optional course format configuration file
-//
-// This file contains any specific configuration settings for the
-// social format.
-//
-// The default blocks layout for this course format:
-    $format['defaultblocks'] = ':news_items,recent_activity,calendar_upcoming';
-
-
index 5fe4710..0bb518d 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains general functions for the course format SCORM
+ * This file contains main class for the course format SCORM
  *
- * @since 2.0
- * @package moodlecore
+ * @since     2.0
+ * @package   format_scorm
  * @copyright 2009 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-/**
- * The string that is used to describe a section of the course
- * e.g. Topic, Week...
- *
- * @return string
- */
-function callback_scorm_definition() {
-    return get_string('scorm');
-}
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
 
 /**
- * Toogle display of course contents (sections, activities)
+ * Main class for the Scorm course format
  *
- * @return bool
+ * @package    format_scorm
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-function callback_scorm_display_content() {
-    return false;
-}
+class format_scorm extends format_base {
+
+    /**
+     * The URL to use for the specified course
+     *
+     * @param int|stdClass $section Section object from database or just field course_sections.section
+     *     if null the course view page is returned
+     * @param array $options options for view URL. At the moment core uses:
+     *     'navigation' (bool) if true and section has no separate page, the function returns null
+     *     'sr' (int) used by multipage formats to specify to which section to return
+     * @return null|moodle_url
+     */
+    public function get_view_url($section, $options = array()) {
+        if (!empty($options['navigation']) && $section !== null) {
+            return null;
+        }
+        return new moodle_url('/course/view.php', array('id' => $this->courseid));
+    }
 
+    /**
+     * Loads all of the course sections into the navigation
+     *
+     * @param global_navigation $navigation
+     * @param navigation_node $node The course node within the navigation
+     */
+    public function extend_course_navigation($navigation, navigation_node $node) {
+        // Scorm course format does not extend course navigation
+    }
 
+    /**
+     * Returns the list of blocks to be automatically added for the newly created course
+     *
+     * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
+     *     each of values is an array of block names (for left and right side columns)
+     */
+    public function get_default_blocks() {
+        return array(
+            BLOCK_POS_LEFT => array(),
+            BLOCK_POS_RIGHT => array('news_items', 'recent_activity', 'calendar_upcoming')
+        );
+    }
+}
index 2583df5..5890e40 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012061700;        // Requires this Moodle version
+$plugin->version   = 2012110900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012110900;        // Requires this Moodle version
 $plugin->component = 'format_scorm';    // Full name of the plugin (used for diagnostics)
diff --git a/course/format/social/config.php b/course/format/social/config.php
deleted file mode 100644 (file)
index ed05335..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-<?php
-//
-// Optional course format configuration file
-//
-// This file contains any specific configuration settings for the
-// social format.
-//
-// The default blocks layout for this course format:
-    $format['defaultblocks'] = ':search_forums,calendar_upcoming,social_activities,recent_activity,course_list';
-
-
index 6a9629d..3c84a15 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains general functions for the course format Social
+ * This file contains main class for the course format Social
  *
- * @since 2.0
- * @package moodlecore
+ * @since     2.0
+ * @package   format_social
  * @copyright 2009 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-/**
- * Used to display the course structure for a course where format=social
- *
- * This is called automatically by {@link load_course()} if the current course
- * format = weeks.
- *
- * @param array $path An array of keys to the course node in the navigation
- * @param stdClass $modinfo The mod info object for the current course
- * @return bool Returns true
- */
-function callback_social_load_content(&$navigation, $course, $coursenode) {
-    return $navigation->load_generic_course_sections($course, $coursenode, 'social');
-}
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
 
 /**
- * Used to display the course structure for a course where format=social
+ * Main class for the Social course format
  *
- * This is called automatically by {@link load_course()} if the current course
- * format = weeks and the navigation was requested via AJAX
- *
- * @param array $path An array of keys to the course node in the navigation
- * @param stdClass $modinfo The mod info object for the current course
- * @return bool Returns true
+ * @package    format_social
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-function limited_load_section_social(&$navigation, $keys, $course, $section) {
-    $navigation->limited_load_section_generic($keys, $course, $section, get_string('social'), 'social');
-}
+class format_social extends format_base {
 
-/**
- * Indicates this format uses sections.
- *
- * @return bool Returns true
- */
-function callback_social_uses_sections() {
-    return false;
-}
+    /**
+     * The URL to use for the specified course
+     *
+     * @param int|stdClass $section Section object from database or just field course_sections.section
+     *     if null the course view page is returned
+     * @param array $options options for view URL. At the moment core uses:
+     *     'navigation' (bool) if true and section has no separate page, the function returns null
+     *     'sr' (int) used by multipage formats to specify to which section to return
+     * @return null|moodle_url
+     */
+    public function get_view_url($section, $options = array()) {
+        if (!empty($options['navigation']) && $section !== null) {
+            return null;
+        }
+        return new moodle_url('/course/view.php', array('id' => $this->courseid));
+    }
 
-/**
- * The string that is used to describe a section of the course
- * e.g. Topic, Week...
- *
- * @return string
- */
-function callback_social_definition() {
-    return get_string('topic');
-}
+    /**
+     * Loads all of the course sections into the navigation
+     *
+     * @param global_navigation $navigation
+     * @param navigation_node $node The course node within the navigation
    */
+    public function extend_course_navigation($navigation, navigation_node $node) {
+        // Social course format does not extend navigation, it uses social_activities block instead
+    }
 
-/**
- * Toogle display of course contents (sections, activities)
- *
- * @return bool
- */
-function callback_social_display_content() {
-    return false;
+    /**
+     * Returns the list of blocks to be automatically added for the newly created course
+     *
+     * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
+     *     each of values is an array of block names (for left and right side columns)
+     */
+    public function get_default_blocks() {
+        return array(
+            BLOCK_POS_LEFT => array(),
+            BLOCK_POS_RIGHT => array('search_forums', 'calendar_upcoming', 'social_activities',
+                'recent_activity', 'course_list')
+        );
+    }
 }
-
-
index 7fe4f6a..2002e73 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012061700;        // Requires this Moodle version
+$plugin->version   = 2012110900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->requires  = 2012110900;        // Requires this Moodle version
 $plugin->component = 'format_social';   // Full name of the plugin (used for diagnostics)
diff --git a/course/format/topics/config.php b/course/format/topics/config.php
deleted file mode 100644 (file)
index 1a429b4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-//
-// Optional course format configuration file
-//
-// This file contains any specific configuration settings for the
-// format.
-//
-// The default blocks layout for this course format:
-    $format['defaultblocks'] = ':search_forums,news_items,calendar_upcoming,recent_activity';
-//
index 7c84084..0f783e1 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains general functions for the course format Topic
+ * This file contains main class for the course format Topic
  *
- * @since 2.0
- * @package moodlecore
+ * @since     2.0
+ * @package   format_topics
  * @copyright 2009 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
 
 /**
- * Indicates this format uses sections.
+ * Main class for the Topics course format
  *
- * @return bool Returns true
+ * @package    format_topics
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-function callback_topics_uses_sections() {
-    return true;
-}
+class format_topics extends format_base {
 
-/**
- * Used to display the course structure for a course where format=topic
- *
- * This is called automatically by {@link load_course()} if the current course
- * format = weeks.
- *
- * @param array $path An array of keys to the course node in the navigation
- * @param stdClass $modinfo The mod info object for the current course
- * @return bool Returns true
- */
-function callback_topics_load_content(&$navigation, $course, $coursenode) {
-    return $navigation->load_generic_course_sections($course, $coursenode, 'topics');
-}
+    /**
+     * Returns true if this course format uses sections
+     *
+     * @return bool
+     */
+    public function uses_sections() {
+        return true;
+    }
 
-/**
- * The string that is used to describe a section of the course
- * e.g. Topic, Week...
- *
- * @return string
- */
-function callback_topics_definition() {
-    return get_string('topic');
-}
+    /**
+     * Returns the display name of the given section that the course prefers.
+     *
+     * Use section name is specified by user. Otherwise use default ("Topic #")
+     *
+     * @param int|stdClass $section Section object from database or just field section.section
+     * @return string Display name that the course format prefers, e.g. "Topic 2"
+     */
+    public function get_section_name($section) {
+        $section = $this->get_section($section);
+        if ((string)$section->name !== '') {
+            return format_string($section->name, true,
+                    array('context' => context_course::instance($this->courseid)));
+        } else if ($section->section == 0) {
+            return get_string('section0name', 'format_topics');
+        } else {
+            return get_string('topic').' '.$section->section;
+        }
+    }
 
-function callback_topics_get_section_name($course, $section) {
-    // We can't add a node without any text
-    if ((string)$section->name !== '') {
-        return format_string($section->name, true, array('context' => context_course::instance($course->id)));
-    } else if ($section->section == 0) {
-        return get_string('section0name', 'format_topics');
-    } else {
-        return get_string('topic').' '.$section->section;
+    /**
+     * The URL to use for the specified course (with section)
+     *
+     * @param int|stdClass $section Section object from database or just field course_sections.section
+     *     if omitted the course view page is returned
+     * @param array $options options for view URL. At the moment core uses:
+     *     'navigation' (bool) if true and section has no separate page, the function returns null
+     *     'sr' (int) used by multipage formats to specify to which section to return
+     * @return null|moodle_url
+     */
+    public function get_view_url($section, $options = array()) {
+        $course = $this->get_course();
+        $url = new moodle_url('/course/view.php', array('id' => $course->id));
+
+        $sr = null;
+        if (array_key_exists('sr', $options)) {
+            $sr = $options['sr'];
+        }
+        if (is_object($section)) {
+            $sectionno = $section->section;
+        } else {
+            $sectionno = $section;
+        }
+        if ($sectionno !== null) {
+            if ($sr !== null) {
+                if ($sr) {
+                    $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE;
+                    $sectionno = $sr;
+                } else {
+                    $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE;
+                }
+            } else {
+                $usercoursedisplay = $course->coursedisplay;
+            }
+            if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+                $url->param('section', $sectionno);
+            } else {
+                if (!empty($options['navigation'])) {
+                    return null;
+                }
+                $url->set_anchor('section-'.$sectionno);
+            }
+        }
+        return $url;
     }
-}
 
-/**
- * Declares support for course AJAX features
- *
- * @see course_format_ajax_support()
- * @return stdClass
- */
-function callback_topics_ajax_support() {
-    $ajaxsupport = new stdClass();
-    $ajaxsupport->capable = true;
-    $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
-    return $ajaxsupport;
-}
+    /**
+     * Returns the information about the ajax support in the given source format
+     *
+     * The returned object's property (boolean)capable indicates that
+     * the course format supports Moodle course ajax features.
+     * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+     *
+     * @return stdClass
+     */
+    public function supports_ajax() {
+        $ajaxsupport = new stdClass();
+        $ajaxsupport->capable = true;
+        $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
+        return $ajaxsupport;
+    }
 
-/**
- * Callback function to do some action after section move
- *
- * @param stdClass $course The course entry from DB
- * @return array This will be passed in ajax respose.
- */
-function callback_topics_ajax_section_move($course) {
-    global $COURSE, $PAGE;
+    /**
+     * Loads all of the course sections into the navigation
+     *
+     * @param global_navigation $navigation
+     * @param navigation_node $node The course node within the navigation
+     */
+    public function extend_course_navigation($navigation, navigation_node $node) {
+        global $PAGE;
+        // if section is specified in course/view.php, make sure it is expanded in navigation
+        if ($navigation->includesectionnum === false) {
+            $selectedsection = optional_param('section', null, PARAM_INT);
+            if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
+                    $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+                $navigation->includesectionnum = $selectedsection;
+            }
+        }
+
+        // check if there are callbacks to extend course navigation
+        parent::extend_course_navigation($navigation, $node);
+    }
+
+    /**
+     * Custom action after section has been moved in AJAX mode
+     *
+     * Used in course/rest.php
+     *
+     * @return array This will be passed in ajax respose
+     */
+    function ajax_section_move() {
+        global $PAGE;
+        $titles = array();
+        $course = $this->get_course();
+        $modinfo = get_fast_modinfo($course);
+        $renderer = $this->get_renderer($PAGE);
+        if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+            foreach ($sections as $number => $section) {
+                $titles[$number] = $renderer->section_title($section, $course);
+            }
+        }
+        return array('sectiontitles' => $titles, 'action' => 'move');
+    }
+
+    /**
+     * Returns the list of blocks to be automatically added for the newly created course
+     *
+     * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
+     *     each of values is an array of block names (for left and right side columns)
+     */
+    public function get_default_blocks() {
+        return array(
+            BLOCK_POS_LEFT => array(),
+            BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
+        );
+    }
+
+    /**
+     * Definitions of the additional options that this course format uses for course
+     *
+     * Topics format uses the following options:
+     * - coursedisplay
+     * - numsections
+     * - hiddensections
+     *
+     * @param bool $foreditform
+     * @return array of options
+     */
+    public function course_format_options($foreditform = false) {
+        static $courseformatoptions = false;
+        if ($courseformatoptions === false) {
+            $courseconfig = get_config('moodlecourse');
+            $courseformatoptions = array(
+                'numsections' => array(
+                    'default' => $courseconfig->numsections,
+                    'type' => PARAM_INT,
+                ),
+                'hiddensections' => array(
+                    'default' => $courseconfig->hiddensections,
+                    'type' => PARAM_INT,
+                ),
+                'coursedisplay' => array(
+                    'default' => $courseconfig->coursedisplay,
+                    'type' => PARAM_INT,
+                ),
+            );
+        }
+        if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) {
+            $courseconfig = get_config('moodlecourse');
+            $sectionmenu = array();
+            for ($i = 0; $i <= $courseconfig->maxsections; $i++) {
+                $sectionmenu[$i] = "$i";
+            }
+            $courseformatoptionsedit = array(
+                'numsections' => array(
+                    'label' => new lang_string('numberweeks'),
+                    'element_type' => 'select',
+                    'element_attributes' => array($sectionmenu),
+                ),
+                'hiddensections' => array(
+                    'label' => new lang_string('hiddensections'),
+                    'help' => 'hiddensections',
+                    'help_component' => 'moodle',
+                    'element_type' => 'select',
+                    'element_attributes' => array(
+                        array(
+                            0 => new lang_string('hiddensectionscollapsed'),
+                            1 => new lang_string('hiddensectionsinvisible')
+                        )
+                    ),
+                ),
+                'coursedisplay' => array(
+                    'label' => new lang_string('coursedisplay'),
+                    'element_type' => 'select',
+                    'element_attributes' => array(
+                        array(
+                            COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'),
+                            COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi')
+                        )
+                    ),
+                    'help' => 'coursedisplay',
+                    'help_component' => 'moodle',
+                )
+            );
+            $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
+        }
+        return $courseformatoptions;
+    }
 
-    $titles = array();
-    rebuild_course_cache($course->id);
-    $modinfo = get_fast_modinfo($COURSE);
-    $renderer = $PAGE->get_renderer('format_topics');
-    if ($renderer && ($sections = $modinfo->get_section_info_all())) {
-        foreach ($sections as $number => $section) {
-            $titles[$number] = $renderer->section_title($section, $course);
+    /**
+     * Updates format options for a course
+     *
+     * In case if course format was changed to 'topics', we try to copy options
+     * 'coursedisplay', 'numsections' and 'hiddensections' from the previous format.
+     * If previous course format did not have 'numsections' option, we populate it with the
+     * current number of sections
+     *
+     * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
+     * @param stdClass $oldcourse if this function is called from {@link update_course()}
+     *     this object contains information about the course before update
+     * @return bool whether there were any changes to the options values
+     */
+    public function update_course_format_options($data, $oldcourse = null) {
+        if ($oldcourse !== null) {
+            $data = (array)$data;
+            $oldcourse = (array)$oldcourse;
+            $options = $this->course_format_options();
+            foreach ($options as $key => $unused) {
+                if (!array_key_exists($key, $data)) {
+                    if (array_key_exists($key, $oldcourse)) {
+                        $data[$key] = $oldcourse[$key];
+                    } else if ($key === 'numsections') {
+                        // If previous format does not have the field 'numsections'
+                        // and $data['numsections'] is not set,
+                        // we fill it with the maximum section number from the DB
+                        $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                            WHERE course = ?', array($this->courseid));
+                        if ($maxsection) {
+                            // If there are no sections, or just default 0-section, 'numsections' will be set to default
+                            $data['numsections'] = $maxsection;
+                        }
+                    }
+                }
+            }
         }
+        return $this->update_format_options($data);
     }
-    return array('sectiontitles' => $titles, 'action' => 'move');
 }
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 b2bba53..738d390 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2012061700;        // Requires this Moodle version.
+$plugin->version   = 2012110900;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2012110900;        // Requires this Moodle version.
 $plugin->component = 'format_topics';    // Full name of the plugin (used for diagnostics).
index 6beb68b..4c5d131 100644 (file)
@@ -12,6 +12,9 @@ format.
   functions callback_XXXX_request_key() are no longer used (where XXXX is the course format name)
 * functions get_generic_section_name(), get_all_sections(), add_mod_to_section(), get_all_mods()
   are deprecated. See their phpdocs in lib/deprecatedlib.php on how to replace them
+* Course formats may now have their settings.php file as the most of other plugin types
+* Function format_section_renderer_base::is_section_current() is deprecated, overwrite/use
+  function is_section_current in format class
 
 === 2.3 ===
 
diff --git a/course/format/weeks/config.php b/course/format/weeks/config.php
deleted file mode 100644 (file)
index 1a429b4..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-//
-// Optional course format configuration file
-//
-// This file contains any specific configuration settings for the
-// format.
-//
-// The default blocks layout for this course format:
-    $format['defaultblocks'] = ':search_forums,news_items,calendar_upcoming,recent_activity';
-//
index fe6fd6b..ba1f54f 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains general functions for the course format Week
+ * This file contains main class for the course format Weeks
  *
- * @since 2.0
- * @package moodlecore
+ * @since     2.0
+ * @package   format_weeks
  * @copyright 2009 Sam Hemelryk
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+defined('MOODLE_INTERNAL') || die();
+require_once($CFG->dirroot. '/course/format/lib.php');
 
 /**
- * Indicates this format uses sections.
+ * Main class for the Weeks course format
  *
- * @return bool Returns true
+ * @package    format_weeks
+ * @copyright  2012 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-function callback_weeks_uses_sections() {
-    return true;
-}
+class format_weeks extends format_base {
 
-/**
- * Used to display the course structure for a course where format=weeks
- *
- * This is called automatically by {@link load_course()} if the current course
- * format = weeks.
- *
- * @param navigation_node $navigation The course node
- * @param array $path An array of keys to the course node
- * @param stdClass $course The course we are loading the section for
- */
-function callback_weeks_load_content(&$navigation, $course, $coursenode) {
-    return $navigation->load_generic_course_sections($course, $coursenode, 'weeks');
-}
+    /**
+     * Returns true if this course format uses sections
+     *
+     * @return bool
+     */
+    public function uses_sections() {
+        return true;
+    }
 
-/**
- * The string that is used to describe a section of the course
- * e.g. Topic, Week...
- *
- * @return string
- */
-function callback_weeks_definition() {
-    return get_string('week');
-}
+    /**
+     * Returns the display name of the given section that the course prefers.
+     *
+     * @param int|stdClass $section Section object from database or just field section.section
+     * @return string Display name that the course format prefers, e.g. "Topic 2"
+     */
+    public function get_section_name($section) {
+        $section = $this->get_section($section);
+        if ((string)$section->name !== '') {
+            // Return the name the user set.
+            return format_string($section->name, true, array('context' => context_course::instance($this->courseid)));
+        } else if ($section->section == 0) {
+            // Return the general section.
+            return get_string('section0name', 'format_weeks');
+        } else {
+            $dates = $this->get_section_dates($section);
 
-/**
- * Gets the name for the provided section.
- *
- * @param stdClass $course
- * @param stdClass $section
- * @return string
- */
-function callback_weeks_get_section_name($course, $section) {
-    // We can't add a node without text
-    if ((string)$section->name !== '') {
-        // Return the name the user set.
-        return format_string($section->name, true, array('context' => context_course::instance($course->id)));
-    } else if ($section->section == 0) {
-        // Return the general section.
-        return get_string('section0name', 'format_weeks');
-    } else {
-        $dates = format_weeks_get_section_dates($section, $course);
-
-        // We subtract 24 hours for display purposes.
-        $dates->end = ($dates->end - 86400);
-
-        $dateformat = ' '.get_string('strftimedateshort');
-        $weekday = userdate($dates->start, $dateformat);
-        $endweekday = userdate($dates->end, $dateformat);
-        return $weekday.' - '.$endweekday;
+            // We subtract 24 hours for display purposes.
+            $dates->end = ($dates->end - 86400);
+
+            $dateformat = ' '.get_string('strftimedateshort');
+            $weekday = userdate($dates->start, $dateformat);
+            $endweekday = userdate($dates->end, $dateformat);
+            return $weekday.' - '.$endweekday;
+        }
     }
-}
 
-/**
- * Declares support for course AJAX features
- *
- * @see course_format_ajax_support()
- * @return stdClass
- */
-function callback_weeks_ajax_support() {
-    $ajaxsupport = new stdClass();
-    $ajaxsupport->capable = true;
-    $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
-    return $ajaxsupport;
-}
+    /**
+     * The URL to use for the specified course (with section)
+     *
+     * @param int|stdClass $section Section object from database or just field course_sections.section
+     *     if omitted the course view page is returned
+     * @param array $options options for view URL. At the moment core uses:
+     *     'navigation' (bool) if true and section has no separate page, the function returns null
+     *     'sr' (int) used by multipage formats to specify to which section to return
+     * @return null|moodle_url
+     */
+    public function get_view_url($section, $options = array()) {
+        $course = $this->get_course();
+        $url = new moodle_url('/course/view.php', array('id' => $course->id));
 
-/**
- * Return the start and end date of the passed section
- *
- * @param stdClass $section The course_section entry from the DB
- * @param stdClass $course The course entry from DB
- * @return stdClass property start for startdate, property end for enddate
- */
-function format_weeks_get_section_dates($section, $course) {
-    $oneweekseconds = 604800;
-    // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
-    // savings and the date changes.
-    $startdate = $course->startdate + 7200;
+        $sr = null;
+        if (array_key_exists('sr', $options)) {
+            $sr = $options['sr'];
+        }
+        if (is_object($section)) {
+            $sectionno = $section->section;
+        } else {
+            $sectionno = $section;
+        }
+        if ($sectionno !== null) {
+            if ($sr !== null) {
+                if ($sr) {
+                    $usercoursedisplay = COURSE_DISPLAY_MULTIPAGE;
+                    $sectionno = $sr;
+                } else {
+                    $usercoursedisplay = COURSE_DISPLAY_SINGLEPAGE;
+                }
+            } else {
+                $usercoursedisplay = $course->coursedisplay;
+            }
+            if ($sectionno != 0 && $usercoursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+                $url->param('section', $sectionno);
+            } else {
+                if (!empty($options['navigation'])) {
+                    return null;
+                }
+                $url->set_anchor('section-'.$sectionno);
+            }
+        }
+        return $url;
+    }
 
-    $dates = new stdClass();
-    $dates->start = $startdate + ($oneweekseconds * ($section->section - 1));
-    $dates->end = $dates->start + $oneweekseconds;
+    /**
+     * Returns the information about the ajax support in the given source format
+     *
+     * The returned object's property (boolean)capable indicates that
+     * the course format supports Moodle course ajax features.
+     * The property (array)testedbrowsers can be used as a parameter for {@link ajaxenabled()}.
+     *
+     * @return stdClass
+     */
+    public function supports_ajax() {
+        $ajaxsupport = new stdClass();
+        $ajaxsupport->capable = true;
+        $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
+        return $ajaxsupport;
+    }
 
-    return $dates;
-}
+    /**
+     * Loads all of the course sections into the navigation
+     *
+     * @param global_navigation $navigation
+     * @param navigation_node $node The course node within the navigation
+     */
+    public function extend_course_navigation($navigation, navigation_node $node) {
+        global $PAGE;
+        // if section is specified in course/view.php, make sure it is expanded in navigation
+        if ($navigation->includesectionnum === false) {
+            $selectedsection = optional_param('section', null, PARAM_INT);
+            if ($selectedsection !== null && (!defined('AJAX_SCRIPT') || AJAX_SCRIPT == '0') &&
+                    $PAGE->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+                $navigation->includesectionnum = $selectedsection;
+            }
+        }
+        parent::extend_course_navigation($navigation, $node);
+    }
 
-/**
- * Callback function to do some action after section move
- *
- * @param stdClass $course The course entry from DB
- * @return array This will be passed in ajax respose.
- */
-function callback_weeks_ajax_section_move($course) {
-    global $COURSE, $PAGE;
-
-    $titles = array();
-    rebuild_course_cache($course->id);
-    $modinfo = get_fast_modinfo($COURSE);
-    $renderer = $PAGE->get_renderer('format_weeks');
-    if ($renderer && ($sections = $modinfo->get_section_info_all())) {
-        foreach ($sections as $number => $section) {
-            $titles[$number] = $renderer->section_title($section, $course);
+    /**
+     * Custom action after section has been moved in AJAX mode
+     *
+     * Used in course/rest.php
+     *
+     * @return array This will be passed in ajax respose
+     */
+    function ajax_section_move() {
+        global $PAGE;
+        $titles = array();
+        $course = $this->get_course();
+        $modinfo = get_fast_modinfo($course);
+        $renderer = $this->get_renderer($PAGE);
+        if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+            foreach ($sections as $number => $section) {
+                $titles[$number] = $renderer->section_title($section, $course);
+            }
+        }
+        return array('sectiontitles' => $titles, 'action' => 'move');
+    }
+
+    /**
+     * Returns the list of blocks to be automatically added for the newly created course
+     *
+     * @return array of default blocks, must contain two keys BLOCK_POS_LEFT and BLOCK_POS_RIGHT
+     *     each of values is an array of block names (for left and right side columns)
+     */
+    public function get_default_blocks() {
+        return array(
+            BLOCK_POS_LEFT => array(),
+            BLOCK_POS_RIGHT => array('search_forums', 'news_items', 'calendar_upcoming', 'recent_activity')
+        );
+    }
+
+    /**
+     * Definitions of the additional options that this course format uses for course
+     *
+     * Weeks format uses the following options:
+     * - coursedisplay
+     * - numsections
+     * - hiddensections
+     *
+     * @param bool $foreditform
+     * @return array of options
+     */
+    public function course_format_options($foreditform = false) {
+        static $courseformatoptions = false;
+        if ($courseformatoptions === false) {
+            $courseconfig = get_config('moodlecourse');
+            $courseformatoptions = array(
+                'numsections' => array(
+                    'default' => $courseconfig->numsections,
+                    'type' => PARAM_INT,
+                ),
+                'hiddensections' => array(
+                    'default' => $courseconfig->hiddensections,
+                    'type' => PARAM_INT,
+                ),
+                'coursedisplay' => array(
+                    'default' => $courseconfig->coursedisplay,
+                    'type' => PARAM_INT,
+                ),
+            );
+        }
+        if ($foreditform && !isset($courseformatoptions['coursedisplay']['label'])) {
+            $courseconfig = get_config('moodlecourse');
+            $sectionmenu = array();
+            for ($i = 0; $i <= $courseconfig->maxsections; $i++) {
+                $sectionmenu[$i] = "$i";
+            }
+            $courseformatoptionsedit = array(
+                'numsections' => array(
+                    'label' => new lang_string('numberweeks'),
+                    'element_type' => 'select',
+                    'element_attributes' => array($sectionmenu),
+                ),
+                'hiddensections' => array(
+                    'label' => new lang_string('hiddensections'),
+                    'help' => 'hiddensections',
+                    'help_component' => 'moodle',
+                    'element_type' => 'select',
+                    'element_attributes' => array(
+                        array(
+                            0 => new lang_string('hiddensectionscollapsed'),
+                            1 => new lang_string('hiddensectionsinvisible')
+                        )
+                    ),
+                ),
+                'coursedisplay' => array(
+                    'label' => new lang_string('coursedisplay'),
+                    'element_type' => 'select',
+                    'element_attributes' => array(
+                        array(
+                            COURSE_DISPLAY_SINGLEPAGE => new lang_string('coursedisplay_single'),
+                            COURSE_DISPLAY_MULTIPAGE => new lang_string('coursedisplay_multi')
+                        )
+                    ),
+                    'help' => 'coursedisplay',
+                    'help_component' => 'moodle',
+                )
+            );
+            $courseformatoptions = array_merge_recursive($courseformatoptions, $courseformatoptionsedit);
+        }
+        return $courseformatoptions;
+    }
+
+    /**
+     * Updates format options for a course
+     *
+     * In case if course format was changed to 'weeks', we try to copy options
+     * 'coursedisplay', 'numsections' and 'hiddensections' from the previous format.
+     * If previous course format did not have 'numsections' option, we populate it with the
+     * current number of sections
+     *
+     * @param stdClass|array $data return value from {@link moodleform::get_data()} or array with data
+     * @param stdClass $oldcourse if this function is called from {@link update_course()}
+     *     this object contains information about the course before update
+     * @return bool whether there were any changes to the options values
+     */
+    public function update_course_format_options($data, $oldcourse = null) {
+        if ($oldcourse !== null) {
+            $data = (array)$data;
+            $oldcourse = (array)$oldcourse;
+            $options = $this->course_format_options();
+            foreach ($options as $key => $unused) {
+                if (!array_key_exists($key, $data)) {
+                    if (array_key_exists($key, $oldcourse)) {
+                        $data[$key] = $oldcourse[$key];
+                    } else if ($key === 'numsections') {
+                        // If previous format does not have the field 'numsections'
+                        // and $data['numsections'] is not set,
+                        // we fill it with the maximum section number from the DB
+                        $maxsection = $DB->get_field_sql('SELECT max(section) from {course_sections}
+                            WHERE course = ?', array($this->courseid));
+                        if ($maxsection) {
+                            // If there are no sections, or just default 0-section, 'numsections' will be set to default
+                            $data['numsections'] = $maxsection;
+                        }
+                    }
+                }
+            }
+        }
+        return $this->update_format_options($data);
+    }
+
+    /**
+     * Return the start and end date of the passed section
+     *
+     * @param int|stdClass|section_info $section section to get the dates for
+     * @return stdClass property start for startdate, property end for enddate
+     */
+    public function get_section_dates($section) {
+        $course = $this->get_course();
+        if (is_object($section)) {
+            $sectionnum = $section->section;
+        } else {
+            $sectionnum = $section;
+        }
+        $oneweekseconds = 604800;
+        // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight
+        // savings and the date changes.
+        $startdate = $course->startdate + 7200;
+
+        $dates = new stdClass();
+        $dates->start = $startdate + ($oneweekseconds * ($sectionnum - 1));
+        $dates->end = $dates->start + $oneweekseconds;
+
+        return $dates;
+    }
+
+    /**
+     * Returns true if the specified week is current
+     *
+     * @param int|stdClass|section_info $section
+     * @return bool
+     */
+    public function is_section_current($section) {
+        if (is_object($section)) {
+            $sectionnum = $section->section;
+        } else {
+            $sectionnum = $section;
+        }
+        if ($sectionnum < 1) {
+            return false;
         }
+        $timenow = time();
+        $dates = $this->get_section_dates($section);
+        return (($timenow >= $dates->start) && ($timenow < $dates->end));
     }
-    return array('sectiontitles' => $titles, 'action' => 'move');
 }
index e87936c..33590fd 100644 (file)
@@ -59,22 +59,4 @@ class format_weeks_renderer extends format_section_renderer_base {
     protected function page_title() {
         return get_string('weeklyoutline');
     }
-
-    /**
-     * Is the section passed in the current section?
-     *
-     * @param stdClass $section The course_section entry from the DB
-     * @param stdClass $course The course entry from DB
-     * @return bool true if the section is current
-     */
-    protected function is_section_current($section, $course) {
-        if ($section->section < 1) {
-            return false;
-        }
-
-        $timenow = time();
-        $dates = format_weeks_get_section_dates($section, $course);
-
-        return (($timenow >= $dates->start) && ($timenow < $dates->end));
-    }
 }
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 6a2d2c8..b89185e 100644 (file)
@@ -25,6 +25,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700;        // The current plugin version (Date: YYYYMMDDXX).
-$plugin->requires  = 2012061700;        // Requires this Moodle version.
+$plugin->version   = 2012110900;        // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2012110900;        // Requires this Moodle version.
 $plugin->component = 'format_weeks';    // Full name of the plugin (used for diagnostics).
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 a772578..8626b42 100644 (file)
@@ -1272,7 +1272,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);
@@ -1489,9 +1498,9 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     $accesstext = '';
                 }
                 if ($linkclasses) {
-                    $linkcss = 'class="' . trim($linkclasses) . '" ';
+                    $linkcss = 'class="activityinstance ' . trim($linkclasses) . '" ';
                 } else {
-                    $linkcss = '';
+                    $linkcss = 'class="activityinstance"';
                 }
                 if ($textclasses) {
                     $textcss = 'class="' . trim($textclasses) . '" ';
@@ -1509,7 +1518,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     // Display link itself
                     echo '<a ' . $linkcss . $mod->extra . $onclick .
                             ' href="' . $url . '"><img src="' . $mod->get_icon_url() .
-                            '" class="activityicon" alt="' . $mod->modfullname . '" /> ' .
+                            '" class="iconlarge activityicon" alt="' . $mod->modfullname . '" />' .
                             $accesstext . '<span class="instancename">' .
                             $instancename . $altname . '</span></a>';
 
@@ -1577,7 +1586,6 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                 } else {
                     $mod->groupmode = false;
                 }
-                echo '&nbsp;&nbsp;';
                 echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
                 echo $mod->get_after_edit_icons();
             }
@@ -2806,16 +2814,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;
     }
@@ -2840,17 +2858,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;
 }
@@ -3316,7 +3331,7 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
     if (has_capability('moodle/role:assign', $modcontext)){
         $actions[] = new action_link(
             new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid' => $modcontext->id)),
-            new pix_icon('i/roles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+            new pix_icon('t/assignroles', $str->assign, 'moodle', array('class' => 'iconsmall', 'title' => '')),
             null,
             array('class' => 'editing_assign', 'title' => $str->assign)
         );
@@ -4512,6 +4527,35 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
     return true;
 }
 
+/**
+ * Returns the sorted list of available course formats, filtered by enabled if necessary
+ *
+ * @param bool $enabledonly return only formats that are enabled
+ * @return array array of sorted format names
+ */
+function get_sorted_course_formats($enabledonly = false) {
+    global $CFG;
+    $formats = get_plugin_list('format');
+
+    if (!empty($CFG->format_plugins_sortorder)) {
+        $order = explode(',', $CFG->format_plugins_sortorder);
+        $order = array_merge(array_intersect($order, array_keys($formats)),
+                    array_diff(array_keys($formats), $order));
+    } else {
+        $order = array_keys($formats);
+    }
+    if (!$enabledonly) {
+        return $order;
+    }
+    $sortedformats = array();
+    foreach ($order as $formatname) {
+        if (!get_config('format_'.$formatname, 'disabled')) {
+            $sortedformats[] = $formatname;
+        }
+    }
+    return $sortedformats;
+}
+
 /**
  * The URL to use for the specified course (with section)
  *
index bce30df..bbb761e 100644 (file)
@@ -233,17 +233,19 @@ class core_course_renderer extends plugin_renderer_base {
         // Put all options into one tag 'alloptions' to allow us to handle scrolling
         $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
 
-        // Activities
-        $activities = array_filter($modules,
-                create_function('$mod', 'return ($mod->archetype !== MOD_CLASS_RESOURCE);'));
+         // Activities
+        $activities = array_filter($modules, function($mod) {
+            return ($mod->archetype !== MOD_ARCHETYPE_RESOURCE && $mod->archetype !== MOD_ARCHETYPE_SYSTEM);
+        });
         if (count($activities)) {
             $formcontent .= $this->course_modchooser_title('activities');
             $formcontent .= $this->course_modchooser_module_types($activities);
         }
 
         // Resources
-        $resources = array_filter($modules,
-                create_function('$mod', 'return ($mod->archetype === MOD_CLASS_RESOURCE);'));
+        $resources = array_filter($modules, function($mod) {
+            return ($mod->archetype === MOD_ARCHETYPE_RESOURCE);
+        });
         if (count($resources)) {
             $formcontent .= $this->course_modchooser_title('resources');
             $formcontent .= $this->course_modchooser_module_types($resources);
index 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 67e9f70..645d73a 100644 (file)
@@ -2300,14 +2300,14 @@ function grade_extend_settings($plugininfo, $courseid) {
     if ($imports = grade_helper::get_plugins_import($courseid)) {
         $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
         foreach ($imports as $import) {
-            $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/restore', ''));
+            $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', ''));
         }
     }
 
     if ($exports = grade_helper::get_plugins_export($courseid)) {
         $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
         foreach ($exports as $export) {
-            $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/backup', ''));
+            $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', ''));
         }
     }
 
index 208f556..dbf755f 100644 (file)
@@ -95,6 +95,7 @@ $string['calendarexportsalt'] = 'Calendar export salt';
 $string['calendarsettings'] = 'Calendar';
 $string['calendar_weekend'] = 'Weekend days';
 $string['cannotdeletemodfilter'] = 'You cannot uninstall the \'{$a->filter}\' because it is part of the \'{$a->module}\' module.';
+$string['cannotuninstall'] = '{$a} can not be uninstalled.';
 $string['cfgwwwrootslashwarning'] = 'You have defined $CFG->wwwroot incorrectly in your config.php file. You have included a \'/\' character at the end. Please remove it, or you will experience strange bugs like <a href=\'http://tracker.moodle.org/browse/MDL-11061\'>MDL-11061</a>.';
 $string['cfgwwwrootwarning'] = 'You have defined $CFG->wwwroot incorrectly in your config.php file. It does not match the URL you are using to access this page. Please correct it, or you will experience strange bugs like <a href=\'http://tracker.moodle.org/browse/MDL-11061\'>MDL-11061</a>.';
 $string['clamfailureonupload'] = 'On clam AV failure';
@@ -417,6 +418,7 @@ $string['debugstringids_desc'] = 'This option is designed to help translators. W
 $string['debugvalidators'] = 'Show validator links';
 $string['defaultcity'] = 'Default city';
 $string['defaultcity_help'] = 'A city entered here will be the default city when creating new user accounts.';
+$string['defaultformatnotset'] = 'Error determining default course format. Please check site settings.';
 $string['defaulthomepage'] = 'Default home page for users';
 $string['defaultrequestcategory'] = 'Default category for course requests';
 $string['defaultsettinginfo'] = 'Default: {$a}';
@@ -533,6 +535,9 @@ $string['forceloginforprofileimage'] = 'Force users to login to view user pictur
 $string['forceloginforprofileimage_help'] = 'If enabled, users must login in order to view user profile pictures and the default user picture will be used in all notification emails.';
 $string['forceloginforprofiles'] = 'Force users to login for profiles';
 $string['forcetimezone'] = 'Force default timezone';
+$string['formatuninstallwithcourses'] = 'There are {$a->count} courses using {$a->format}. Their format will be changed to {$a->defaultformat} (default format for this site). Some format-specific data may be lost. Are you sure you want to proceed?';
+$string['formatuninstallconfirm'] = '{$a} will be uninstalled. No courses currently use it. Continue?';
+$string['formatuninstalled'] = 'All data associated with the format plugin \'{$a->plugin}\' has been deleted from the database.  To complete the deletion (and prevent the plugin re-installing itself), you should now delete this directory from your server: {$a->directory}';
 $string['frontpage'] = 'Front page';
 $string['frontpagebackup'] = 'Front page backup';
 $string['frontpagedefaultrole'] = 'Default frontpage role';
@@ -641,6 +646,8 @@ $string['longtimewarning'] = '<b>Please note that this process can take a long t
 $string['maintenancemode'] = 'In maintenance mode';
 $string['maintfileopenerror'] = 'Error opening maintenance files!';
 $string['maintinprogress'] = 'Maintenance is in progress...';
+$string['manageformats'] = 'Manage course formats';
+$string['manageformatsgotosettings'] = 'Default format can be changed in {$a}';
 $string['managelang'] = 'Manage';
 $string['managelicenses'] = 'Manage licences';
 $string['manageqbehaviours'] = 'Manage question behaviours';
index bf9e294..543a49f 100644 (file)
@@ -65,6 +65,7 @@ $string['cannotdeletefile'] = 'Cannot delete this file';
 $string['cannotdeleterole'] = 'It cannot be deleted, because {$a}';
 $string['cannotdeleterolewithid'] = 'Could not delete role with ID {$a}';
 $string['cannotdeletethisrole'] = 'You cannot delete this role because it is used by the system, or because it is the last role with administrator capabilities.';
+$string['cannotdisableformat'] = 'You can not disable the default format';
 $string['cannotdownloadcomponents'] = 'Cannot download components';
 $string['cannotdownloadlanguageupdatelist'] = 'Cannot download list of language updates from download.moodle.org';
 $string['cannotdownloadzipfile'] = 'Cannot download ZIP file';
index 960eac4..16430f7 100644 (file)
@@ -297,7 +297,7 @@ $string['coursecompletions'] = 'Course completions';
 $string['coursecreators'] = 'Course creator';
 $string['coursecreatorsdescription'] = 'Course creators can create new courses.';
 $string['coursedisplay'] = 'Course layout';
-$string['coursedisplay_help'] = 'This setting determines whether the whole course is displayed on one page or split over several pages. The setting has no affect on certain course formats, such as SCORM format.';
+$string['coursedisplay_help'] = 'This setting determines whether the whole course is displayed on one page or split over several pages.';
 $string['coursedisplay_single'] = 'Show all sections on one page';
 $string['coursedisplay_multi'] = 'Show one section per page';
 $string['coursedeleted'] = 'Deleted course {$a}';
@@ -1805,6 +1805,7 @@ $string['whattocallzip'] = 'What do you want to call the zip file?';
 $string['whattodo'] = 'What to do';
 $string['windowclosing'] = 'This window should close automatically. If not, please close it now.';
 $string['withchosenfiles'] = 'With chosen files';
+$string['withdisablednote'] = '{$a} (disabled)';
 $string['withoutuserdata'] = 'without user data';
 $string['withselectedusers'] = 'With selected users...';
 $string['withselectedusers_help'] = '* Send message - For sending a message to one or more participants
index 3813b4d..e5b4760 100644 (file)
@@ -266,6 +266,16 @@ function uninstall_plugin($type, $name) {
             // Delete block
             $DB->delete_records('block', array('id'=>$block->id));
         }
+    } else if ($type === 'format') {
+        if (($defaultformat = get_config('moodlecourse', 'format')) && $defaultformat !== $name) {
+            $courses = $DB->get_records('course', array('format' => $name), 'id');
+            $data = (object)array('id' => null, 'format' => $defaultformat);
+            foreach ($courses as $record) {
+                $data->id = $record->id;
+                update_course($data);
+            }
+        }
+        $DB->delete_records('course_format_options', array('format' => $name));
     }
 
     // perform clean-up task common for all the plugin/subplugin types
@@ -5289,7 +5299,8 @@ class admin_page_manageqtypes extends admin_externalpage {
      */
     public function __construct() {
         global $CFG;
-        parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'), "$CFG->wwwroot/$CFG->admin/qtypes.php");
+        parent::__construct('manageqtypes', get_string('manageqtypes', 'admin'),
+                new moodle_url('/admin/qtypes.php'));
     }
 
     /**
@@ -5897,6 +5908,147 @@ class admin_setting_managelicenses extends admin_setting {
     }
 }
 
+/**
+ * Course formats manager. Allows to enable/disable formats and jump to settings
+ */
+class admin_setting_manageformats extends admin_setting {
+
+    /**
+     * Calls parent::__construct with specific arguments
+     */
+    public function __construct() {
+        $this->nosave = true;
+        parent::__construct('formatsui', new lang_string('manageformats', 'core_admin'), '', '');
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_setting() {
+        return true;
+    }
+
+    /**
+     * Always returns true
+     *
+     * @return true
+     */
+    public function get_defaultsetting() {
+        return true;
+    }
+
+    /**
+     * Always returns '' and doesn't write anything
+     *
+     * @param mixed $data string or array, must not be NULL
+     * @return string Always returns ''
+     */
+    public function write_setting($data) {
+        // do not write any setting
+        return '';
+    }
+
+    /**
+     * Search to find if Query is related to format plugin
+     *
+     * @param string $query The string to search for
+     * @return bool true for related false for not
+     */
+    public function is_related($query) {
+        if (parent::is_related($query)) {
+            return true;
+        }
+        $allplugins = plugin_manager::instance()->get_plugins();
+        $formats = $allplugins['format'];
+        foreach ($formats as $format) {
+            if (strpos($format->component, $query) !== false ||
+                    strpos(textlib::strtolower($format->displayname), $query) !== false) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return XHTML to display control
+     *
+     * @param mixed $data Unused
+     * @param string $query
+     * @return string highlight
+     */
+    public function output_html($data, $query='') {
+        global $CFG, $OUTPUT;
+        $return = '';
+        $return = $OUTPUT->heading(new lang_string('courseformats'), 3, 'main');
+        $return .= $OUTPUT->box_start('generalbox formatsui');
+
+        $allplugins = plugin_manager::instance()->get_plugins();
+        $formats = $allplugins['format'];
+
+        // display strings
+        $txt = get_strings(array('settings', 'name', 'enable', 'disable', 'up', 'down', 'default', 'delete'));
+        $txt->updown = "$txt->up/$txt->down";
+
+        $table = new html_table();
+        $table->head  = array($txt->name, $txt->enable, $txt->updown, $txt->delete, $txt->settings);
+        $table->align = array('left', 'center', 'center', 'center', 'center');
+        $table->width = '90%';
+        $table->attributes['class'] = 'manageformattable generaltable';
+        $table->data  = array();
+
+        $cnt = 0;
+        $defaultformat = get_config('moodlecourse', 'format');
+        $spacer = $OUTPUT->pix_icon('spacer', '', 'moodle', array('class' => 'icon'));
+        foreach ($formats as $format) {
+            $url = new moodle_url('/admin/courseformats.php',
+                    array('sesskey' => sesskey(), 'format' => $format->name));
+            $isdefault = '';
+            if ($format->is_enabled()) {
+                $strformatname = html_writer::tag('span', $format->displayname);
+                if ($defaultformat === $format->name) {
+                    $hideshow = $txt->default;
+                } else {
+                    $hideshow = html_writer::link($url->out(false, array('action' => 'disable')),
+                            $OUTPUT->pix_icon('i/hide', $txt->disable, 'moodle', array('class' => 'icon')));
+                }
+            } else {
+                $strformatname = html_writer::tag('span', $format->displayname, array('class' => 'dimmed_text'));
+                $hideshow = html_writer::link($url->out(false, array('action' => 'enable')),
+                    $OUTPUT->pix_icon('i/show', $txt->enable, 'moodle', array('class' => 'icon')));
+            }
+            $updown = '';
+            if ($cnt) {
+                $updown .= html_writer::link($url->out(false, array('action' => 'up')),
+                    $OUTPUT->pix_icon('t/up', $txt->up, 'moodle')). '&nbsp;';
+            } else {
+                $updown .= $spacer;
+            }
+            if ($cnt < count($formats) - 1) {
+                $updown .= '&nbsp;'.html_writer::link($url->out(false, array('action' => 'down')),
+                    $OUTPUT->pix_icon('t/down', $txt->down, 'moodle'));
+            } else {
+                $updown .= $spacer;
+            }
+            $cnt++;
+            $settings = '';
+            if ($format->get_settings_url()) {
+                $settings = html_writer::link($format->get_settings_url(), $txt->settings);
+            }
+            $uninstall = '';
+            if ($defaultformat !== $format->name) {
+                $uninstall = html_writer::link($format->get_uninstall_url(), $txt->delete);
+            }
+            $table->data[] =array($strformatname, $hideshow, $updown, $uninstall, $settings);
+        }
+        $return .= html_writer::table($table);
+        $link = html_writer::link(new moodle_url('/admin/settings.php', array('section' => 'coursesettings')), new lang_string('coursesettings'));
+        $return .= html_writer::tag('p', get_string('manageformatsgotosettings', 'admin', $link));
+        $return .= $OUTPUT->box_end();
+        return highlight($query, $return);
+    }
+}
 
 /**
  * Special class for filter administration.
index c28c919..e566933 100644 (file)
@@ -1067,7 +1067,7 @@ class block_manager {
 
             $controls[] = array('url' => $CFG->wwwroot . '/' . $CFG->admin .
                     '/roles/assign.php?contextid=' . $block->context->id . '&returnurl=' . urlencode($return),
-                    'icon' => 'i/roles', 'caption' => get_string('assignroles', 'role'), 'class' => 'editing_roles');
+                    'icon' => 't/assignroles', 'caption' => get_string('assignroles', 'role'), 'class' => 'editing_roles');
         }
 
         return $controls;
index d160359..548f854 100644 (file)
@@ -251,6 +251,8 @@ function css_minify_css($files) {
     Minify::setCache(null, false);
 
     $options = array(
+        // JSMin is not GNU GPL compatible, use the plus version instead.
+        'minifiers' => array(Minify::TYPE_JS => array('JSMinPlus', 'minify')),
         'bubbleCssImports' => false,
         // Don't gzip content we just want text for storage
         'encodeOutput' => false,
index aee878f..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 1a2b205..900b6e7 100644 (file)
@@ -3044,3 +3044,24 @@ function get_course_section($section, $courseid) {
     rebuild_course_cache($courseid, true);
     return $DB->get_record("course_sections", array("id"=>$id));
 }
+
+/**
+ * Return the start and end date of the week in Weekly course format
+ *
+ * It is not recommended to use this function outside of format_weeks plugin
+ *
+ * @deprecated since 2.4
+ * @see format_weeks::get_section_dates()
+ *
+ * @param stdClass $section The course_section entry from the DB
+ * @param stdClass $course The course entry from DB
+ * @return stdClass property start for startdate, property end for enddate
+ */
+function format_weeks_get_section_dates($section, $course) {
+    debugging('Function format_weeks_get_section_dates() is deprecated. It is not recommended to'.
+            ' use it outside of format_weeks plugin', DEBUG_DEVELOPER);
+    if (isset($course->format) && $course->format === 'weeks') {
+        return course_get_format($course)->get_section_dates($section);
+    }
+    return null;
+}
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 4b54f37..77e9883 100644 (file)
@@ -4572,7 +4572,8 @@ function delete_course($courseorid, $showfeedback = true) {
     // which should know about this updated property, as this event is meant to pass the full course record
     $course->timemodified = time();
 
-    $DB->delete_records("course", array("id"=>$courseid));
+    $DB->delete_records("course", array("id" => $courseid));
+    $DB->delete_records("course_format_options", array("courseid" => $courseid));
 
     //trigger events
     $course->context = $context; // you can not fetch context in the event because it was already deleted
@@ -8543,17 +8544,25 @@ function check_php_version($version='5.2.4') {
           if (strpos($agent, 'Opera') !== false) {     // Reject Opera
               return false;
           }
-          // in case of IE we have to deal with BC of the version parameter
+          // In case of IE we have to deal with BC of the version parameter.
           if (is_null($version)) {
-              $version = 5.5; // anything older is not considered a browser at all!
+              $version = 5.5; // Anything older is not considered a browser at all!
           }
-
-          //see: http://www.useragentstring.com/pages/Internet%20Explorer/
+          // IE uses simple versions, let's cast it to float to simplify the logic here.
+          $version = round($version, 1);
+          // See: http://www.useragentstring.com/pages/Internet%20Explorer/
           if (preg_match("/MSIE ([0-9\.]+)/", $agent, $match)) {
-              if (version_compare($match[1], $version) >= 0) {
-                  return true;
-              }
+              $browser = $match[1];
+          } else {
+              return false;
+          }
+          // IE8 and later versions may pretend to be IE7 for intranet sites, use Trident version instead,
+          // the Trident should always describe the capabilities of IE in any emulation mode.
+          if ($browser === '7.0' and preg_match("/Trident\/([0-9\.]+)/", $agent, $match)) {
+              $browser = $match[1] + 4; // NOTE: Hopefully this will work also for future IE versions.
           }
+          $browser = round($browser, 1);
+          return ($browser >= $version);
           break;
 
 
@@ -8831,14 +8840,11 @@ function get_browser_version_classes() {
 
     if (check_browser_version("MSIE", "0")) {
         $classes[] = 'ie';
-        if (check_browser_version("MSIE", 9)) {
-            $classes[] = 'ie9';
-        } else if (check_browser_version("MSIE", 8)) {
-            $classes[] = 'ie8';
-        } elseif (check_browser_version("MSIE", 7)) {
-            $classes[] = 'ie7';
-        } elseif (check_browser_version("MSIE", 6)) {
-            $classes[] = 'ie6';
+        for($i=12; $i>=6; $i--) {
+            if (check_browser_version("MSIE", $i)) {
+                $classes[] = 'ie'.$i;
+                break;
+            }
         }
 
     } else if (check_browser_version("Firefox") || check_browser_version("Gecko") || check_browser_version("Camino")) {
index 0f8df49..0fd2496 100644 (file)
@@ -3527,7 +3527,7 @@ class settings_navigation extends navigation_node {
         // Import data from other courses
         if (has_capability('moodle/restore:restoretargetimport', $coursecontext)) {
             $url = new moodle_url('/backup/import.php', array('id'=>$course->id));
-            $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/restore', ''));
+            $coursenode->add(get_string('import'), $url, self::TYPE_SETTING, null, 'import', new pix_icon('i/import', ''));
         }
 
         // Publish course on a hub
@@ -3597,7 +3597,7 @@ class settings_navigation extends navigation_node {
             $returnurl->param('sesskey', sesskey());
             foreach ($roles as $key => $name) {
                 $url = new moodle_url('/course/switchrole.php', array('id'=>$course->id,'sesskey'=>sesskey(), 'switchrole'=>$key, 'returnurl'=>$returnurl->out(false)));
-                $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/roles', ''));
+                $switchroles->add($name, $url, self::TYPE_SETTING, null, $key, new pix_icon('i/switchrole', ''));
             }
         }
         // Return we are done
@@ -4064,7 +4064,7 @@ class settings_navigation extends navigation_node {
         // Assign local roles
         if (has_capability('moodle/role:assign', $this->context)) {
             $assignurl = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->context->id));
-            $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/roles', ''));
+            $categorynode->add(get_string('assignroles', 'role'), $assignurl, self::TYPE_SETTING, null, 'roles', new pix_icon('i/assignroles', ''));
         }
 
         // Override roles
index e5d1410..e8357c9 100644 (file)
@@ -1120,7 +1120,7 @@ class theme_config {
                 if (empty($_SERVER['HTTP_USER_AGENT'])) {
                     // Can't be sure, just say no.
                     $this->usesvg = false;
-                } else if (preg_match('#MSIE +[5-8]\.#', $_SERVER['HTTP_USER_AGENT'])) {
+                } else if (check_browser_version('MSIE', 0) and !check_browser_version('MSIE', 9)) {
                     // IE < 9 doesn't support SVG. Say no.
                     $this->usesvg = false;
                 } else if (preg_match('#Android +[0-2]\.#', $_SERVER['HTTP_USER_AGENT'])) {
index 6239615..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) {
@@ -759,6 +753,23 @@ class core_renderer extends renderer_base {
             $header = $this->doctype() . $header;
         }
 
+        // If this theme version is below 2.4 release and this is a course view page
+        if ((!isset($this->page->theme->settings->version) || $this->page->theme->settings->version < 2012101500) &&
+                $this->page->pagelayout === 'course' && $this->page->url->compare(new moodle_url('/course/view.php'), URL_MATCH_BASE)) {
+            // check if course content header/footer have not been output during render of theme layout
+            $coursecontentheader = $this->course_content_header(true);
+            $coursecontentfooter = $this->course_content_footer(true);
+            if (!empty($coursecontentheader)) {
+                // display debug message and add header and footer right above and below main content
+                // Please note that course header and footer (to be displayed above and below the whole page)
+                // are not displayed in this case at all.
+                // Besides the content header and footer are not displayed on any other course page
+                debugging('The current theme is not optimised for 2.4, the course-specific header and footer defined in course format will not be output', DEBUG_DEVELOPER);
+                $header .= $coursecontentheader;
+                $footer = $coursecontentfooter. $footer;
+            }
+        }
+
         send_headers($this->contenttype, $this->page->cacheable);
 
         $this->opencontainers->push('header/footer', $footer);
@@ -845,6 +856,100 @@ class core_renderer extends renderer_base {
         return $this->opencontainers->pop_all_but_last($shouldbenone);
     }
 
+    /**
+     * Returns course-specific information to be output immediately above content on any course page
+     * (for the current course)
+     *
+     * @param bool $onlyifnotcalledbefore output content only if it has not been output before
+     * @return string
+     */
+    public function course_content_header($onlyifnotcalledbefore = false) {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        static $functioncalled = false;
+        if ($functioncalled && $onlyifnotcalledbefore) {
+            // we have already output the content header
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $functioncalled = true;
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_content_header()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output immediately below content on any course page
+     * (for the current course)
+     *
+     * @param bool $onlyifnotcalledbefore output content only if it has not been output before
+     * @return string
+     */
+    public function course_content_footer($onlyifnotcalledbefore = false) {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        static $functioncalled = false;
+        if ($functioncalled && $onlyifnotcalledbefore) {
+            // we have already output the content footer
+            return '';
+        }
+        $functioncalled = true;
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_content_footer()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output on any course page in the header area
+     * (for the current course)
+     *
+     * @return string
+     */
+    public function course_header() {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_header()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
+    /**
+     * Returns course-specific information to be output on any course page in the footer area
+     * (for the current course)
+     *
+     * @return string
+     */
+    public function course_footer() {
+        global $CFG;
+        if ($this->page->course->id == SITEID) {
+            // return immediately and do not include /course/lib.php if not necessary
+            return '';
+        }
+        require_once($CFG->dirroot.'/course/lib.php');
+        $courseformat = course_get_format($this->page->course);
+        if (($obj = $courseformat->course_footer()) !== null) {
+            return $courseformat->get_renderer($this->page)->render($obj);
+        }
+        return '';
+    }
+
     /**
      * Returns lang menu or '', this method also checks forcing of languages in courses.
      *
index e8cb001..a449bc6 100644 (file)
@@ -913,7 +913,10 @@ class available_update_checker {
                 $this->recentfetch = $config->recentfetch;
                 $this->recentresponse = $this->decode_response($config->recentresponse);
             } catch (available_update_checker_exception $e) {
-                debugging('Invalid info about available updates detected and will be ignored: '.$e->getMessage(), DEBUG_ALL);
+                // The server response is not valid. Behave as if no data were fetched yet.
+                // This may happen when the most recent update info (cached locally) has been
+                // fetched with the previous branch of Moodle (like during an upgrade from 2.x
+                // to 2.y) or when the API of the response has changed.
                 $this->recentresponse = array();
             }
 
@@ -2924,9 +2927,11 @@ class plugininfo_qtype extends plugininfo_base {
         $section = $this->get_settings_section_name();
 
         $settings = null;
-        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
+        $systemcontext = context_system::instance();
+        if (($hassiteconfig || has_capability('moodle/question:config', $systemcontext)) &&
+                file_exists($this->full_path('settings.php'))) {
             $settings = new admin_settingpage($section, $this->displayname,
-                    'moodle/site:config', $this->is_enabled() === false);
+                    'moodle/question:config', $this->is_enabled() === false);
             include($this->full_path('settings.php')); // this may also set $settings to null
         }
         if ($settings) {
@@ -3362,3 +3367,61 @@ class plugininfo_webservice extends plugininfo_base {
                 array('sesskey' => sesskey(), 'action' => 'uninstall', 'webservice' => $this->name));
     }
 }
+
+/**
+ * Class for course formats
+ */
+class plugininfo_format extends plugininfo_base {
+
+    /**
+     * Gathers and returns the information about all plugins of the given type
+     *
+     * @param string $type the name of the plugintype, eg. mod, auth or workshopform
+     * @param string $typerootdir full path to the location of the plugin dir
+     * @param string $typeclass the name of the actually called class
+     * @return array of plugintype classes, indexed by the plugin name
+     */
+    public static function get_plugins($type, $typerootdir, $typeclass) {
+        global $CFG;
+        $formats = parent::get_plugins($type, $typerootdir, $typeclass);
+        require_once($CFG->dirroot.'/course/lib.php');
+        $order = get_sorted_course_formats();
+        $sortedformats = array();
+        foreach ($order as $formatname) {
+            $sortedformats[$formatname] = $formats[$formatname];
+        }
+        return $sortedformats;
+    }
+
+    public function get_settings_section_name() {
+        return 'formatsetting' . $this->name;
+    }
+
+    public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        global $CFG, $USER, $DB, $OUTPUT, $PAGE; // in case settings.php wants to refer to them
+        $ADMIN = $adminroot; // also may be used in settings.php
+        $section = $this->get_settings_section_name();
+
+        $settings = null;
+        if ($hassiteconfig && file_exists($this->full_path('settings.php'))) {
+            $settings = new admin_settingpage($section, $this->displayname,
+                    'moodle/site:config', $this->is_enabled() === false);
+            include($this->full_path('settings.php')); // this may also set $settings to null
+        }
+        if ($settings) {
+            $ADMIN->add($parentnodename, $settings);
+        }
+    }
+
+    public function is_enabled() {
+        return !get_config($this->component, 'disabled');
+    }
+
+    public function get_uninstall_url() {
+        if ($this->name !== get_config('moodlecourse', 'format') && $this->name !== 'site') {
+            return new moodle_url('/admin/courseformats.php',
+                    array('sesskey' => sesskey(), 'action' => 'uninstall', 'format' => $this->name));
+        }
+        return parent::get_uninstall_url();
+    }
+}
index 1e7c439..90c5c73 100644 (file)
@@ -182,6 +182,7 @@ if (defined('WEB_CRON_EMULATED_CLI')) {
 if (file_exists("$CFG->dataroot/climaintenance.html")) {
     if (!CLI_SCRIPT) {
         header('Content-type: text/html; charset=utf-8');
+        header('X-UA-Compatible: IE=edge');
         /// Headers to make it not cacheable and json
         header('Cache-Control: no-store, no-cache, must-revalidate');
         header('Cache-Control: post-check=0, pre-check=0', false);
index ace9c43..33894ad 100644 (file)
@@ -1540,6 +1540,7 @@ width: 80%; -moz-border-radius: 20px; padding: 15px">
 
         // better disable any caching
         @header('Content-Type: text/html; charset=utf-8');
+        @header('X-UA-Compatible: IE=edge');
         @header('Cache-Control: no-store, no-cache, must-revalidate');
         @header('Cache-Control: post-check=0, pre-check=0', false);
         @header('Pragma: no-cache');
index 6b2220f..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>
+            &l