Merge branch 'MDL-36316-master' of git://github.com/danpoltawski/moodle
authorSam Hemelryk <sam@moodle.com>
Mon, 16 Sep 2013 01:13:41 +0000 (13:13 +1200)
committerSam Hemelryk <sam@moodle.com>
Mon, 16 Sep 2013 01:13:41 +0000 (13:13 +1200)
339 files changed:
admin/cli/fix_course_sequence.php [new file with mode: 0644]
admin/index.php
admin/modules.php
admin/settings/badges.php
admin/tool/behat/lang/en/tool_behat.php
auth/cas/auth.php
auth/db/lang/en/auth_db.php
auth/mnet/auth.php
backup/controller/restore_controller.class.php
backup/restore.php
backup/util/includes/restore_includes.php
backup/util/progress/core_backup_display_progress.class.php
backup/util/progress/core_backup_display_progress_if_slow.class.php [new file with mode: 0644]
backup/util/ui/restore_ui.class.php
backup/util/ui/restore_ui_stage.class.php
badges/action.php
badges/criteria.php
badges/edit.php
badges/newbadge.php
blocks/course_overview/locallib.php
blocks/rss_client/styles.css [new file with mode: 0644]
blocks/site_main_menu/block_site_main_menu.php
blocks/social_activities/block_social_activities.php
blog/edit.php
blog/locallib.php
blog/tests/bloglib_test.php
cache/classes/definition.php
cache/classes/factory.php
calendar/classes/type_base.php [new file with mode: 0644]
calendar/classes/type_factory.php [new file with mode: 0644]
calendar/tests/calendartype_test.php [new file with mode: 0644]
calendar/tests/calendartype_test_example.php [new file with mode: 0644]
calendar/type/gregorian/classes/structure.php [new file with mode: 0644]
calendar/type/gregorian/lang/en/calendartype_gregorian.php [new file with mode: 0644]
calendar/type/gregorian/version.php [new file with mode: 0644]
config-dist.php
course/dnduploadlib.php
course/edit.php
course/edit_form.php
course/editcategory.php
course/externallib.php
course/format/renderer.php
course/lib.php
course/modedit.php
course/moodleform_mod.php
course/tests/courselib_test.php
enrol/database/lang/en/enrol_database.php
enrol/database/settings.php
enrol/ldap/lib.php
enrol/meta/lib.php
enrol/renderer.php
enrol/self/lib.php
enrol/tests/enrollib_test.php
enrol/users.php
enrol/yui/rolemanager/rolemanager.js
error/index.php
files/renderer.php
filter/activitynames/filter.php
grade/edit/outcome/edit.php
grade/edit/scale/edit.php
grade/report/grader/lib.php
grade/report/grader/module.js
grade/report/grader/styles.css
group/autogroup_form.php
group/group.php
group/group_form.php
group/grouping_form.php
group/import_form.php
group/tests/behat/auto_creation.feature
lang/en/admin.php
lang/en/blog.php
lang/en/cache.php
lang/en/calendar.php
lang/en/group.php
lang/en/plugin.php
lib/adminlib.php
lib/authlib.php
lib/badgeslib.php
lib/behat/classes/util.php
lib/classes/component.php
lib/classes/event/base.php
lib/classes/event/blog_entry_created.php
lib/classes/event/blog_entry_deleted.php
lib/classes/event/blog_entry_updated.php [new file with mode: 0644]
lib/classes/event/content_viewed.php
lib/classes/event/course_category_deleted.php
lib/classes/event/course_completed.php
lib/classes/event/course_content_deleted.php
lib/classes/event/course_created.php
lib/classes/event/course_deleted.php
lib/classes/event/course_module_completion_updated.php
lib/classes/event/course_restored.php
lib/classes/event/course_updated.php
lib/classes/event/manager.php
lib/classes/event/role_allow_assign_updated.php
lib/classes/event/role_allow_override_updated.php
lib/classes/event/role_allow_switch_updated.php
lib/classes/event/role_assigned.php
lib/classes/event/role_capabilities_updated.php
lib/classes/event/role_deleted.php
lib/classes/event/role_unassigned.php
lib/classes/user.php [new file with mode: 0644]
lib/completionlib.php
lib/conditionlib.php
lib/coursecatlib.php
lib/cronlib.php
lib/datalib.php
lib/db/caches.php
lib/db/install.xml
lib/db/upgrade.php
lib/deprecatedlib.php
lib/editor/atto/plugins/title/lang/en/atto_title.php
lib/editor/atto/plugins/title/lib.php
lib/editor/atto/plugins/title/version.php
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button-debug.js
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button-min.js
lib/editor/atto/plugins/title/yui/build/moodle-atto_title-button/moodle-atto_title-button.js
lib/editor/atto/plugins/title/yui/src/button/js/button.js
lib/editor/atto/styles.css
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/editor.js
lib/enrollib.php
lib/filelib.php
lib/filestorage/file_packer.php
lib/filestorage/file_progress.php [new file with mode: 0644]
lib/filestorage/stored_file.php
lib/filestorage/tests/zip_packer_test.php
lib/filestorage/zip_archive.php
lib/filestorage/zip_packer.php
lib/form/dateselector.php
lib/form/datetimeselector.php
lib/form/editor.php
lib/formslib.php
lib/grouplib.php
lib/messagelib.php
lib/modinfolib.php
lib/moodlelib.php
lib/oauthlib.php
lib/outputrenderers.php
lib/pluginlib.php
lib/setuplib.php
lib/tests/behat/behat_hooks.php
lib/tests/conditionlib_test.php
lib/tests/coursecatlib_test.php
lib/tests/cronlib_test.php [new file with mode: 0644]
lib/tests/datalib_test.php
lib/tests/event_test.php
lib/tests/grouplib_test.php
lib/tests/modinfolib_test.php
lib/tests/user_test.php [new file with mode: 0644]
lib/upgrade.txt
login/index.php
message/index.php
message/lib.php
message/output/email/message_output_email.php
message/output/lib.php
message/upgrade.txt
mod/assign/locallib.php
mod/assign/tests/locallib_test.php
mod/assignment/index.php
mod/book/delete.php
mod/book/edit.php
mod/book/edit_form.php
mod/book/styles.css
mod/book/tool/importhtml/import_form.php
mod/book/tool/importhtml/index.php
mod/book/tool/print/index.php
mod/book/tool/print/locallib.php
mod/book/tool/print/print.css
mod/book/view.php
mod/chat/gui_basic/index.php
mod/chat/index.php
mod/chat/report.php
mod/chat/styles.css
mod/chat/view.php
mod/data/edit.php
mod/data/export.php
mod/data/index.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/locallib.php
mod/data/preset.php
mod/data/renderer.php
mod/data/styles.css
mod/data/templates.php
mod/data/view.php
mod/feedback/analysis.php
mod/feedback/complete.php
mod/feedback/complete_guest.php
mod/feedback/delete_completed.php
mod/feedback/delete_item.php
mod/feedback/delete_template.php
mod/feedback/edit.php
mod/feedback/edit_item.php
mod/feedback/import.php
mod/feedback/index.php
mod/feedback/mapcourse.php
mod/feedback/show_entries.php
mod/feedback/show_entries_anonym.php
mod/feedback/use_templ.php
mod/feedback/use_templ_form.php
mod/feedback/view.php
mod/folder/edit.php
mod/folder/index.php
mod/forum/classes/post_form.php
mod/forum/discuss.php
mod/forum/index.php
mod/forum/lib.php
mod/forum/post.php
mod/forum/search.php
mod/forum/upgrade.txt
mod/forum/view.php
mod/glossary/approve.php
mod/glossary/backup/moodle2/restore_glossary_activity_task.class.php
mod/glossary/db/log.php
mod/glossary/edit.php
mod/glossary/editcategories.php
mod/glossary/index.php
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/tabs.php
mod/glossary/view.php
mod/lesson/import.php
mod/lesson/index.php
mod/lesson/lesson.php
mod/lesson/report.php
mod/page/backup/moodle1/lib.php
mod/page/index.php
mod/page/lang/en/page.php
mod/page/lib.php
mod/page/mod_form.php
mod/page/settings.php
mod/page/tests/generator/lib.php
mod/page/view.php
mod/resource/backup/moodle1/lib.php
mod/resource/backup/moodle2/backup_resource_activity_task.class.php
mod/resource/index.php
mod/resource/lang/en/resource.php
mod/resource/lib.php
mod/resource/locallib.php
mod/resource/mod_form.php
mod/resource/settings.php
mod/resource/tests/generator/lib.php
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/player.php
mod/scorm/renderer.php [new file with mode: 0644]
mod/scorm/report/basic/report.php
mod/scorm/report/default.php
mod/scorm/report/interactions/report.php
mod/scorm/report/objectives/report.php
mod/scorm/report/reportlib.php
mod/scorm/report/userreport.php [new file with mode: 0644]
mod/scorm/report/userreportinteractions.php [new file with mode: 0644]
mod/scorm/report/userreporttabs.php [new file with mode: 0644]
mod/scorm/report/userreporttracks.php [new file with mode: 0644]
mod/scorm/styles.css
mod/scorm/userreport.php [deleted file]
mod/survey/index.php
mod/survey/lang/en/survey.php
mod/survey/lib.php
mod/survey/report.php
mod/survey/save.php
mod/survey/view.php
mod/url/backup/moodle1/lib.php
mod/url/index.php
mod/url/lang/en/url.php
mod/url/lib.php
mod/url/locallib.php
mod/url/mod_form.php
mod/url/settings.php
mod/wiki/editors/html.php
mod/wiki/editors/wiki_editor.php
mod/wiki/files.php
mod/wiki/filesedit.php
mod/wiki/filesedit_form.php
mod/wiki/index.php
mod/wiki/pagelib.php
mod/wiki/renderer.php
mod/wiki/styles.css
mod/workshop/submission.php
question/format/blackboard_six/tests/blackboardformatpool_test.php
question/format/blackboard_six/tests/blackboardsixformatqti_test.php
report/participation/index.php
report/progress/index.php
repository/repository_ajax.php
repository/url/lang/en/repository_url.php
repository/url/lib.php
rss/file.php
tag/edit.php
theme/anomaly/style/base.css
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/grade.css
theme/base/style/user.css
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/forms.less
theme/bootstrapbase/less/moodle/grade.less [deleted file]
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/undo.less
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/style/moodle.css
theme/bootstrapbase/upgrade.txt [new file with mode: 0644]
theme/boxxie/style/core.css
theme/brick/style/colors.css
theme/brick/style/core.css
theme/canvas/style/admin.css
theme/canvas/style/blocks.css
theme/canvas/style/core.css
theme/canvas/style/mods.css
theme/canvas/style/text.css
theme/clean/config.php
theme/formal_white/style/course.css
theme/formal_white/style/formal_white.css
theme/magazine/style/colors.css
theme/magazine/style/core.css
theme/mymobile/jquery/custom131.js
theme/mymobile/style/core.css
theme/nimble/style/core.css
theme/splash/style/core.css
theme/splash/style/pagelayout.css
theme/standard/style/blocks.css
theme/standard/style/core.css
theme/standard/style/course.css
theme/upgrade.txt
user/edit.php
user/editlib.php
user/profile/field/datetime/define.class.php
user/profile/field/datetime/field.class.php
user/profile/field/datetime/lang/en/profilefield_datetime.php
user/profile/field/datetime/version.php
version.php

diff --git a/admin/cli/fix_course_sequence.php b/admin/cli/fix_course_sequence.php
new file mode 100644 (file)
index 0000000..b825422
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This script fixed incorrectly deleted users.
+ *
+ * @package    core
+ * @subpackage cli
+ * @copyright  2013 Marina Glancy
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+
+// Get cli options.
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'courses'           => false,
+        'fix'               => false,
+        'help'              => false
+    ),
+    array(
+        'h' => 'help',
+        'c' => 'courses',
+        'f' => 'fix'
+    )
+);
+
+if ($options['help'] || empty($options['courses'])) {
+    $help =
+"Checks and fixes that course modules and sections reference each other correctly.
+
+Compares DB fields course_sections.sequence and course_modules.section
+checking that:
+- course_sections.sequence contains each module id not more than once in the course
+- for each moduleid from course_sections.sequence the field course_modules.section
+  refers to the same section id (this means course_sections.sequence is more
+  important if they are different)
+- each module in the course is present in one of course_sections.sequence
+- section sequences do not contain non-existing course modules
+
+If there are any mismatches, the message is displayed. If --fix is specified,
+the records in DB are corrected.
+
+This script may run for a long time on big systems if called for all courses.
+
+Avoid executing the script when another user may simultaneously edit any of the
+courses being checked (recommended to run in mainenance mode).
+
+Options:
+-c, --courses         List courses that need to be checked (comma-separated
+                      values or * for all). Required
+-f, --fix             Fix the mismatches in DB. If not specified check only and
+                      report problems to STDERR
+-h, --help            Print out this help
+
+Example:
+\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=*
+\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=2,3,4 --fix
+";
+
+    echo $help;
+    die;
+}
+
+$courseslist = preg_split('/\s*,\s*/', $options['courses'], -1, PREG_SPLIT_NO_EMPTY);
+if (in_array('*', $courseslist)) {
+    $where = '';
+    $params = array();
+} else {
+    list($sql, $params) = $DB->get_in_or_equal($courseslist, SQL_PARAMS_NAMED, 'id');
+    $where = 'WHERE id '. $sql;
+}
+$coursescount = $DB->get_field_sql('SELECT count(id) FROM {course} '. $where, $params);
+
+if (!$coursescount) {
+    cli_error('No courses found');
+}
+echo "Checking $coursescount courses...\n\n";
+
+require_once($CFG->dirroot. '/course/lib.php');
+
+$problems = array();
+$courses = $DB->get_fieldset_sql('SELECT id FROM {course} '. $where, $params);
+foreach ($courses as $courseid) {
+    $errors = course_integrity_check($courseid, null, null, true, empty($options['fix']));
+    if ($errors) {
+        if (!empty($options['fix'])) {
+            // Reset the course cache to make sure cache is recalculated next time the course is viewed.
+            rebuild_course_cache($courseid, true);
+        }
+        foreach ($errors as $error) {
+            cli_problem($error);
+        }
+        $problems[] = $courseid;
+    } else {
+        echo "Course [$courseid] is OK\n";
+    }
+}
+if (!count($problems)) {
+    echo "\n...All courses are OK\n";
+} else {
+    if (!empty($options['fix'])) {
+        echo "\n...Found and fixed ".count($problems)." courses with problems". "\n";
+    } else {
+        echo "\n...Found ".count($problems)." courses with problems. To fix run:\n";
+        echo "\$sudo -u www-data /usr/bin/php admin/cli/fix_course_sequence.php --courses=".join(',', $problems)." --fix". "\n";
+    }
+}
\ No newline at end of file
index 466832c..1f186b6 100644 (file)
@@ -66,6 +66,16 @@ if (empty($_GET['cache']) and empty($_POST['cache']) and empty($_GET['sesskey'])
 }
 
 require('../config.php');
+
+// Invalidate the cache of version.php in any circumstances to help core_component
+// detecting if the version has changed and component cache should be reset.
+if (function_exists('opcache_invalidate')) {
+    opcache_invalidate($CFG->dirroot . '/version.php', true);
+}
+// Make sure the component cache gets rebuilt if necessary, any method that
+// indirectly calls the protected init() method is good here.
+core_component::get_core_subsystems();
+
 require_once($CFG->libdir.'/adminlib.php');    // various admin-only functions
 require_once($CFG->libdir.'/upgradelib.php');  // general upgrade/install related functions
 require_once($CFG->libdir.'/pluginlib.php');   // available updates notifications
index 8eab4bc..c8f69d4 100644 (file)
                    SET visibleold=visible, visible=0
                  WHERE module=?";
         $DB->execute($sql, array($module->id));
-        // clear the course modinfo cache for courses
-        // where we just uninstalld something
-        $sql = "UPDATE {course}
-                   SET modinfo=''
-                 WHERE id IN (SELECT DISTINCT course
+        // Increment course.cacherev for courses where we just made something invisible.
+        // This will force cache rebuilding on the next request.
+        increment_revision_number('course', 'cacherev',
+                "id IN (SELECT DISTINCT course
                                 FROM {course_modules}
-                               WHERE visibleold=1 AND module=?)";
-        $DB->execute($sql, array($module->id));
+                               WHERE visibleold=1 AND module=?)",
+                array($module->id));
         admin_get_root(true, false);  // settings not required - only pages
     }
 
         }
         $DB->set_field("modules", "visible", "1", array("id"=>$module->id)); // Show main module
         $DB->set_field('course_modules', 'visible', '1', array('visibleold'=>1, 'module'=>$module->id)); // Get the previous saved visible state for the course module.
-        // clear the course modinfo cache for courses
-        // where we just made something visible
-        $sql = "UPDATE {course}
-                   SET modinfo = ''
-                 WHERE id IN (SELECT DISTINCT course
+        // Increment course.cacherev for courses where we just made something visible.
+        // This will force cache rebuilding on the next request.
+        increment_revision_number('course', 'cacherev',
+                "id IN (SELECT DISTINCT course
                                 FROM {course_modules}
-                               WHERE visible=1 AND module=?)";
-        $DB->execute($sql, array($module->id));
+                               WHERE visible=1 AND module=?)",
+                array($module->id));
         admin_get_root(true, false);  // settings not required - only pages
     }
 
index 10994fc..276582c 100644 (file)
@@ -32,6 +32,7 @@ if (($hassiteconfig || has_any_capability(array(
             'moodle/badges:createbadge',
             'moodle/badges:manageglobalsettings',
             'moodle/badges:awardbadge',
+            'moodle/badges:configurecriteria',
             'moodle/badges:configuremessages',
             'moodle/badges:configuredetails',
             'moodle/badges:deletebadge'), $systemcontext))) {
@@ -74,6 +75,7 @@ if (($hassiteconfig || has_any_capability(array(
                 'moodle/badges:viewawarded',
                 'moodle/badges:createbadge',
                 'moodle/badges:awardbadge',
+                'moodle/badges:configurecriteria',
                 'moodle/badges:configuremessages',
                 'moodle/badges:configuredetails',
                 'moodle/badges:deletebadge'
index 9f426cc..a67581d 100644 (file)
@@ -38,6 +38,7 @@ $string['stepsdefinitionscontains'] = 'Contains';
 $string['stepsdefinitionsfilters'] = 'Steps definitions';
 $string['stepsdefinitionstype'] = 'Type';
 $string['theninfo'] = 'Then. Checkings to ensure the outcomes are the expected ones';
+$string['unknownexceptioninfo'] = 'There was a problem with Selenium or the browser, try to upgrade Selenium to the latest version. Error: ';
 $string['viewsteps'] = 'Filter';
 $string['wheninfo'] = 'When. Actions that provokes an event';
 $string['wrongbehatsetup'] = 'Something is wrong with behat setup, ensure:<ul>
index 6d1d582..634e32b 100644 (file)
@@ -137,7 +137,7 @@ class auth_plugin_cas extends auth_plugin_ldap {
             // test pgtIou parameter for proxy mode (https connection
             // in background from CAS server to the php server)
             if ($authCAS != 'CAS' && !isset($_GET['pgtIou'])) {
-                $PAGE->set_url('/auth/cas/auth.php');
+                $PAGE->set_url('/login/index.php');
                 $PAGE->navbar->add($CASform);
                 $PAGE->set_title("$site->fullname: $CASform");
                 $PAGE->set_heading($site->fullname);
index e2e295c..678f11b 100644 (file)
@@ -35,13 +35,13 @@ $string['auth_dbfieldpass'] = 'Name of the field containing passwords';
 $string['auth_dbfieldpass_key'] = 'Password field';
 $string['auth_dbfielduser'] = 'Name of the field containing usernames';
 $string['auth_dbfielduser_key'] = 'Username field';
-$string['auth_dbhost'] = 'The computer hosting the database server.';
+$string['auth_dbhost'] = 'The computer hosting the database server. Use a system DSN entry if using ODBC.';
 $string['auth_dbhost_key'] = 'Host';
 $string['auth_dbchangepasswordurl_key'] = 'Password-change URL';
 $string['auth_dbinsertuser'] = 'Inserted user {$a->name} id {$a->id}';
 $string['auth_dbinsertuserduplicate'] = 'Error inserting user {$a->username} - user with this username was already created through \'{$a->auth}\' plugin.';
 $string['auth_dbinsertusererror'] = 'Error inserting user {$a}';
-$string['auth_dbname'] = 'Name of the database itself';
+$string['auth_dbname'] = 'Name of the database itself. Leave empty if using an ODBC DSN.';
 $string['auth_dbname_key'] = 'DB name';
 $string['auth_dbpass'] = 'Password matching the above username';
 $string['auth_dbpass_key'] = 'Password';
index 2f626bd..e21d77a 100644 (file)
@@ -766,26 +766,17 @@ class auth_plugin_mnet extends auth_plugin_base {
             }
             $mnethostlogssql = "
             SELECT
-                mhostlogs.remoteid, mhostlogs.time, mhostlogs.userid, mhostlogs.ip,
-                mhostlogs.course, mhostlogs.module, mhostlogs.cmid, mhostlogs.action,
-                mhostlogs.url, mhostlogs.info, mhostlogs.username, c.fullname as coursename,
-                c.modinfo
+                l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
+                l.action, l.url, l.info, u.username
             FROM
-                (
-                    SELECT
-                        l.id as remoteid, l.time, l.userid, l.ip, l.course, l.module, l.cmid,
-                        l.action, l.url, l.info, u.username
-                    FROM
-                        {user} u
-                        INNER JOIN {log} l on l.userid = u.id
-                    WHERE
-                        u.mnethostid = ?
-                        AND l.id > ?
-                    ORDER BY remoteid ASC
-                    LIMIT 500
-                ) mhostlogs
-                INNER JOIN {course} c on c.id = mhostlogs.course
-            ORDER by mhostlogs.remoteid ASC";
+                {user} u
+                INNER JOIN {log} l on l.userid = u.id
+            WHERE
+                u.mnethostid = ?
+                AND l.id > ?
+                AND l.course IS NOT NULL
+            ORDER by l.id ASC
+            LIMIT 500";
 
             $mnethostlogs = $DB->get_records_sql($mnethostlogssql, array($mnethostid, $mnet_request->response['last log id']));
 
@@ -796,18 +787,18 @@ class auth_plugin_mnet extends auth_plugin_base {
             $processedlogs = array();
 
             foreach($mnethostlogs as $hostlog) {
-                // Extract the name of the relevant module instance from the
-                // course modinfo if possible.
-                if (!empty($hostlog->modinfo) && !empty($hostlog->cmid)) {
-                    $modinfo = unserialize($hostlog->modinfo);
-                    unset($hostlog->modinfo);
-                    $modulearray = array();
-                    foreach($modinfo as $module) {
-                        $modulearray[$module->cm] = $module->name;
+                try {
+                    // Get impersonalised course information. If it is cached there will be no DB queries.
+                    $modinfo = get_fast_modinfo($hostlog->course, -1);
+                    $hostlog->coursename = $modinfo->get_course()->fullname;
+                    if (!empty($hostlog->cmid) && isset($modinfo->cms[$hostlog->cmid])) {
+                        $hostlog->resource_name = $modinfo->cms[$hostlog->cmid]->name;
+                    } else {
+                        $hostlog->resource_name = '';
                     }
-                    $hostlog->resource_name = $modulearray[$hostlog->cmid];
-                } else {
-                    $hostlog->resource_name = '';
+                } catch (moodle_exception $e) {
+                    // Course not found
+                    continue;
                 }
 
                 $processedlogs[] = array (
index 9d98df6..00b36e0 100644 (file)
@@ -363,6 +363,9 @@ class restore_controller extends backup implements loggable {
         if ($this->status != backup::STATUS_NEED_PRECHECK) {
             throw new restore_controller_exception('cannot_precheck_wrong_status', $this->status);
         }
+        // Basic/initial prevention against time/memory limits
+        set_time_limit(1 * 60 * 60); // 1 hour for 1 course initially granted
+        raise_memory_limit(MEMORY_EXTRA);
         $this->precheck = restore_prechecks_helper::execute_prechecks($this, $droptemptablesafter);
         if (!array_key_exists('errors', $this->precheck)) { // No errors, can be executed
             $this->set_status(backup::STATUS_AWAITING);
index a31501c..6040d4b 100644 (file)
@@ -43,7 +43,6 @@ if ($stage & restore_ui::STAGE_CONFIRM + restore_ui::STAGE_DESTINATION) {
     }
 }
 
-$outcome = $restore->process();
 $heading = $course->fullname;
 
 $PAGE->set_title($heading.': '.$restore->get_stage_name());
@@ -52,6 +51,15 @@ $PAGE->navbar->add($restore->get_stage_name());
 
 $renderer = $PAGE->get_renderer('core','backup');
 echo $OUTPUT->header();
+
+// Prepare a progress bar which can display optionally during long-running
+// operations while setting up the UI.
+$slowprogress = new core_backup_display_progress_if_slow();
+// Depending on the code branch above, $restore may be a restore_ui or it may
+// be a restore_ui_independent_stage. Either way, this function exists.
+$restore->set_progress_reporter($slowprogress);
+$outcome = $restore->process();
+
 if (!$restore->is_independent() && $restore->enforce_changed_dependencies()) {
     debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
 }
index 78cd24b..c22faef 100644 (file)
@@ -63,6 +63,7 @@ require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.
 require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
 require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
 require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
+require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress_if_slow.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
index 62696cb..f893b43 100644 (file)
@@ -95,6 +95,11 @@ class core_backup_display_progress extends core_backup_progress {
         echo html_writer::end_div();
     }
 
+    /**
+     * When progress is updated, updates the bar.
+     *
+     * @see core_backup_progress::update_progress()
+     */
     public function update_progress() {
         // If finished...
         if (!$this->is_in_progress_section()) {
diff --git a/backup/util/progress/core_backup_display_progress_if_slow.class.php b/backup/util/progress/core_backup_display_progress_if_slow.class.php
new file mode 100644 (file)
index 0000000..ee9511e
--- /dev/null
@@ -0,0 +1,107 @@
+<?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/>.
+
+/**
+ * Progress handler that uses a standard Moodle progress bar to display
+ * progress. Same as core_backup_display_progress, but the bar does not
+ * appear until a certain time has elapsed, and disappears automatically
+ * after it finishes.
+ *
+ * The bar can be re-used, i.e. if you end all sections it will disappear,
+ * but if you start all sections, a new bar will be output.
+ *
+ * @package core_backup
+ * @copyright 2013 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_backup_display_progress_if_slow extends core_backup_display_progress {
+    /**
+     * @var int Waits this many seconds before displaying progress bar
+     */
+    const DEFAULT_DISPLAY_DELAY = 5;
+
+    /**
+     * @var int Number in the next id to use
+     */
+    private static $nextid = 1;
+
+    /**
+     * @var string HTML id for containing div
+     */
+    protected $id;
+
+    /**
+     * @var int Time at which the progress bar should display (if it isn't yet)
+     */
+    protected $starttime;
+
+    /**
+     * Constructs the progress reporter. This will not output HTML just yet,
+     * until the required delay time expires.
+     *
+     * @param int $delay Delay time (default 5 seconds)
+     */
+    public function __construct($delay = self::DEFAULT_DISPLAY_DELAY) {
+        // Set start time based on delay.
+        $this->starttime = time() + $delay;
+        parent::__construct(false);
+    }
+
+    /**
+     * Adds a div around the parent display so it can be hidden.
+     *
+     * @see core_backup_display_progress::start_html()
+     */
+    public function start_html() {
+        $this->id = 'core_backup_display_progress_if_slow' . self::$nextid;
+        self::$nextid++;
+        echo html_writer::start_div('', array('id' => $this->id));
+        parent::start_html();
+    }
+
+    /**
+     * When progress is updated, after a certain time, starts actually displaying
+     * the progress bar.
+     *
+     * @see core_backup_progress::update_progress()
+     */
+    public function update_progress() {
+        // If we haven't started yet, consider starting.
+        if ($this->starttime) {
+            if (time() > $this->starttime) {
+                $this->starttime = 0;
+            } else {
+                // Do nothing until start time.
+                return;
+            }
+        }
+
+        // We have started, so handle as default.
+        parent::update_progress();
+    }
+
+    /**
+     * Finishes parent display then closes div and hides it.
+     *
+     * @see core_backup_display_progress::end_html()
+     */
+    public function end_html() {
+        parent::end_html();
+        echo html_writer::end_div();
+        echo html_writer::script('document.getElementById("' . $this->id .
+                '").style.display = "none"');
+    }
+}
index bf80a96..251cbc1 100644 (file)
@@ -50,6 +50,11 @@ class restore_ui extends base_ui {
      */
     protected $stage = null;
 
+    /**
+     * @var core_backup_progress Progress indicator (where there is no controller)
+     */
+    protected $progressreporter = null;
+
     /**
      * String mappings to the above stages
      * @var array
@@ -127,6 +132,38 @@ class restore_ui extends base_ui {
     public function get_restoreid() {
         return $this->controller->get_restoreid();
     }
+
+    /**
+     * Gets the progress reporter object in use for this restore UI.
+     *
+     * IMPORTANT: This progress reporter is used only for UI progress that is
+     * outside the restore controller. The restore controller has its own
+     * progress reporter which is used for progress during the main restore.
+     * Use the restore controller's progress reporter to report progress during
+     * a restore operation, not this one.
+     *
+     * This extra reporter is necessary because on some restore UI screens,
+     * there are long-running tasks even though there is no restore controller
+     * in use.
+     *
+     * @return core_backup_null_progress
+     */
+    public function get_progress_reporter() {
+        if (!$this->progressreporter) {
+            $this->progressreporter = new core_backup_null_progress();
+        }
+        return $this->progressreporter;
+    }
+
+    /**
+     * Sets the progress reporter that will be returned by get_progress_reporter.
+     *
+     * @param core_backup_progress $progressreporter Progress reporter
+     */
+    public function set_progress_reporter(core_backup_progress $progressreporter) {
+        $this->progressreporter = $progressreporter;
+    }
+
     /**
      * Executes the restore plan
      * @return bool
index a7464bf..327722e 100644 (file)
@@ -93,10 +93,48 @@ abstract class restore_ui_stage extends base_ui_stage {
  * no use for the restore controller.
  */
 abstract class restore_ui_independent_stage {
+    /**
+     * @var core_backup_progress Optional progress reporter
+     */
+    private $progressreporter;
+
     abstract public function __construct($contextid);
     abstract public function process();
     abstract public function display(core_backup_renderer $renderer);
     abstract public function get_stage();
+
+    /**
+     * Gets the progress reporter object in use for this restore UI stage.
+     *
+     * IMPORTANT: This progress reporter is used only for UI progress that is
+     * outside the restore controller. The restore controller has its own
+     * progress reporter which is used for progress during the main restore.
+     * Use the restore controller's progress reporter to report progress during
+     * a restore operation, not this one.
+     *
+     * This extra reporter is necessary because on some restore UI screens,
+     * there are long-running tasks even though there is no restore controller
+     * in use. There is a similar function in restore_ui. but that class is not
+     * used on some stages.
+     *
+     * @return core_backup_null_progress
+     */
+    public function get_progress_reporter() {
+        if (!$this->progressreporter) {
+            $this->progressreporter = new core_backup_null_progress();
+        }
+        return $this->progressreporter;
+    }
+
+    /**
+     * Sets the progress reporter that will be returned by get_progress_reporter.
+     *
+     * @param core_backup_progress $progressreporter Progress reporter
+     */
+    public function set_progress_reporter(core_backup_progress $progressreporter) {
+        $this->progressreporter = $progressreporter;
+    }
+
     /**
      * Gets an array of progress bar items that can be displayed through the restore renderer.
      * @return array Array of items for the progress bar
@@ -142,11 +180,18 @@ abstract class restore_ui_independent_stage {
  *
  * This is the first stage, it is independent.
  */
-class restore_ui_stage_confirm extends restore_ui_independent_stage {
+class restore_ui_stage_confirm extends restore_ui_independent_stage implements file_progress {
+
     protected $contextid;
     protected $filename = null;
     protected $filepath = null;
     protected $details;
+
+    /**
+     * @var bool True if we have started reporting progress
+     */
+    protected $startedprogress = false;
+
     public function __construct($contextid) {
         $this->contextid = $contextid;
         $this->filename = required_param('filename', PARAM_FILE);
@@ -168,7 +213,35 @@ class restore_ui_stage_confirm extends restore_ui_independent_stage {
         $this->filepath = restore_controller::get_tempdir_name($this->contextid, $USER->id);
 
         $fb = get_file_packer();
-        return ($fb->extract_to_pathname("$CFG->tempdir/backup/".$this->filename, "$CFG->tempdir/backup/$this->filepath/"));
+        $result = $fb->extract_to_pathname("$CFG->tempdir/backup/".$this->filename,
+                "$CFG->tempdir/backup/$this->filepath/", null, $this);
+
+        // If any progress happened, end it.
+        if ($this->startedprogress) {
+            $this->get_progress_reporter()->end_progress();
+        }
+        return $result;
+    }
+
+    /**
+     * Implementation for file_progress interface to display unzip progress.
+     *
+     * @param int $progress Current progress
+     * @param int $max Max value
+     */
+    public function progress($progress = file_progress::INDETERMINATE, $max = file_progress::INDETERMINATE) {
+        $reporter = $this->get_progress_reporter();
+
+        // Start tracking progress if necessary.
+        if (!$this->startedprogress) {
+            $reporter->start_progress('extract_file_to_dir',
+                    ($max == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $max);
+            $this->startedprogress = true;
+        }
+
+        // Pass progress through to whatever handles it.
+        $reporter->progress(
+                ($progress == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $progress);
     }
 
     /**
@@ -475,8 +548,9 @@ class restore_ui_stage_schema extends restore_ui_stage {
         if ($this->stageform === null) {
             $form = new restore_schema_form($this, $PAGE->url);
             $tasks = $this->ui->get_tasks();
-            $content = '';
             $courseheading = false;
+
+            $allsettings = array();
             foreach ($tasks as $task) {
                 if (!($task instanceof restore_root_task)) {
                     if (!$courseheading) {
@@ -484,13 +558,11 @@ class restore_ui_stage_schema extends restore_ui_stage {
                         $form->add_heading('coursesettings', get_string('coursesettings', 'backup'));
                         $courseheading = true;
                     }
-                    // First add each setting
-                    foreach ($task->get_settings() as $setting) {
-                        $form->add_setting($setting, $task);
-                    }
-                    // The add all the dependencies
+                    // Put each setting into an array of settings to add. Adding
+                    // a setting individually is a very slow operation, so we add
+                    // them all in a batch later on.
                     foreach ($task->get_settings() as $setting) {
-                        $form->add_dependencies($setting);
+                        $allsettings[] = array($setting, $task);
                     }
                 } else if ($this->ui->enforce_changed_dependencies()) {
                     // Only show these settings if dependencies changed them.
@@ -505,6 +577,15 @@ class restore_ui_stage_schema extends restore_ui_stage {
                     }
                 }
             }
+
+            // Actually add all the settings that we put in the array.
+            $form->add_settings($allsettings);
+
+            // Add the dependencies for all the settings.
+            foreach ($allsettings as $settingtask) {
+                $form->add_dependencies($settingtask[0]);
+            }
+
             $this->stageform = $form;
         }
         return $this->stageform;
@@ -523,7 +604,7 @@ class restore_ui_stage_schema extends restore_ui_stage {
 class restore_ui_stage_review extends restore_ui_stage {
     /**
      * Constructs the stage
-     * @param backup_ui $ui
+     * @param restore_ui $ui
      */
     public function __construct($ui, array $params=null) {
         $this->stage = restore_ui::STAGE_REVIEW;
@@ -562,7 +643,11 @@ class restore_ui_stage_review extends restore_ui_stage {
             $content = '';
             $courseheading = false;
 
-            foreach ($this->ui->get_tasks() as $task) {
+            $progress = $this->ui->get_progress_reporter();
+            $tasks = $this->ui->get_tasks();
+            $progress->start_progress('initialise_stage_form', count($tasks));
+            $done = 1;
+            foreach ($tasks as $task) {
                 if ($task instanceof restore_root_task) {
                     // If its a backup root add a root settings heading to group nicely
                     $form->add_heading('rootsettings', get_string('rootsettings', 'backup'));
@@ -575,7 +660,10 @@ class restore_ui_stage_review extends restore_ui_stage {
                 foreach ($task->get_settings() as $setting) {
                     $form->add_fixed_setting($setting, $task);
                 }
+                // Update progress.
+                $progress->progress($done++);
             }
+            $progress->end_progress();
             $this->stageform = $form;
         }
         return $this->stageform;
index ae38fa7..f6f9524 100644 (file)
@@ -95,7 +95,11 @@ if ($copy) {
     require_capability('moodle/badges:createbadge', $context);
 
     $cloneid = $badge->make_clone();
-    redirect(new moodle_url('/badges/edit.php', array('id' => $cloneid, 'action' => 'details')));
+    // If a user can edit badge details, they will be redirected to the edit page.
+    if (has_capability('moodle/badges:configuredetails', $context)) {
+        redirect(new moodle_url('/badges/edit.php', array('id' => $cloneid, 'action' => 'details')));
+    }
+    redirect(new moodle_url('/badges/overview.php', array('id' => $cloneid)));
 }
 
 if ($activate) {
index 9bfd389..7a1e0e4 100644 (file)
@@ -40,6 +40,8 @@ $badge = new badge($badgeid);
 $context = $badge->get_context();
 $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
 
+require_capability('moodle/badges:configurecriteria', $context);
+
 if ($badge->type == BADGE_TYPE_COURSE) {
     if (empty($CFG->badges_allowcoursebadges)) {
         print_error('coursebadgesdisabled', 'badges');
@@ -66,7 +68,6 @@ $emsg = optional_param('emsg', '', PARAM_TEXT);
 
 if ((($update == BADGE_CRITERIA_AGGREGATION_ALL) || ($update == BADGE_CRITERIA_AGGREGATION_ANY))) {
     require_sesskey();
-    require_capability('moodle/badges:configurecriteria', $context);
     $obj = new stdClass();
     $obj->id = $badge->criteria[BADGE_CRITERIA_TYPE_OVERALL]->id;
     $obj->method = $update;
index 1c91562..b0a4783 100644 (file)
@@ -41,7 +41,11 @@ $badge = new badge($badgeid);
 $context = $badge->get_context();
 $navurl = new moodle_url('/badges/index.php', array('type' => $badge->type));
 
-require_capability('moodle/badges:configuredetails', $context);
+if ($action == 'message') {
+    require_capability('moodle/badges:configuremessages', $context);
+} else {
+    require_capability('moodle/badges:configuredetails', $context);
+}
 
 if ($badge->type == BADGE_TYPE_COURSE) {
     if (empty($CFG->badges_allowcoursebadges)) {
index 91c316c..756ef91 100644 (file)
@@ -98,7 +98,11 @@ if ($form->is_cancelled()) {
 
     $newbadge = new badge($newid);
     badges_process_badge_image($newbadge, $form->save_temp_file('image'));
-    redirect(new moodle_url('/badges/criteria.php', array('id' => $newid)));
+    // If a user can configure badge criteria, they will be redirected to the criteria page.
+    if (has_capability('moodle/badges:configurecriteria', $PAGE->context)) {
+        redirect(new moodle_url('/badges/criteria.php', array('id' => $newid)));
+    }
+    redirect(new moodle_url('/badges/overview.php', array('id' => $newid)));
 }
 
 echo $OUTPUT->header();
index 71267b6..439282e 100644 (file)
@@ -134,7 +134,7 @@ function block_course_overview_get_sorted_courses() {
 
     $limit = block_course_overview_get_max_user_courses();
 
-    $courses = enrol_get_my_courses('id, shortname, fullname, modinfo, sectioncache');
+    $courses = enrol_get_my_courses();
     $site = get_site();
 
     if (array_key_exists($site->id,$courses)) {
diff --git a/blocks/rss_client/styles.css b/blocks/rss_client/styles.css
new file mode 100644 (file)
index 0000000..1ec870d
--- /dev/null
@@ -0,0 +1,9 @@
+/* RSS Feeds
+-------------------------*/
+.block_rss_client .list li:first-child {
+    border-top-width: 0;
+}
+.block_rss_client .list li {
+    border-top: 1px solid;
+    padding: 5px;
+}
\ No newline at end of file
index fc150c2..7f650c4 100644 (file)
@@ -73,7 +73,6 @@ class block_site_main_menu extends block_list {
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
         }
-        // Casting $course->modinfo to string prevents one notice when the field is null.
         $editbuttons = '';
 
         if ($ismoving) {
index bcab936..b802221 100644 (file)
@@ -75,7 +75,6 @@ class block_social_activities extends block_list {
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
         }
-        // Casting $course->modinfo to string prevents one notice when the field is null.
         $editbuttons = '';
 
         if ($ismoving) {
index 2c36763..5c06993 100644 (file)
@@ -162,7 +162,8 @@ if (!empty($entry->id)) {
 }
 
 require_once('edit_form.php');
-$summaryoptions = array('subdirs'=>false, 'maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>true, 'context'=>$sitecontext);
+$summaryoptions = array('maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes, 'trusttext'=>true, 'context'=>$sitecontext,
+    'subdirs'=>file_area_contains_subdirs($sitecontext, 'blog', 'post', $entry->id));
 $attachmentoptions = array('subdirs'=>false, 'maxfiles'=> 99, 'maxbytes'=>$CFG->maxbytes);
 
 $blogeditform = new blog_edit_form(null, compact('entry', 'summaryoptions', 'attachmentoptions', 'sitecontext', 'courseid', 'modid'));
index 97da6fb..a1a4704 100644 (file)
@@ -263,10 +263,11 @@ class blog_entry implements renderable {
         tag_set('post', $this->id, $this->tags);
 
         // Trigger an event for the new entry.
-        $event = \core\event\blog_entry_created::create(array('objectid' => $this->id,
-                                                            'userid'   => $this->userid,
-                                                            'other'    => array ("subject" => $this->subject)
-                                                      ));
+        $event = \core\event\blog_entry_created::create(array(
+            'objectid'      => $this->id,
+            'relateduserid' => $this->userid,
+            'other'         => array('subject' => $this->subject)
+        ));
         $event->set_custom_data($this);
         $event->trigger();
     }
@@ -274,11 +275,15 @@ class blog_entry implements renderable {
     /**
      * Updates this entry in the database. Access control checks must be done by calling code.
      *
-     * @param mform $form Used for attachments
+     * @param array       $params            Entry parameters.
+     * @param moodleform  $form              Used for attachments.
+     * @param array       $summaryoptions    Summary options.
+     * @param array       $attachmentoptions Attachment options.
+     *
      * @return void
      */
     public function edit($params=array(), $form=null, $summaryoptions=array(), $attachmentoptions=array()) {
-        global $CFG, $USER, $DB, $PAGE;
+        global $CFG, $DB;
 
         $sitecontext = context_system::instance();
         $entry = $this;
@@ -297,12 +302,17 @@ class blog_entry implements renderable {
 
         $entry->lastmodified = time();
 
-        // Update record
+        // Update record.
         $DB->update_record('post', $entry);
         tag_set('post', $entry->id, $entry->tags);
 
-        add_to_log(SITEID, 'blog', 'update', 'index.php?userid='.$USER->id.'&entryid='.$entry->id, $entry->subject);
-        events_trigger('blog_entry_edited', $entry);
+        $event = \core\event\blog_entry_updated::create(array(
+            'objectid'      => $entry->id,
+            'relateduserid' => $entry->userid,
+            'other'         => array('subject' => $entry->subject)
+        ));
+        $event->set_custom_data($entry);
+        $event->trigger();
     }
 
     /**
@@ -321,10 +331,11 @@ class blog_entry implements renderable {
         $DB->delete_records('post', array('id' => $this->id));
         tag_set('post', $this->id, array());
 
-        $event = \core\event\blog_entry_deleted::create(array('objectid' => $this->id,
-                                                            'userid'   => $this->userid,
-                                                            'other'   => array("record" => (array)$record)
-                                                      ));
+        $event = \core\event\blog_entry_deleted::create(array(
+            'objectid'      => $this->id,
+            'relateduserid' => $this->userid,
+            'other'         => array('record' => (array) $record)
+        ));
         $event->add_record_snapshot("post", $record);
         $event->set_custom_data($this);
         $event->trigger();
index 60e5858..689e3be 100644 (file)
@@ -27,7 +27,6 @@ global $CFG;
 require_once($CFG->dirroot . '/blog/locallib.php');
 require_once($CFG->dirroot . '/blog/lib.php');
 
-
 /**
  * Test functions that rely on the DB tables
  */
@@ -154,21 +153,22 @@ class core_bloglib_testcase extends advanced_testcase {
     /**
      * Test various blog related events.
      */
-    public function test_blog_entry_events() {
-        global $USER, $DB;
+    public function test_blog_entry_created_event() {
+        global $USER;
 
         $this->setAdminUser();
         $this->resetAfterTest();
 
-        // Create a blog entry.
+        // Create a blog entry for another user as Admin.
+        $sink = $this->redirectEvents();
         $blog = new blog_entry();
-        $blog->summary = "This is summary of blog";
         $blog->subject = "Subject of blog";
+        $blog->userid = $this->userid;
         $states = blog_entry::get_applicable_publish_states();
         $blog->publishstate = reset($states);
-        $sink = $this->redirectEvents();
         $blog->add();
         $events = $sink->get_events();
+        $sink->close();
         $event = reset($events);
         $sitecontext = context_system::instance();
 
@@ -177,22 +177,78 @@ class core_bloglib_testcase extends advanced_testcase {
         $this->assertEquals($sitecontext->id, $event->contextid);
         $this->assertEquals($blog->id, $event->objectid);
         $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
         $this->assertEquals("post", $event->objecttable);
+        $arr = array(SITEID, 'blog', 'add', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEquals("blog_entry_added", $event->get_legacy_eventname());
+        $this->assertEventLegacyData($blog, $event);
+    }
+
+    /**
+     * Tests for event blog_entry_updated.
+     */
+    public function test_blog_entry_updated_event() {
+        global $USER;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
 
-        // Delete a blog entry.
+        // Edit a blog entry as Admin.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
+        $blog->summary_editor = array('text' => 'Something', 'format' => FORMAT_MOODLE);
+        $blog->edit(array(), null, array(), array());
+        $events = $sink->get_events();
+        $event = array_pop($events);
+        $sink->close();
+
+        // Validate event data.
+        $this->assertInstanceOf('\core\event\blog_entry_updated', $event);
+        $this->assertEquals($sitecontext->id, $event->contextid);
+        $this->assertEquals($blog->id, $event->objectid);
+        $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
+        $this->assertEquals("post", $event->objecttable);
+        $this->assertEquals("blog_entry_edited", $event->get_legacy_eventname());
+        $this->assertEventLegacyData($blog, $event);
+        $arr = array (SITEID, 'blog', 'update', 'index.php?userid=' . $this->userid . '&entryid=' . $blog->id, $blog->subject);
+        $this->assertEventLegacyLogData($arr, $event);
+    }
+
+    /**
+     * Tests for event blog_entry_deleted.
+     */
+    public function test_blog_entry_deleted_event() {
+        global $USER, $DB;
+
+        $this->setAdminUser();
+        $this->resetAfterTest();
+        $sitecontext = context_system::instance();
+
+        // Delete a user blog entry as Admin.
+        $blog = new blog_entry($this->postid);
+        $sink = $this->redirectEvents();
         $record = $DB->get_record('post', array('id' => $blog->id));
         $blog->delete();
         $events = $sink->get_events();
         $event = array_pop($events);
+        $sink->close();
 
         // Validate event data.
         $this->assertInstanceOf('\core\event\blog_entry_deleted', $event);
-        $this->assertEquals(context_system::instance()->id, $event->contextid);
+        $this->assertEquals($sitecontext->id, $event->contextid);
         $this->assertEquals($blog->id, $event->objectid);
         $this->assertEquals($USER->id, $event->userid);
+        $this->assertEquals($this->userid, $event->relateduserid);
         $this->assertEquals("post", $event->objecttable);
         $this->assertEquals($record, $event->get_record_snapshot("post", $blog->id));
         $this->assertSame('blog_entry_deleted', $event->get_legacy_eventname());
-
+        $arr = array(SITEID, 'blog', 'delete', 'index.php?userid=' . $blog->userid, 'deleted blog entry with entry id# ' .
+                $blog->id);
+        $this->assertEventLegacyLogData($arr, $event);
+        $this->assertEventLegacyData($blog, $event);
     }
 }
+
index 1b19fb2..da5cd4c 100644 (file)
@@ -756,7 +756,7 @@ class cache_definition {
      */
     public function set_identifiers(array $identifiers = array()) {
         foreach ($this->requireidentifiers as $identifier) {
-            if (!array_key_exists($identifier, $identifiers)) {
+            if (!isset($identifiers[$identifier])) {
                 throw new coding_exception('Identifier required for cache has not been provided: '.$identifier);
             }
         }
index e57d6ef..476ec0b 100644 (file)
@@ -173,7 +173,7 @@ class cache_factory {
      */
     public function create_cache_from_definition($component, $area, array $identifiers = array(), $aggregate = null) {
         $definitionname = $component.'/'.$area;
-        if (array_key_exists($definitionname, $this->cachesfromdefinitions)) {
+        if (isset($this->cachesfromdefinitions[$definitionname])) {
             $cache = $this->cachesfromdefinitions[$definitionname];
             $cache->set_identifiers($identifiers);
             return $cache;
@@ -368,7 +368,7 @@ class cache_factory {
         if ($aggregate) {
             $id .= '::'.$aggregate;
         }
-        if (!array_key_exists($id, $this->definitions)) {
+        if (!isset($this->definitions[$id])) {
             // This is the first time this definition has been requested.
             if ($this->is_initialising()) {
                 // We're initialising the cache right now. Don't try to create another config instance.
diff --git a/calendar/classes/type_base.php b/calendar/classes/type_base.php
new file mode 100644 (file)
index 0000000..6458220
--- /dev/null
@@ -0,0 +1,116 @@
+<?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/>.
+
+namespace core_calendar;
+
+/**
+ * Defines functions used by calendar type plugins.
+ *
+ * This library provides a unified interface for calendar types.
+ *
+ * @package core_calendar
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class type_base {
+
+    /**
+     * Returns a list of all the possible days for all months.
+     *
+     * This is used to generate the select box for the days
+     * in the date selector elements. Some months contain more days
+     * than others so this function should return all possible days as
+     * we can not predict what month will be chosen (the user
+     * may have JS turned off and we need to support this situation in
+     * Moodle).
+     *
+     * @return array the days
+     */
+    public abstract function get_days();
+
+    /**
+     * Returns a list of all the names of the months.
+     *
+     * @return array the month names
+     */
+    public abstract function get_months();
+
+    /**
+     * Returns the minimum year of the calendar.
+     *
+     * @return int the minumum year
+     */
+    public abstract function get_min_year();
+
+    /**
+     * Returns the maximum year of the calendar.
+     *
+     * @return int the max year
+     */
+    public abstract function get_max_year();
+
+    /**
+     * Returns a formatted string that represents a date in user time.
+     *
+     * @param int $date the timestamp in UTC, as obtained from the database
+     * @param string $format strftime format
+     * @param int|float|string $timezone the timezone to use
+     *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @param bool $fixday if true then the leading zero from %d is removed,
+     *        if false then the leading zero is maintained
+     * @param bool $fixhour if true then the leading zero from %I is removed,
+     *        if false then the leading zero is maintained
+     * @return string the formatted date/time
+     */
+    public abstract function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour);
+
+    /**
+     * Given a $time timestamp in GMT (seconds since epoch), returns an array that represents
+     * the date in user time.
+     *
+     * @param int $time timestamp in GMT
+     * @param float|int|string $timezone the timezone to use to calculate the time
+     *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @return array an array that represents the date in user time
+     */
+    public abstract function timestamp_to_date_array($time, $timezone);
+
+    /**
+     * Provided with a day, month, year, hour and minute in the specific
+     * calendar type convert it into the equivalent Gregorian date.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month and year.
+     */
+    public abstract function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0);
+
+    /**
+     * Provided with a day, month, year, hour and minute in a Gregorian date
+     * convert it into the specific calendar type date.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month and year.
+     */
+    public abstract function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0);
+}
diff --git a/calendar/classes/type_factory.php b/calendar/classes/type_factory.php
new file mode 100644 (file)
index 0000000..535dd2b
--- /dev/null
@@ -0,0 +1,91 @@
+<?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/>.
+
+namespace core_calendar;
+
+/**
+ * Class \core_calendar\type_factory.
+ *
+ * Factory class producing required subclasses of {@link \core_calendar\type_base}.
+ *
+ * @package core_calendar
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class type_factory {
+
+    /**
+     * Returns an instance of the currently used calendar type.
+     *
+     * @param string|null $type the calendar type to use, if none provided use logic to determine
+     * @return calendartype_* the created calendar_type class
+     * @throws coding_exception if the calendar type file could not be loaded
+     */
+    public static function get_calendar_instance($type = null) {
+        if (is_null($type)) {
+            $type = self::get_calendar_type();
+        }
+
+        $class = "\\calendartype_$type\\structure";
+
+        // Ensure the calendar type exists. It may occur that a user has selected a calendar type, which was then
+        // deleted. If this happens we want to fall back on the Gregorian calendar type.
+        if (!class_exists($class)) {
+            $class = "\\calendartype_gregorian\\structure";
+        }
+
+        return new $class();
+    }
+
+    /**
+     * Returns a list of calendar typess available for use.
+     *
+     * @return array the list of calendar types
+     */
+    public static function get_list_of_calendar_types() {
+        $calendars = array();
+        $calendardirs = \core_component::get_plugin_list('calendartype');
+
+        foreach ($calendardirs as $name => $location) {
+            $calendars[$name] = get_string('name', "calendartype_{$name}");
+        }
+
+        return $calendars;
+    }
+
+    /**
+     * Returns the current calendar type in use.
+     *
+     * @return string the current calendar type being used
+     */
+    public static function get_calendar_type() {
+        global $CFG, $USER, $SESSION, $COURSE;
+
+        if (!empty($COURSE->id) and $COURSE->id != SITEID and !empty($COURSE->calendartype)) { // Course calendartype can override all other settings for this page.
+            $return = $COURSE->calendartype;
+        } else if (!empty($SESSION->calendartype)) { // Session calendartype can override other settings.
+            $return = $SESSION->calendartype;
+        } else if (!empty($USER->calendartype)) {
+            $return = $USER->calendartype;
+        } else if (!empty($CFG->calendartype)) {
+            $return = $CFG->calendartype;
+        } else {
+            $return = 'gregorian';
+        }
+
+        return $return;
+    }
+}
diff --git a/calendar/tests/calendartype_test.php b/calendar/tests/calendartype_test.php
new file mode 100644 (file)
index 0000000..10e72ea
--- /dev/null
@@ -0,0 +1,282 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the class that handles testing the calendar type system.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+// The test calendar type.
+require_once($CFG->dirroot . '/calendar/tests/calendartype_test_example.php');
+
+// Used to test the dateselector elements.
+require_once($CFG->libdir . '/form/dateselector.php');
+require_once($CFG->libdir . '/form/datetimeselector.php');
+
+// Used to test the user datetime profile field.
+require_once($CFG->dirroot . '/user/profile/lib.php');
+require_once($CFG->dirroot . '/user/profile/definelib.php');
+require_once($CFG->dirroot . '/user/profile/index_field_form.php');
+
+/**
+ * Unit tests for the calendar type system.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.6
+ */
+class core_calendar_type_testcase extends advanced_testcase {
+
+    /**
+     * The test user.
+     */
+    private $user;
+
+    /**
+     * Test set up.
+     */
+    protected function setUp() {
+        // The user we are going to test this on.
+        $this->user = self::getDataGenerator()->create_user();
+        self::setUser($this->user);
+    }
+
+    /**
+     * Test that setting the calendar type works.
+     */
+    public function test_calendar_type_set() {
+        // We want to reset the test data after this run.
+        $this->resetAfterTest();
+
+        // Test setting it as the 'Test' calendar type.
+        $this->set_calendar_type('test');
+        $this->assertEquals('test', \core_calendar\type_factory::get_calendar_type());
+
+        // Test setting it as the 'Gregorian' calendar type.
+        $this->set_calendar_type('gregorian');
+        $this->assertEquals('gregorian', \core_calendar\type_factory::get_calendar_type());
+    }
+
+    /**
+     * Test that calling core Moodle functions responsible for displaying the date
+     * have the same results as directly calling the same function in the calendar type.
+     */
+    public function test_calendar_type_core_functions() {
+        // We want to reset the test data after this run.
+        $this->resetAfterTest();
+
+        // Test that the core functions reproduce the same results as the Gregorian calendar.
+        $this->core_functions_test('gregorian');
+
+        // Test that the core functions reproduce the same results as the test calendar.
+        $this->core_functions_test('test');
+    }
+
+    /**
+     * Test that dates selected using the date selector elements are being saved as unixtime, and that the
+     * unixtime is being converted back to a valid date to display in the date selector elements for
+     * different calendar types.
+     */
+    public function test_calendar_type_dateselector_elements() {
+        // We want to reset the test data after this run.
+        $this->resetAfterTest();
+
+        // Check converting dates to Gregorian when submitting a date selector element works. Note: the test
+        // calendar is 2 years, 2 months, 2 days, 2 hours and 2 minutes ahead of the Gregorian calendar.
+        $date1 = array();
+        $date1['day'] = 4;
+        $date1['month'] = 7;
+        $date1['year'] = 2013;
+        $date1['hour'] = 0;
+        $date1['minute'] = 0;
+        $date1['timestamp'] = 1372896000;
+        $this->convert_dateselector_to_unixtime_test('dateselector', 'gregorian', $date1);
+
+        $date2 = array();
+        $date2['day'] = 7;
+        $date2['month'] = 9;
+        $date2['year'] = 2015;
+        $date2['hour'] = 0; // The dateselector element does not have hours.
+        $date2['minute'] = 0; // The dateselector element does not have minutes.
+        $date2['timestamp'] = 1372896000;
+        $this->convert_dateselector_to_unixtime_test('dateselector', 'test', $date2);
+
+        $date3 = array();
+        $date3['day'] = 4;
+        $date3['month'] = 7;
+        $date3['year'] = 2013;
+        $date3['hour'] = 23;
+        $date3['minute'] = 15;
+        $date3['timestamp'] = 1372979700;
+        $this->convert_dateselector_to_unixtime_test('datetimeselector', 'gregorian', $date3);
+
+        $date4 = array();
+        $date4['day'] = 7;
+        $date4['month'] = 9;
+        $date4['year'] = 2015;
+        $date4['hour'] = 1;
+        $date4['minute'] = 17;
+        $date4['timestamp'] = 1372979700;
+        $this->convert_dateselector_to_unixtime_test('datetimeselector', 'test', $date4);
+
+        // The date selector element values are set by using the function usergetdate, here we want to check that
+        // the unixtime passed is being successfully converted to the correct values for the calendar type.
+        $this->convert_unixtime_to_dateselector_test('gregorian', $date3);
+        $this->convert_unixtime_to_dateselector_test('test', $date4);
+    }
+
+    /**
+     * Test that the user profile field datetime minimum and maximum year settings are saved as the
+     * equivalent Gregorian years.
+     */
+    public function test_calendar_type_datetime_field_submission() {
+        // We want to reset the test data after this run.
+        $this->resetAfterTest();
+
+        // Create an array with the input values and expected values once submitted.
+        $date = array();
+        $date['inputminyear'] = '1970';
+        $date['inputmaxyear'] = '2013';
+        $date['expectedminyear'] = '1970';
+        $date['expectedmaxyear'] = '2013';
+        $this->datetime_field_submission_test('gregorian', $date);
+
+        // The test calendar is 2 years, 2 months, 2 days in the future, so when the year 1970 is submitted,
+        // the year 1967 should be saved in the DB, as 1/1/1970 converts to 30/10/1967 in Gregorian.
+        $date['expectedminyear'] = '1967';
+        $date['expectedmaxyear'] = '2010';
+        $this->datetime_field_submission_test('test', $date);
+    }
+
+    /**
+     * Test all the core functions that use the calendar type system.
+     *
+     * @param string $type the calendar type we want to test
+     */
+    private function core_functions_test($type) {
+        $this->set_calendar_type($type);
+
+        // Get the calendar.
+        $calendar = \core_calendar\type_factory::get_calendar_instance();
+
+        // Test the userdate function.
+        $this->assertEquals($calendar->timestamp_to_date_string($this->user->timecreated, '', 99, true, true),
+            userdate($this->user->timecreated));
+    }
+
+    /**
+     * Simulates submitting a form with a date selector element and tests that the chosen dates
+     * are converted into unixtime before being saved in DB.
+     *
+     * @param string $element the form element we are testing
+     * @param string $type the calendar type we want to test
+     * @param array $date the date variables
+     */
+    private function convert_dateselector_to_unixtime_test($element, $type, $date) {
+        $this->set_calendar_type($type);
+
+        if ($element == 'dateselector') {
+            $el = new MoodleQuickForm_date_selector('dateselector', null, array('timezone' => 0.0, 'step' => 1));
+        } else {
+            $el = new MoodleQuickForm_date_time_selector('dateselector', null, array('timezone' => 0.0, 'step' => 1));
+        }
+        $el->_createElements();
+        $submitvalues = array('dateselector' => $date);
+
+        $this->assertSame($el->exportValue($submitvalues), array('dateselector' => $date['timestamp']));
+    }
+
+    /**
+     * Test converting dates from unixtime to a date for the calendar type specified.
+     *
+     * @param string $type the calendar type we want to test
+     * @param array $date the date variables
+     */
+    private function convert_unixtime_to_dateselector_test($type, $date) {
+        $this->set_calendar_type($type);
+
+        // Get the calendar.
+        $calendar = \core_calendar\type_factory::get_calendar_instance();
+
+        $usergetdate = $calendar->timestamp_to_date_array($date['timestamp'], 0.0);
+        $comparedate = array(
+            'minute' => $usergetdate['minutes'],
+            'hour' => $usergetdate['hours'],
+            'day' => $usergetdate['mday'],
+            'month' => $usergetdate['mon'],
+            'year' => $usergetdate['year'],
+            'timestamp' => $date['timestamp']
+        );
+
+        $this->assertEquals($comparedate, $date);
+    }
+
+    /**
+     * Test saving the minimum and max year settings for the user datetime field.
+     *
+     * @param string $type the calendar type we want to test
+     * @param array $date the date variables
+     */
+    private function datetime_field_submission_test($type, $date) {
+        $this->set_calendar_type($type);
+
+        // Get the data we are submitting for the form.
+        $formdata = array();
+        $formdata['id'] = 0;
+        $formdata['shortname'] = 'Shortname';
+        $formdata['name'] = 'Name';
+        $formdata['param1'] = $date['inputminyear'];
+        $formdata['param2'] = $date['inputmaxyear'];
+
+        // Mock submitting this.
+        field_form::mock_submit($formdata);
+
+        // Create the user datetime form.
+        $form = new field_form(null, 'datetime');
+
+        // Get the data from the submission.
+        $submissiondata = $form->get_data();
+        // On the user profile field page after get_data, the function define_save is called
+        // in the field base class, which then calls the field's function define_save_preprocess.
+        $field = new profile_define_datetime();
+        $submissiondata = $field->define_save_preprocess($submissiondata);
+
+        // Create an array we want to compare with the date passed.
+        $comparedate = $date;
+        $comparedate['expectedminyear'] = $submissiondata->param1;
+        $comparedate['expectedmaxyear'] = $submissiondata->param2;
+
+        $this->assertEquals($comparedate, $date);
+    }
+
+    /**
+     * Set the calendar type for this user.
+     *
+     * @param string $type the calendar type we want to set
+     */
+    private function set_calendar_type($type) {
+        $this->user->calendartype = $type;
+        session_set_user($this->user);
+    }
+}
diff --git a/calendar/tests/calendartype_test_example.php b/calendar/tests/calendartype_test_example.php
new file mode 100644 (file)
index 0000000..679422e
--- /dev/null
@@ -0,0 +1,176 @@
+<?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/>.
+
+namespace calendartype_test;
+use \core_calendar\type_base;
+
+/**
+ * Handles calendar functions for the test calendar.
+ *
+ * The test calendar is going to be 2 years, 2 days, 2 hours and 2 minutes
+ * in the future of the Gregorian calendar.
+ *
+ * @package core_calendar
+ * @copyright 2013 Mark Nelson <markn@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class structure extends type_base {
+
+    /**
+     * Returns a list of all the possible days for all months.
+     *
+     * This is used to generate the select box for the days
+     * in the date selector elements. Some months contain more days
+     * than others so this function should return all possible days as
+     * we can not predict what month will be chosen (the user
+     * may have JS turned off and we need to support this situation in
+     * Moodle).
+     *
+     * @return array the days
+     */
+    public function get_days() {
+        $days = array();
+
+        for ($i = 1; $i <= 31; $i++) {
+            $days[$i] = $i;
+        }
+
+        return $days;
+    }
+
+    /**
+     * Returns a list of all the names of the months.
+     *
+     * @return array the month names
+     */
+    public function get_months() {
+        $months = array();
+
+        for ($i = 1; $i <= 12; $i++) {
+            $months[$i] = $i;
+        }
+
+        return $months;
+    }
+
+    /**
+     * Returns the minimum year of the calendar.
+     *
+     * @return int the minumum year
+     */
+    public function get_min_year() {
+        return 1970;
+    }
+
+    /**
+     * Returns the maximum year of the calendar.
+     *
+     * @return int the max year
+     */
+    public function get_max_year() {
+        return 2050;
+    }
+
+    /**
+     * Returns a formatted string that represents a date in user time.
+     *
+     * @param int $date the timestamp in UTC, as obtained from the database
+     * @param string $format strftime format
+     * @param int|float|string $timezone the timezone to use
+     *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @param bool $fixday if true then the leading zero from %d is removed,
+     *        if false then the leading zero is maintained
+     * @param bool $fixhour if true then the leading zero from %I is removed,
+     *        if false then the leading zero is maintained
+     * @return string the formatted date/time
+     */
+    public function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour) {
+        return '';
+    }
+
+    /**
+     * Given a $time timestamp in GMT (seconds since epoch), returns an array that represents
+     * the date in user time.
+     *
+     * @param int $time timestamp in GMT
+     * @param float|int|string $timezone the timezone to use to calculate the time
+     *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @return array an array that represents the date in user time
+     */
+    public function timestamp_to_date_array($time, $timezone) {
+        $gregoriancalendar = \core_calendar\type_factory::get_calendar_instance('gregorian');
+        $date = $gregoriancalendar->timestamp_to_date_array($time, $timezone);
+        $newdate = $this->convert_from_gregorian($date['year'], $date['mon'], $date['mday'],
+            $date['hours'], $date['minutes']);
+
+        $date['year'] = $newdate['year'];
+        $date['mon'] = $newdate['month'];
+        $date['mday'] = $newdate['day'];
+        $date['hours'] = $newdate['hour'];
+        $date['minutes']  = $newdate['minute'];
+
+        return $date;
+    }
+
+    /**
+     * Provided with a day, month, year, hour and minute
+     * convert it into the equivalent Gregorian date.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month, year, hour and minute.
+     */
+    public function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+        $timestamp = make_timestamp($year, $month, $day, $hour, $minute);
+        $date = date('Y/n/j/H/i', strtotime('-2 year, -2 months, -2 days, -2 hours, -2 minutes', $timestamp));
+
+        list($year, $month, $day, $hour, $minute) = explode('/', $date);
+
+        return array('year' => (int) $year,
+                     'month' => (int) $month,
+                     'day' => (int) $day,
+                     'hour' => (int) $hour,
+                     'minute' => (int) $minute);
+
+    }
+
+    /**
+     * Provided with a day, month, year, hour and minute in a Gregorian date
+     * convert it into the specific calendar type date.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month, year, hour and minute.
+     */
+    public function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+        $timestamp = make_timestamp($year, $month, $day, $hour, $minute);
+        $date = date('Y/n/j/H/i', strtotime('+2 year, +2 months, +2 days, +2 hours, +2 minutes', $timestamp));
+
+        list($year, $month, $day, $hour, $minute) = explode('/', $date);
+
+        return array('year' => (int) $year,
+                     'month' => (int) $month,
+                     'day' => (int) $day,
+                     'hour' => (int) $hour,
+                     'minute' => (int) $minute);
+    }
+}
diff --git a/calendar/type/gregorian/classes/structure.php b/calendar/type/gregorian/classes/structure.php
new file mode 100644 (file)
index 0000000..d209864
--- /dev/null
@@ -0,0 +1,232 @@
+<?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/>.
+
+namespace calendartype_gregorian;
+use core_calendar\type_base;
+
+/**
+ * Handles calendar functions for the gregorian calendar.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class structure extends type_base {
+
+    /**
+     * Returns a list of all the possible days for all months.
+     *
+     * This is used to generate the select box for the days
+     * in the date selector elements. Some months contain more days
+     * than others so this function should return all possible days as
+     * we can not predict what month will be chosen (the user
+     * may have JS turned off and we need to support this situation in
+     * Moodle).
+     *
+     * @return array the days
+     */
+    public function get_days() {
+        $days = array();
+
+        for ($i = 1; $i <= 31; $i++) {
+            $days[$i] = $i;
+        }
+
+        return $days;
+    }
+
+    /**
+     * Returns a list of all the names of the months.
+     *
+     * @return array the month names
+     */
+    public function get_months() {
+        $months = array();
+
+        for ($i = 1; $i <= 12; $i++) {
+            $months[$i] = userdate(gmmktime(12, 0, 0, $i, 15, 2000), '%B');
+        }
+
+        return $months;
+    }
+
+    /**
+     * Returns the minimum year of the calendar.
+     *
+     * @return int the minumum year
+     */
+    public function get_min_year() {
+        return 1900;
+    }
+
+    /**
+     * Returns the maximum year of the calendar.
+     *
+     * @return int the max year
+     */
+    public function get_max_year() {
+        return 2050;
+    }
+
+    /**
+     * Returns a formatted string that represents a date in user time.
+     *
+     * Returns a formatted string that represents a date in user time
+     * <b>WARNING: note that the format is for strftime(), not date().</b>
+     * Because of a bug in most Windows time libraries, we can't use
+     * the nicer %e, so we have to use %d which has leading zeroes.
+     * A lot of the fuss in the function is just getting rid of these leading
+     * zeroes as efficiently as possible.
+     *
+     * If parameter fixday = true (default), then take off leading
+     * zero from %d, else maintain it.
+     *
+     * @param int $date the timestamp in UTC, as obtained from the database
+     * @param string $format strftime format
+     * @param int|float|string $timezone the timezone to use
+     *        {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @param bool $fixday if true then the leading zero from %d is removed,
+     *        if false then the leading zero is maintained
+     * @param bool $fixhour if true then the leading zero from %I is removed,
+     *        if false then the leading zero is maintained
+     * @return string the formatted date/time
+     */
+    public function timestamp_to_date_string($date, $format, $timezone, $fixday, $fixhour) {
+        global $CFG;
+
+        if (empty($format)) {
+            $format = get_string('strftimedaydatetime', 'langconfig');
+        }
+
+        if (!empty($CFG->nofixday)) { // Config.php can force %d not to be fixed.
+            $fixday = false;
+        } else if ($fixday) {
+            $formatnoday = str_replace('%d', 'DD', $format);
+            $fixday = ($formatnoday != $format);
+            $format = $formatnoday;
+        }
+
+        // Note: This logic about fixing 12-hour time to remove unnecessary leading
+        // zero is required because on Windows, PHP strftime function does not
+        // support the correct 'hour without leading zero' parameter (%l).
+        if (!empty($CFG->nofixhour)) {
+            // Config.php can force %I not to be fixed.
+            $fixhour = false;
+        } else if ($fixhour) {
+            $formatnohour = str_replace('%I', 'HH', $format);
+            $fixhour = ($formatnohour != $format);
+            $format = $formatnohour;
+        }
+
+        // Add daylight saving offset for string timezones only, as we can't get dst for
+        // float values. if timezone is 99 (user default timezone), then try update dst.
+        if ((99 == $timezone) || !is_numeric($timezone)) {
+            $date += dst_offset_on($date, $timezone);
+        }
+
+        $timezone = get_user_timezone_offset($timezone);
+
+        // If we are running under Windows convert to windows encoding and then back to UTF-8
+        // (because it's impossible to specify UTF-8 to fetch locale info in Win32).
+        if (abs($timezone) > 13) { // Server time.
+            $datestring = date_format_string($date, $format, $timezone);
+            if ($fixday) {
+                $daystring  = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
+                $datestring = str_replace('DD', $daystring, $datestring);
+            }
+            if ($fixhour) {
+                $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
+                $datestring = str_replace('HH', $hourstring, $datestring);
+            }
+        } else {
+            $date += (int)($timezone * 3600);
+            $datestring = date_format_string($date, $format, $timezone);
+            if ($fixday) {
+                $daystring  = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
+                $datestring = str_replace('DD', $daystring, $datestring);
+            }
+            if ($fixhour) {
+                $hourstring = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %I', $date)));
+                $datestring = str_replace('HH', $hourstring, $datestring);
+            }
+        }
+
+        return $datestring;
+    }
+
+    /**
+     * Given a $time timestamp in GMT (seconds since epoch), returns an array that
+     * represents the date in user time.
+     *
+     * @param int $time Timestamp in GMT
+     * @param float|int|string $timezone offset's time with timezone, if float and not 99, then no
+     *        dst offset is applied {@link http://docs.moodle.org/dev/Time_API#Timezone}
+     * @return array an array that represents the date in user time
+     */
+    public function timestamp_to_date_array($time, $timezone) {
+        return usergetdate($time, $timezone);
+    }
+
+    /**
+     * Provided with a day, month, year, hour and minute in a specific
+     * calendar type convert it into the equivalent Gregorian date.
+     *
+     * In this function we don't need to do anything except pass the data
+     * back as an array. This is because the date received is Gregorian.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month, year, hour and minute.
+     */
+    public function convert_from_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+        $date = array();
+        $date['year'] = $year;
+        $date['month'] = $month;
+        $date['day'] = $day;
+        $date['hour'] = $hour;
+        $date['minute'] = $minute;
+
+        return $date;
+    }
+
+    /**
+     * Provided with a day, month, year, hour and minute in a specific
+     * calendar type convert it into the equivalent Gregorian date.
+     *
+     * In this function we don't need to do anything except pass the data
+     * back as an array. This is because the date received is Gregorian.
+     *
+     * @param int $year
+     * @param int $month
+     * @param int $day
+     * @param int $hour
+     * @param int $minute
+     * @return array the converted day, month, year, hour and minute.
+     */
+    public function convert_to_gregorian($year, $month, $day, $hour = 0, $minute = 0) {
+        $date = array();
+        $date['year'] = $year;
+        $date['month'] = $month;
+        $date['day'] = $day;
+        $date['hour'] = $hour;
+        $date['minute'] = $minute;
+
+        return $date;
+    }
+}
diff --git a/calendar/type/gregorian/lang/en/calendartype_gregorian.php b/calendar/type/gregorian/lang/en/calendartype_gregorian.php
new file mode 100644 (file)
index 0000000..9d2c5ba
--- /dev/null
@@ -0,0 +1,26 @@
+<?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/>.
+
+/**
+ * Strings for component 'calendartype_gregorian', language 'en'.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['name'] = 'Gregorian';
+$string['pluginname'] = 'Gregorian calendar type';
diff --git a/calendar/type/gregorian/version.php b/calendar/type/gregorian/version.php
new file mode 100644 (file)
index 0000000..deeac17
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Version details.
+ *
+ * @package calendartype_gregorian
+ * @copyright 2008 onwards Foodle Group {@link http://foodle.org}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2013082300; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2012120300; // Requires this Moodle version.
+$plugin->component = 'calendartype_gregorian'; // Full name of the plugin (used for diagnostics).
index 0b2a9b1..1865166 100644 (file)
@@ -448,6 +448,14 @@ $CFG->admin = 'admin';
 // config.php file
 //      $CFG->preventexecpath = true;
 //
+// Use the following flag to set userid for noreply user. If not set then moodle will
+// create dummy user and use -ve value as user id.
+//      $CFG->noreplyuserid = -10;
+//
+// As of version 2.6 Moodle supports admin to set support user. If not set, all mails
+// will be sent to supportemail.
+//      $CFG->supportuserid = -20;
+//
 //=========================================================================
 // 7. SETTINGS FOR DEVELOPMENT SERVERS - not intended for production use!!!
 //=========================================================================
index 917d670..38a323b 100644 (file)
@@ -684,7 +684,6 @@ class dndupload_ajax_processor {
         $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
         // Rebuild the course cache after update action
         rebuild_course_cache($this->course->id, true);
-        $this->course->modinfo = null; // Otherwise we will just get the old version back again.
 
         $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
 
index c549cc1..1021c23 100644 (file)
@@ -70,6 +70,7 @@ $overviewfilesoptions = course_overviewfiles_options($course);
 if (!empty($course)) {
     //add context for editor
     $editoroptions['context'] = $coursecontext;
+    $editoroptions['subdirs'] = file_area_contains_subdirs($coursecontext, 'course', 'summary', 0);
     $course = file_prepare_standard_editor($course, 'summary', $editoroptions, $coursecontext, 'course', 'summary', 0);
     if ($overviewfilesoptions) {
         file_prepare_standard_filemanager($course, 'overviewfiles', $overviewfilesoptions, $coursecontext, 'course', 'overviewfiles', 0);
@@ -84,6 +85,7 @@ if (!empty($course)) {
 } else {
     //editor should respect category context if course context is not set.
     $editoroptions['context'] = $catcontext;
+    $editoroptions['subdirs'] = 0;
     $course = file_prepare_standard_editor($course, 'summary', $editoroptions, null, 'course', 'summary', null);
     if ($overviewfilesoptions) {
         file_prepare_standard_filemanager($course, 'overviewfiles', $overviewfilesoptions, null, 'course', 'overviewfiles', 0);
index 06f7435..2dcbce2 100644 (file)
@@ -6,12 +6,18 @@ require_once($CFG->libdir.'/formslib.php');
 require_once($CFG->libdir.'/completionlib.php');
 require_once($CFG->libdir. '/coursecatlib.php');
 
+/**
+ * The form for handling editing a course.
+ */
 class course_edit_form extends moodleform {
     protected $course;
     protected $context;
 
+    /**
+     * Form definition.
+     */
     function definition() {
-        global $USER, $CFG, $DB, $PAGE;
+        global $CFG, $PAGE;
 
         $mform    = $this->_form;
         $PAGE->requires->yui_module('moodle-course-formatchooser', 'M.course.init_formatchooser',
@@ -38,8 +44,7 @@ class course_edit_form extends moodleform {
         $this->course  = $course;
         $this->context = $context;
 
-/// form definition with new course defaults
-//--------------------------------------------------------------------------------
+        // Form definition with new course defaults.
         $mform->addElement('header','general', get_string('general', 'form'));
 
         $mform->addElement('hidden', 'returnto', null);
@@ -194,6 +199,16 @@ class course_edit_form extends moodleform {
         $mform->addElement('select', 'lang', get_string('forcelanguage'), $languages);
         $mform->setDefault('lang', $courseconfig->lang);
 
+        // Multi-Calendar Support - see MDL-18375.
+        $calendartypes = \core_calendar\type_factory::get_list_of_calendar_types();
+        // We do not want to show this option unless there is more than one calendar type to display.
+        if (count($calendartypes) > 1) {
+            $calendars = array();
+            $calendars[''] = get_string('forceno');
+            $calendars += $calendartypes;
+            $mform->addElement('select', 'calendartype', get_string('forcecalendartype', 'calendar'), $calendars);
+        }
+
         $options = range(0, 10);
         $mform->addElement('select', 'newsitems', get_string('newsitemsnumber'), $options);
         $mform->addHelpButton('newsitems', 'newsitemsnumber');
@@ -247,10 +262,8 @@ class course_edit_form extends moodleform {
             $mform->setDefault('enablecompletion', 0);
         }
 
-//--------------------------------------------------------------------------------
         enrol_course_edit_form($mform, $course, $context);
 
-//--------------------------------------------------------------------------------
         $mform->addElement('header','groups', get_string('groupsettingsheader', 'group'));
 
         $choices = array();
@@ -270,10 +283,7 @@ class course_edit_form extends moodleform {
         $options[0] = get_string('none');
         $mform->addElement('select', 'defaultgroupingid', get_string('defaultgrouping', 'group'), $options);
 
-//--------------------------------------------------------------------------------
-
-/// customizable role names in this course
-//--------------------------------------------------------------------------------
+        // Customizable role names in this course.
         $mform->addElement('header','rolerenaming', get_string('rolerenaming'));
         $mform->addHelpButton('rolerenaming', 'rolerenaming');
 
@@ -286,17 +296,18 @@ class course_edit_form extends moodleform {
             }
         }
 
-//--------------------------------------------------------------------------------
         $this->add_action_buttons();
-//--------------------------------------------------------------------------------
+
         $mform->addElement('hidden', 'id', null);
         $mform->setType('id', PARAM_INT);
 
-/// finally set the current form data
-//--------------------------------------------------------------------------------
+        // Finally set the current form data
         $this->set_data($course);
     }
 
+    /**
+     * Fill in the current page data for this course.
+     */
     function definition_after_data() {
         global $DB;
 
@@ -327,21 +338,31 @@ class course_edit_form extends moodleform {
         }
     }
 
-/// perform some extra moodle validation
+    /**
+     * Validation.
+     *
+     * @param array $data
+     * @param array $files
+     * @return array the errors that were found
+     */
     function validation($data, $files) {
-        global $DB, $CFG;
+        global $DB;
 
         $errors = parent::validation($data, $files);
-        if ($foundcourses = $DB->get_records('course', array('shortname'=>$data['shortname']))) {
-            if (!empty($data['id'])) {
-                unset($foundcourses[$data['id']]);
+
+        // Add field validation check for duplicate shortname.
+        if ($course = $DB->get_record('course', array('shortname' => $data['shortname']), '*', IGNORE_MULTIPLE)) {
+            if (empty($data['id']) || $course->id != $data['id']) {
+                $errors['shortname'] = get_string('shortnametaken', '', $course->fullname);
             }
-            if (!empty($foundcourses)) {
-                foreach ($foundcourses as $foundcourse) {
-                    $foundcoursenames[] = $foundcourse->fullname;
+        }
+
+        // Add field validation check for duplicate idnumber.
+        if (!empty($data['idnumber']) && (empty($data['id']) || $this->course->idnumber != $data['idnumber'])) {
+            if ($course = $DB->get_record('course', array('idnumber' => $data['idnumber']), '*', IGNORE_MULTIPLE)) {
+                if (empty($data['id']) || $course->id != $data['id']) {
+                    $errors['idnumber'] = get_string('courseidnumbertaken', 'error', $course->fullname);
                 }
-                $foundcoursenamestring = implode(',', $foundcoursenames);
-                $errors['shortname']= get_string('shortnametaken', '', $foundcoursenamestring);
             }
         }
 
index a2de1f1..05a5777 100644 (file)
@@ -77,7 +77,8 @@ $editoroptions = array(
     'maxfiles'  => EDITOR_UNLIMITED_FILES,
     'maxbytes'  => $CFG->maxbytes,
     'trusttext' => true,
-    'context'   => $editorcontext
+    'context'   => $editorcontext,
+    'subdirs'   => file_area_contains_subdirs($editorcontext, 'coursecat', 'description', $itemid),
 );
 $category = file_prepare_standard_editor($category, 'description', $editoroptions, $editorcontext, 'coursecat', 'description', $itemid);
 
index 26742db..2d61c28 100644 (file)
@@ -717,20 +717,14 @@ class core_course_external extends external_api {
                     require_capability('moodle/course:changefullname', $context);
                 }
 
-                // Check if the shortname already exist and user have capability.
+                // Check if the user can change shortname.
                 if (array_key_exists('shortname', $course) && ($oldcourse->shortname != $course['shortname'])) {
                     require_capability('moodle/course:changeshortname', $context);
-                    if ($DB->record_exists('course', array('shortname' => $course['shortname']))) {
-                        throw new moodle_exception('shortnametaken', '', '', $course['shortname']);
-                    }
                 }
 
-                // Check if the id number already exist and user have capability.
+                // Check if the user can change the idnumber.
                 if (array_key_exists('idnumber', $course) && ($oldcourse->idnumber != $course['idnumber'])) {
                     require_capability('moodle/course:changeidnumber', $context);
-                    if ($DB->record_exists('course', array('idnumber' => $course['idnumber']))) {
-                        throw new moodle_exception('courseidnumbertaken', '', '', $course['idnumber']);
-                    }
                 }
 
                 // Check if user can change summary.
index bfc5641..ac921dc 100644 (file)
@@ -655,11 +655,12 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['previous'], array('class' => 'mdl-left'));
         $sectiontitle .= html_writer::tag('span', $sectionnavlinks['next'], array('class' => 'mdl-right'));
         // Title attributes
-        $titleattr = 'mdl-align title';
+        $classes = 'sectionname';
         if (!$thissection->visible) {
-            $titleattr .= ' dimmed_text';
+            $classes .= ' dimmed_text';
         }
-        $sectiontitle .= html_writer::tag('div', get_section_name($course, $displaysection), array('class' => $titleattr));
+        $sectiontitle .= $this->output->heading(get_section_name($course, $displaysection), 3, $classes);
+
         $sectiontitle .= html_writer::end_tag('div');
         echo $sectiontitle;
 
index 5cfa240..50494d5 100644 (file)
@@ -853,6 +853,138 @@ function print_log_ods($course, $user, $date, $order='l.time DESC', $modname,
     return true;
 }
 
+/**
+ * Checks the integrity of the course data.
+ *
+ * In summary - compares course_sections.sequence and course_modules.section.
+ *
+ * More detailed, checks that:
+ * - course_sections.sequence contains each module id not more than once in the course
+ * - for each moduleid from course_sections.sequence the field course_modules.section
+ *   refers to the same section id (this means course_sections.sequence is more
+ *   important if they are different)
+ * - ($fullcheck only) each module in the course is present in one of
+ *   course_sections.sequence
+ * - ($fullcheck only) removes non-existing course modules from section sequences
+ *
+ * If there are any mismatches, the changes are made and records are updated in DB.
+ *
+ * Course cache is NOT rebuilt if there are any errors!
+ *
+ * This function is used each time when course cache is being rebuilt with $fullcheck = false
+ * and in CLI script admin/cli/fix_course_sequence.php with $fullcheck = true
+ *
+ * @param int $courseid id of the course
+ * @param array $rawmods result of funciton {@link get_course_mods()} - containst
+ *     the list of enabled course modules in the course. Retrieved from DB if not specified.
+ *     Argument ignored in cashe of $fullcheck, the list is retrieved form DB anyway.
+ * @param array $sections records from course_sections table for this course.
+ *     Retrieved from DB if not specified
+ * @param bool $fullcheck Will add orphaned modules to their sections and remove non-existing
+ *     course modules from sequences. Only to be used in site maintenance mode when we are
+ *     sure that another user is not in the middle of the process of moving/removing a module.
+ * @param bool $checkonly Only performs the check without updating DB, outputs all errors as debug messages.
+ * @return array array of messages with found problems. Empty output means everything is ok
+ */
+function course_integrity_check($courseid, $rawmods = null, $sections = null, $fullcheck = false, $checkonly = false) {
+    global $DB;
+    $messages = array();
+    if ($sections === null) {
+        $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section', 'id,section,sequence');
+    }
+    if ($fullcheck) {
+        // Retrieve all records from course_modules regardless of module type visibility.
+        $rawmods = $DB->get_records('course_modules', array('course' => $courseid), 'id', 'id,section');
+    }
+    if ($rawmods === null) {
+        $rawmods = get_course_mods($courseid);
+    }
+    if (!$fullcheck && (empty($sections) || empty($rawmods))) {
+        // If either of the arrays is empty, no modules are displayed anyway.
+        return true;
+    }
+    $debuggingprefix = 'Failed integrity check for course ['.$courseid.']. ';
+
+    // First make sure that each module id appears in section sequences only once.
+    // If it appears in several section sequences the last section wins.
+    // If it appears twice in one section sequence, the first occurence wins.
+    $modsection = array();
+    foreach ($sections as $sectionid => $section) {
+        $sections[$sectionid]->newsequence = $section->sequence;
+        if (!empty($section->sequence)) {
+            $sequence = explode(",", $section->sequence);
+            $sequenceunique = array_unique($sequence);
+            if (count($sequenceunique) != count($sequence)) {
+                // Some course module id appears in this section sequence more than once.
+                ksort($sequenceunique); // Preserve initial order of modules.
+                $sequence = array_values($sequenceunique);
+                $sections[$sectionid]->newsequence = join(',', $sequence);
+                $messages[] = $debuggingprefix.'Sequence for course section ['.
+                        $sectionid.'] is "'.$sections[$sectionid]->sequence.'", must be "'.$sections[$sectionid]->newsequence.'"';
+            }
+            foreach ($sequence as $cmid) {
+                if (array_key_exists($cmid, $modsection) && isset($rawmods[$cmid])) {
+                    // Some course module id appears to be in more than one section's sequences.
+                    $wrongsectionid = $modsection[$cmid];
+                    $sections[$wrongsectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$wrongsectionid]->newsequence. ','), ',');
+                    $messages[] = $debuggingprefix.'Course module ['.$cmid.'] must be removed from sequence of section ['.
+                            $wrongsectionid.'] because it is also present in sequence of section ['.$sectionid.']';
+                }
+                $modsection[$cmid] = $sectionid;
+            }
+        }
+    }
+
+    // Add orphaned modules to their sections if they exist or to section 0 otherwise.
+    if ($fullcheck) {
+        foreach ($rawmods as $cmid => $mod) {
+            if (!isset($modsection[$cmid])) {
+                // This is a module that is not mentioned in course_section.sequence at all.
+                // Add it to the section $mod->section or to the last available section.
+                if ($mod->section && isset($sections[$mod->section])) {
+                    $modsection[$cmid] = $mod->section;
+                } else {
+                    $firstsection = reset($sections);
+                    $modsection[$cmid] = $firstsection->id;
+                }
+                $sections[$modsection[$cmid]]->newsequence = trim($sections[$modsection[$cmid]]->newsequence.','.$cmid, ',');
+                $messages[] = $debuggingprefix.'Course module ['.$cmid.'] is missing from sequence of section ['.
+                        $modsection[$cmid].']';
+            }
+        }
+        foreach ($modsection as $cmid => $sectionid) {
+            if (!isset($rawmods[$cmid])) {
+                // Section $sectionid refers to module id that does not exist.
+                $sections[$sectionid]->newsequence = trim(preg_replace("/,$cmid,/", ',', ','.$sections[$sectionid]->newsequence.','), ',');
+                $messages[] = $debuggingprefix.'Course module ['.$cmid.
+                        '] does not exist but is present in the sequence of section ['.$sectionid.']';
+            }
+        }
+    }
+
+    // Update changed sections.
+    if (!$checkonly && !empty($messages)) {
+        foreach ($sections as $sectionid => $section) {
+            if ($section->newsequence !== $section->sequence) {
+                $DB->update_record('course_sections', array('id' => $sectionid, 'sequence' => $section->newsequence));
+            }
+        }
+    }
+
+    // Now make sure that all modules point to the correct sections.
+    foreach ($rawmods as $cmid => $mod) {
+        if (isset($modsection[$cmid]) && $modsection[$cmid] != $mod->section) {
+            if (!$checkonly) {
+                $DB->update_record('course_modules', array('id' => $cmid, 'section' => $modsection[$cmid]));
+            }
+            $messages[] = $debuggingprefix.'Course module ['.$cmid.
+                    '] points to section ['.$mod->section.'] instead of ['.$modsection[$cmid].']';
+        }
+    }
+
+    return $messages;
+}
+
 /**
  * For a given course, returns an array of course activity objects
  * Each item in the array contains he following properties:
@@ -884,7 +1016,14 @@ function get_array_of_activities($courseid) {
         return $mod; // always return array
     }
 
-    if ($sections = $DB->get_records("course_sections", array("course"=>$courseid), "section ASC")) {
+    if ($sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence')) {
+        // First check and correct obvious mismatches between course_sections.sequence and course_modules.section.
+        if ($errormessages = course_integrity_check($courseid, $rawmods, $sections)) {
+            debugging(join('<br>', $errormessages));
+            $rawmods = get_course_mods($courseid);
+            $sections = $DB->get_records('course_sections', array('course' => $courseid), 'section ASC', 'id,section,sequence');
+        }
+        // Build array of activities.
        foreach ($sections as $section) {
            if (!empty($section->sequence)) {
                $sequence = explode(",", $section->sequence);
@@ -1929,18 +2068,13 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
 
     // Groupmode.
     if ($hasmanageactivities and plugin_supports('mod', $mod->modname, FEATURE_GROUPS, 0)) {
-        if ($mod->coursegroupmodeforce) {
-            $modgroupmode = $mod->coursegroupmode;
-        } else {
-            $modgroupmode = $mod->groupmode;
-        }
-        if ($modgroupmode == SEPARATEGROUPS) {
+        if ($mod->effectivegroupmode == SEPARATEGROUPS) {
             $nextgroupmode = VISIBLEGROUPS;
             $grouptitle = $str->groupsseparate;
             $forcedgrouptitle = $str->forcedgroupsseparate;
             $actionname = 'groupsseparate';
             $groupimage = 't/groups';
-        } else if ($modgroupmode == VISIBLEGROUPS) {
+        } else if ($mod->effectivegroupmode == VISIBLEGROUPS) {
             $nextgroupmode = NOGROUPS;
             $grouptitle = $str->groupsvisible;
             $forcedgrouptitle = $str->forcedgroupsvisible;
@@ -2051,40 +2185,43 @@ function move_courses($courseids, $categoryid) {
     $newparent = context_coursecat::instance($category->id);
     $i = 1;
 
-    foreach ($courseids as $courseid) {
-        if ($dbcourse = $DB->get_record('course', array('id' => $courseid))) {
-            $course = new stdClass();
-            $course->id = $courseid;
-            $course->category  = $category->id;
-            $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
-            if ($category->visible == 0) {
-                // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
-                // to previous state if somebody unhides the category.
-                $course->visible = 0;
-            }
-
-            $DB->update_record('course', $course);
-
-            // Store the context.
-            $context = context_course::instance($course->id);
-
-            // Update the course object we are passing to the event.
-            $dbcourse->category = $course->category;
-            $dbcourse->sortorder = $course->sortorder;
-
-            // Trigger a course updated event.
-            $event = \core\event\course_updated::create(array(
-                'objectid' => $course->id,
-                'context' => $context,
-                'other' => array('shortname' => $dbcourse->shortname,
-                                 'fullname' => $dbcourse->fullname)
-            ));
-            $event->add_record_snapshot('course', $dbcourse);
-            $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
-            $event->trigger();
+    list($where, $params) = $DB->get_in_or_equal($courseids);
+    $dbcourses = $DB->get_records_select('course', 'id ' . $where, $params);
+    foreach ($dbcourses as $dbcourse) {
+        $course = new stdClass();
+        $course->id = $dbcourse->id;
+        $course->category  = $category->id;
+        $course->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - $i++;
+        if ($category->visible == 0) {
+            // Hide the course when moving into hidden category, do not update the visibleold flag - we want to get
+            // to previous state if somebody unhides the category.
+            $course->visible = 0;
+        }
+
+        $DB->update_record('course', $course);
+
+        // Store the context.
+        $context = context_course::instance($course->id);
+
+        // Update the course object we are passing to the event.
+        $dbcourse->category = $course->category;
+        $dbcourse->sortorder = $course->sortorder;
+        if (isset($course->visible)) {
+            $dbcourse->visible = $course->visible;
+        }
+
+        // Trigger a course updated event.
+        $event = \core\event\course_updated::create(array(
+            'objectid' => $course->id,
+            'context' => $context,
+            'other' => array('shortname' => $dbcourse->shortname,
+                             'fullname' => $dbcourse->fullname)
+        ));
+        $event->add_record_snapshot('course', $dbcourse);
+        $event->set_legacy_logdata(array($course->id, 'course', 'move', 'edit.php?id=' . $course->id, $course->id));
+        $event->trigger();
 
-            $context->update_moved($newparent);
-        }
+        $context->update_moved($newparent);
     }
     fix_course_sortorder();
     cache_helper::purge_by_event('changesincourse');
@@ -2368,6 +2505,20 @@ function update_course($data, $editoroptions = NULL) {
         $data = file_postupdate_standard_filemanager($data, 'overviewfiles', $overviewfilesoptions, $context, 'course', 'overviewfiles', 0);
     }
 
+    // Check we don't have a duplicate shortname.
+    if (!empty($data->shortname) && $oldcourse->shortname != $data->shortname) {
+        if ($DB->record_exists('course', array('shortname' => $data->shortname))) {
+            throw new moodle_exception('shortnametaken', '', '', $data->shortname);
+        }
+    }
+
+    // Check we don't have a duplicate idnumber.
+    if (!empty($data->idnumber) && $oldcourse->idnumber != $data->idnumber) {
+        if ($DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+            throw new moodle_exception('courseidnumbertaken', '', '', $data->idnumber);
+        }
+    }
+
     if (!isset($data->category) or empty($data->category)) {
         // prevent nulls and 0 in category field
         unset($data->category);
@@ -2408,7 +2559,10 @@ function update_course($data, $editoroptions = NULL) {
         $context->update_moved($newparent);
     }
 
-    fix_course_sortorder();
+    if ($movecat || (isset($data->sortorder) && $oldcourse->sortorder != $data->sortorder)) {
+        fix_course_sortorder();
+    }
+
     // purge appropriate caches in case fix_course_sortorder() did not change anything
     cache_helper::purge_by_event('changesincourse');
     if ($changesincoursecat) {
index 8cfe0a0..db70a9f 100644 (file)
@@ -78,7 +78,7 @@ if (!empty($add)) {
 
     if (plugin_supports('mod', $data->modulename, FEATURE_MOD_INTRO, true)) {
         $draftid_editor = file_get_submitted_draft_itemid('introeditor');
-        file_prepare_draft_area($draftid_editor, null, null, null, null);
+        file_prepare_draft_area($draftid_editor, null, null, null, null, array('subdirs'=>true));
         $data->introeditor = array('text'=>'', 'format'=>FORMAT_HTML, 'itemid'=>$draftid_editor); // TODO: add better default
     }
 
index 97724aa..8a7aa14 100644 (file)
@@ -821,7 +821,7 @@ abstract class moodleform_mod extends moodleform {
         $label = is_null($customlabel) ? get_string('moduleintro') : $customlabel;
 
         $mform->addElement('editor', 'introeditor', $label, array('rows' => 10), array('maxfiles' => EDITOR_UNLIMITED_FILES,
-            'noclean' => true, 'context' => $this->context));
+            'noclean' => true, 'context' => $this->context, 'subdirs' => true));
         $mform->setType('introeditor', PARAM_RAW); // no XSS prevention here, users must be trusted
         if ($required) {
             $mform->addRule('introeditor', get_string('required'), 'required', null, 'client');
index 4c42b9f..a890de2 100644 (file)
@@ -641,7 +641,7 @@ class core_course_courselib_testcase extends advanced_testcase {
                     'numsections' => 5),
                 array('createsections' => true));
 
-        // Ensure all 6 (0-5) sections were created and modinfo/sectioninfo cache works properly
+        // Ensure all 6 (0-5) sections were created and course content cache works properly
         $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
         $this->assertEquals(range(0, $course->numsections), $sectionscreated);
 
@@ -656,6 +656,53 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals(range(0, $course->numsections + 1), $sectionscreated);
     }
 
+    public function test_update_course() {
+        global $DB;
+
+        $this->resetAfterTest();
+
+        $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
+
+        $course = new stdClass();
+        $course->fullname = 'Apu loves Unit TÉ™sts';
+        $course->shortname = 'test1';
+        $course->idnumber = '1';
+        $course->summary = 'Awesome!';
+        $course->summaryformat = FORMAT_PLAIN;
+        $course->format = 'topics';
+        $course->newsitems = 0;
+        $course->numsections = 5;
+        $course->category = $defaultcategory;
+
+        $created = create_course($course);
+        // Ensure the checks only work on idnumber/shortname that are not already ours.
+        update_course($created);
+
+        $course->shortname = 'test2';
+        $course->idnumber = '2';
+
+        $created2 = create_course($course);
+
+        // Test duplicate idnumber.
+        $created2->idnumber = '1';
+        try {
+            update_course($created2);
+            $this->fail('Expected exception when trying to update a course with duplicate idnumber');
+        } catch (moodle_exception $e) {
+            $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
+        }
+
+        // Test duplicate shortname.
+        $created2->idnumber = '2';
+        $created2->shortname = 'test1';
+        try {
+            update_course($created2);
+            $this->fail('Expected exception when trying to update a course with a duplicate shortname');
+        } catch (moodle_exception $e) {
+            $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
+        }
+    }
+
     public function test_course_add_cm_to_section() {
         global $DB;
         $this->resetAfterTest(true);
@@ -684,13 +731,14 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEquals($cmids[0], $sequence);
 
         // Add a second, this time using courseid variant of parameters.
+        $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
         course_add_cm_to_section($course->id, $cmids[1], 1);
         $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
         $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
 
-        // Check modinfo was not rebuilt (important for performance if calling
-        // repeatedly).
-        $this->assertNull($DB->get_field('course', 'modinfo', array('id' => $course->id)));
+        // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
+        $this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
+        $this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
 
         // Add one to section that doesn't exist (this might rebuild modinfo).
         course_add_cm_to_section($course, $cmids[2], 2);
@@ -1381,6 +1429,9 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Create a category we are going to move this course to.
         $category = $this->getDataGenerator()->create_category();
 
+        // Create a hidden category we are going to move this course to.
+        $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
+
         // Catch the update events.
         $sink = $this->redirectEvents();
 
@@ -1399,11 +1450,18 @@ class core_course_courselib_testcase extends advanced_testcase {
         // Return the moved course information from the DB.
         $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
 
+        // Now move the course to the hidden category, this will also trigger an event.
+        move_courses(array($course->id), $categoryhidden->id);
+
+        // Return the moved course information from the DB.
+        $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
+
         // Now we want to set the sortorder back to what it was before fix_course_sortorder() was called. The reason for
         // this is because update_course() and move_courses() call fix_course_sortorder() which alters the sort order in
         // the DB, but it does not set the value of the sortorder for the course object passed to the event.
         $updatedcourse->sortorder = $sortorder;
         $movedcourse->sortorder = $category->sortorder + MAX_COURSES_IN_CATEGORY - 1;
+        $movedcoursehidden->sortorder = $categoryhidden->sortorder + MAX_COURSES_IN_CATEGORY - 1;
 
         // Capture the events.
         $events = $sink->get_events();
@@ -1431,6 +1489,17 @@ class core_course_courselib_testcase extends advanced_testcase {
         $this->assertEventLegacyData($movedcourse, $event);
         $expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
         $this->assertEventLegacyLogData($expectedlog, $event);
+
+        $event = $events[2];
+        $this->assertInstanceOf('\core\event\course_updated', $event);
+        $this->assertEquals('course', $event->objecttable);
+        $this->assertEquals($movedcoursehidden->id, $event->objectid);
+        $this->assertEquals(context_course::instance($movedcoursehidden->id)->id, $event->contextid);
+        $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
+        $this->assertEquals('course_updated', $event->get_legacy_eventname());
+        $this->assertEventLegacyData($movedcoursehidden, $event);
+        $expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
+        $this->assertEventLegacyLogData($expectedlog, $event);
     }
 
     /**
@@ -1703,4 +1772,124 @@ class core_course_courselib_testcase extends advanced_testcase {
         $expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
         $this->assertEventLegacyLogData($expectedlegacydata, $event);
     }
+
+    public function test_course_integrity_check() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
+           array('createsections'=>true));
+
+        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
+                array('section' => 0));
+        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
+                array('section' => 0));
+        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
+                array('section' => 0));
+        $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
+
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($correctseq, $section0->sequence);
+        $this->assertEmpty($section1->sequence);
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$page->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+        $this->assertEmpty(course_integrity_check($course->id));
+
+        // Now let's make manual change in DB and let course_integrity_check() fix it:
+
+        // 1. Module appears twice in one section.
+        $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
+        $this->assertEquals(
+                array('Failed integrity check for course ['. $course->id.
+                ']. Sequence for course section ['. $section0->id. '] is "'.
+                $section0->sequence. ','. $page->cmid. '", must be "'.
+                $section0->sequence. '"'),
+                course_integrity_check($course->id));
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($correctseq, $section0->sequence);
+        $this->assertEmpty($section1->sequence);
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$page->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+        // 2. Module appears in two sections (last section wins).
+        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
+        // First message about double mentioning in sequence, second message about wrong section field for $page.
+        $this->assertEquals(array(
+            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+            '] must be removed from sequence of section ['. $section0->id.
+            '] because it is also present in sequence of section ['. $section1->id. ']',
+            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+            '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
+                course_integrity_check($course->id));
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+        $this->assertEquals(''. $page->cmid, $section1->sequence);
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section1->id, $cms[$page->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+        // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
+        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
+        $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+        $this->assertEmpty($section1->sequence);
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+        // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
+        $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
+                $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
+                course_integrity_check($course->id, null, null, true)); // Error!
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+        $this->assertEquals(''. $page->cmid, $section1->sequence);  // Yay, module added to section.
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+        // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
+        $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
+        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
+        $this->assertEquals(array(
+            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+            '] is missing from sequence of section ['. $section0->id. ']',
+            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
+            '] points to section [8765] instead of ['. $section0->id. ']'),
+                course_integrity_check($course->id, null, null, true));
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+
+        // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
+        $DB->delete_records('course_modules', array('id' => $page->cmid));
+        $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
+                $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
+                course_integrity_check($course->id, null, null, true));
+        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
+        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
+        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
+        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
+        $this->assertEmpty($section1->sequence);
+        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
+        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
+        $this->assertEquals(2, count($cms));
+    }
 }
index 98a967d..e7a47f6 100644 (file)
@@ -25,8 +25,9 @@
 $string['database:unenrol'] = 'Unenrol suspended users';
 $string['dbencoding'] = 'Database encoding';
 $string['dbhost'] = 'Database host';
-$string['dbhost_desc'] = 'Type database server IP address or host name';
+$string['dbhost_desc'] = 'Type database server IP address or host name. Use a system DSN name if using ODBC.';
 $string['dbname'] = 'Database name';
+$string['dbname_desc'] = 'Leave empty if using a DSN name in database host.';
 $string['dbpass'] = 'Database password';
 $string['dbsetupsql'] = 'Database setup command';
 $string['dbsetupsql_desc'] = 'SQL command for special database setup, often used to setup communication encoding - example for MySQL and PostgreSQL: <em>SET NAMES \'utf8\'</em>';
index 0a8d906..39d7881 100644 (file)
@@ -41,7 +41,7 @@ if ($ADMIN->fulltree) {
 
     $settings->add(new admin_setting_configpasswordunmask('enrol_database/dbpass', get_string('dbpass', 'enrol_database'), '', ''));
 
-    $settings->add(new admin_setting_configtext('enrol_database/dbname', get_string('dbname', 'enrol_database'), '', ''));
+    $settings->add(new admin_setting_configtext('enrol_database/dbname', get_string('dbname', 'enrol_database'), get_string('dbname_desc', 'enrol_database'), ''));
 
     $settings->add(new admin_setting_configtext('enrol_database/dbencoding', get_string('dbencoding', 'enrol_database'), '', 'utf-8'));
 
index 73eb118..f37e12a 100644 (file)
@@ -962,7 +962,7 @@ class enrol_ldap_plugin extends enrol_plugin {
             $template->groupmodeforce = $courseconfig->groupmodeforce;
             $template->visible        = $courseconfig->visible;
             $template->lang           = $courseconfig->lang;
-            $template->groupmodeforce = $courseconfig->groupmodeforce;
+            $template->enablecompletion = $courseconfig->enablecompletion;
         }
         $course = $template;
 
index 60dbb96..6742087 100644 (file)
@@ -46,7 +46,12 @@ class enrol_meta_plugin extends enrol_plugin {
         } else if (empty($instance->name)) {
             $enrol = $this->get_name();
             $course = $DB->get_record('course', array('id'=>$instance->customint1));
-            $coursename = format_string(get_course_display_name_for_list($course));
+            if ($course) {
+                $coursename = format_string(get_course_display_name_for_list($course));
+            } else {
+                // Use course id, if course is deleted.
+                $coursename = $instance->customint1;
+            }
             return get_string('pluginname', 'enrol_' . $enrol) . ' (' . $coursename . ')';
         } else {
             return format_string($instance->name);
index 9f7d8fa..2a2ee1f 100644 (file)
@@ -193,7 +193,7 @@ class core_enrol_renderer extends plugin_renderer_base {
             if ($canassign and (is_siteadmin() or isset($assignableroles[$roleid])) and !$role['unchangeable']) {
                 $strunassign = get_string('unassignarole', 'role', $role['text']);
                 $icon = html_writer::empty_tag('img', array('alt'=>$strunassign, 'src'=>$iconenrolremove));
-                $url = new moodle_url($pageurl, array('action'=>'unassign', 'role'=>$roleid, 'user'=>$userid));
+                $url = new moodle_url($pageurl, array('action'=>'unassign', 'roleid'=>$roleid, 'user'=>$userid));
                 $rolesoutput .= html_writer::tag('div', $role['text'] . html_writer::link($url, $icon, array('class'=>'unassignrolelink', 'rel'=>$roleid, 'title'=>$strunassign)), array('class'=>'role role_'.$roleid));
             } else {
                 $rolesoutput .= html_writer::tag('div', $role['text'], array('class'=>'role unchangeable', 'rel'=>$roleid));
index 9b0fa48..22d2f74 100644 (file)
@@ -433,7 +433,7 @@ class enrol_self_plugin extends enrol_plugin {
         if ($rusers) {
             $contact = reset($rusers);
         } else {
-            $contact = generate_email_supportuser();
+            $contact = core_user::get_support_user();
         }
 
         // Directly emailing welcome message rather than using messaging.
index bbdc3bc..fcf9cad 100644 (file)
@@ -144,9 +144,9 @@ class core_enrollib_testcase extends advanced_testcase {
         $course = (array)$course;
         $this->assertEquals($basefields, array_keys($course), '', 0, 10, true);
 
-        $courses = enrol_get_all_users_courses($user2->id, false, 'modinfo');
+        $courses = enrol_get_all_users_courses($user2->id, false, 'timecreated');
         $course = reset($courses);
-        $this->assertTrue(property_exists($course, 'modinfo'));
+        $this->assertTrue(property_exists($course, 'timecreated'));
 
         $courses = enrol_get_all_users_courses($user2->id, false, null, 'id DESC');
         $this->assertEquals(array($course3->id, $course2->id, $course1->id), array_keys($courses));
index c01e361..b82ebe4 100644 (file)
@@ -75,7 +75,7 @@ if ($action) {
          */
         case 'unassign':
             if (has_capability('moodle/role:assign', $manager->get_context())) {
-                $role = required_param('role', PARAM_INT);
+                $role = required_param('roleid', PARAM_INT);
                 $user = required_param('user', PARAM_INT);
                 if ($confirm && $manager->unassign_role_from_user($user, $role)) {
                     redirect($PAGE->url);
@@ -83,7 +83,7 @@ if ($action) {
                     $user = $DB->get_record('user', array('id'=>$user), '*', MUST_EXIST);
                     $allroles = $manager->get_all_roles();
                     $role = $allroles[$role];
-                    $yesurl = new moodle_url($PAGE->url, array('action'=>'unassign', 'role'=>$role->id, 'user'=>$user->id, 'confirm'=>1, 'sesskey'=>sesskey()));
+                    $yesurl = new moodle_url($PAGE->url, array('action'=>'unassign', 'roleid'=>$role->id, 'user'=>$user->id, 'confirm'=>1, 'sesskey'=>sesskey()));
                     $message = get_string('unassignconfirm', 'role', array('user'=>fullname($user, true), 'role'=>$role->localname));
                     $pagetitle = get_string('unassignarole', 'role', $role->localname);
                     $pagecontent = $OUTPUT->confirm($message, $yesurl, $PAGE->url);
index 6c82cc3..093a58a 100644 (file)
@@ -114,6 +114,8 @@ YUI.add('moodle-enrol-rolemanager', function(Y) {
                 event.detach();
                 var s = M.str.role, confirmation = {
                     lightbox :  true,
+                    visible  :  true,
+                    centered :  true,
                     title    :  s.confirmunassigntitle,
                     question :  s.confirmunassign,
                     yesLabel :  s.confirmunassignyes,
index aac240a..54f0b53 100644 (file)
@@ -3,32 +3,19 @@
     require('../config.php');
     require_once($CFG->libdir.'/eventslib.php');
 
-    if ($form = data_submitted()) { // form submitted, do not check referer (original page unknown)!
-
-    /// Only deal with real users
+    // Form submitted, do not check referer (original page unknown).
+    if ($form = data_submitted()) {
+        // Only deal with real users.
         if (!isloggedin()) {
             redirect($CFG->wwwroot);
         }
 
-    /// Work out who to send the message to
-        if (!$admin = get_admin() ) {
-            print_error('cannotfindadmin', 'debug');
-        }
-
-        $supportuser = new stdClass();
-        $supportuser->email = $CFG->supportemail ? $CFG->supportemail : $admin->email;
-        $supportuser->firstname = $CFG->supportname ? $CFG->supportname : $admin->firstname;
-        $supportuser->lastname = $CFG->supportname ? '' : $admin->lastname;
-        // emailstop could be hard coded "false" to ensure error reports are sent
-        // but then admin's would have to alter their messaging preferences to temporarily stop them
-        $supportuser->emailstop = $admin->emailstop;
-        $supportuser->maildisplay = true;
-
-    /// Send the message and redirect
+        // Send the message and redirect.
         $eventdata = new stdClass();
-        $eventdata->modulename        = 'moodle';
+        $eventdata->component        = 'moodle';
+        $eventdata->name             = 'errors';
         $eventdata->userfrom          = $USER;
-        $eventdata->userto            = $supportuser;
+        $eventdata->userto            = core_user::get_support_user();
         $eventdata->subject           = 'Error: '. $form->referer .' -> '. $form->requested;
         $eventdata->fullmessage       = $form->text;
         $eventdata->fullmessageformat = FORMAT_PLAIN;
index 8ccb757..64353f0 100644 (file)
@@ -400,7 +400,7 @@ class core_files_renderer extends plugin_renderer_base {
             <button class="{!}fp-file-cancel">'.get_string('cancel').'</button>
         </div>
     </form>
-    <div class="fp-info">
+    <div class="fp-info clearfix">
         <div class="fp-hr"></div>
         <p class="{!}fp-thumbnail"></p>
         <div class="fp-fileinfo">
@@ -699,7 +699,7 @@ class core_files_renderer extends plugin_renderer_base {
             <button class="{!}fp-select-cancel">'.get_string('cancel').'</button>
         </div>
     </form>
-    <div class="fp-info">
+    <div class="fp-info clearfix">
         <div class="fp-hr"></div>
         <p class="{!}fp-thumbnail"></p>
         <div class="fp-fileinfo">
index 03f035e..07123c7 100644 (file)
@@ -55,27 +55,36 @@ class filter_activitynames extends moodle_text_filter {
 
             $modinfo = get_fast_modinfo($courseid);
             if (!empty($modinfo->cms)) {
-                self::$activitylist = array();      /// We will store all the activities here
+                self::$activitylist = array(); // We will store all the created filters here.
 
-                //Sort modinfo by name length
-                $sortedactivities = fullclone($modinfo->cms);
-                usort($sortedactivities, 'filter_activitynames_comparemodulenamesbylength');
+                // Create array of visible activities sorted by the name length (we are only interested in properties name and url).
+                $sortedactivities = array();
+                foreach ($modinfo->cms as $cm) {
+                    // Exclude labels, hidden activities and activities for group members only.
+                    if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
+                        $sortedactivities[] = (object)array(
+                            'name' => $cm->name,
+                            'url' => $cm->url,
+                            'id' => $cm->id,
+                            'namelen' => strlen($cm->name),
+                        );
+                    }
+                }
+                core_collator::asort_objects_by_property($sortedactivities, 'namelen', SORT_NUMERIC);
 
                 foreach ($sortedactivities as $cm) {
-                    //Exclude labels, hidden activities and activities for group members only
-                    if ($cm->visible and empty($cm->groupmembersonly) and $cm->has_view()) {
-                        $title = s(trim(strip_tags($cm->name)));
-                        $currentname = trim($cm->name);
-                        $entitisedname  = s($currentname);
-                        /// Avoid empty or unlinkable activity names
-                        if (!empty($title)) {
-                            $href_tag_begin = html_writer::start_tag('a',
-                                    array('class' => 'autolink', 'title' => $title,
-                                        'href' => $cm->get_url()));
-                            self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
-                            if ($currentname != $entitisedname) { /// If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545
-                                self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
-                            }
+                    $title = s(trim(strip_tags($cm->name)));
+                    $currentname = trim($cm->name);
+                    $entitisedname  = s($currentname);
+                    // Avoid empty or unlinkable activity names.
+                    if (!empty($title)) {
+                        $href_tag_begin = html_writer::start_tag('a',
+                                array('class' => 'autolink', 'title' => $title,
+                                    'href' => $cm->url));
+                        self::$activitylist[$cm->id] = new filterobject($currentname, $href_tag_begin, '</a>', false, true);
+                        if ($currentname != $entitisedname) {
+                            // If name has some entity (&amp; &quot; &lt; &gt;) add that filter too. MDL-17545.
+                            self::$activitylist[$cm->id.'-e'] = new filterobject($entitisedname, $href_tag_begin, '</a>', false, true);
                         }
                     }
                 }
@@ -100,14 +109,3 @@ class filter_activitynames extends moodle_text_filter {
         }
     }
 }
-
-
-
-//This function is used to order module names from longer to shorter
-function filter_activitynames_comparemodulenamesbylength($a, $b)  {
-    if (strlen($a->name) == strlen($b->name)) {
-        return 0;
-    }
-    return (strlen($a->name) < strlen($b->name)) ? 1 : -1;
-}
-
index 2d2c670..48164e2 100644 (file)
@@ -111,8 +111,10 @@ $editoroptions = array(
 );
 
 if (!empty($outcome_rec->id)) {
+    $editoroptions['subdirs'] = file_area_contains_subdirs($systemcontext, 'grade', 'outcome', $outcome_rec->id);
     $outcome_rec = file_prepare_standard_editor($outcome_rec, 'description', $editoroptions, $systemcontext, 'grade', 'outcome', $outcome_rec->id);
 } else {
+    $editoroptions['subdirs'] = false;
     $outcome_rec = file_prepare_standard_editor($outcome_rec, 'description', $editoroptions, $systemcontext, 'grade', 'outcome', null);
 }
 
index 9374a9f..04373b1 100644 (file)
@@ -104,8 +104,10 @@ $editoroptions = array(
 );
 
 if (!empty($scale_rec->id)) {
+    $editoroptions['subdirs'] = file_area_contains_subdirs($systemcontext, 'grade', 'scale', $scale_rec->id);
     $scale_rec = file_prepare_standard_editor($scale_rec, 'description', $editoroptions, $systemcontext, 'grade', 'scale', $scale_rec->id);
 } else {
+    $editoroptions['subdirs'] = false;
     $scale_rec = file_prepare_standard_editor($scale_rec, 'description', $editoroptions, $systemcontext, 'grade', 'scale', null);
 }
 $mform = new edit_scale_form(null, compact('gpr', 'editoroptions'));
index 66d0ed8..a6dcf42 100644 (file)
@@ -783,7 +783,7 @@ class grade_report_grader extends grade_report {
                     $headerlink = $this->gtree->get_element_header($element, true, $this->get_pref('showactivityicons'), false);
 
                     $itemcell = new html_table_cell();
-                    $itemcell->attributes['class'] = $type . ' ' . $catlevel . ' highlightable';
+                    $itemcell->attributes['class'] = $type . ' ' . $catlevel . ' highlightable'. ' i'. $element['object']->id;
 
                     if ($element['object']->is_hidden()) {
                         $itemcell->attributes['class'] .= ' dimmed_text';
@@ -891,7 +891,7 @@ class grade_report_grader extends grade_report {
                 $eid = $this->gtree->get_grade_eid($grade);
                 $element = array('eid'=>$eid, 'object'=>$grade, 'type'=>'grade');
 
-                $itemcell->attributes['class'] .= ' grade';
+                $itemcell->attributes['class'] .= ' grade i'.$itemid;
                 if ($item->is_category_item()) {
                     $itemcell->attributes['class'] .= ' cat';
                 }
@@ -1226,7 +1226,7 @@ class grade_report_grader extends grade_report {
                 $eid = $this->gtree->get_item_eid($item);
                 $element = $this->gtree->locate_element($eid);
                 $itemcell = new html_table_cell();
-                $itemcell->attributes['class'] = 'controls icons';
+                $itemcell->attributes['class'] = 'controls icons i'.$itemid;
                 $itemcell->text = $this->get_icons($element);
                 $iconsrow->cells[] = $itemcell;
             }
@@ -1252,8 +1252,7 @@ class grade_report_grader extends grade_report {
             foreach ($this->gtree->items as $itemid=>$unused) {
                 $item =& $this->gtree->items[$itemid];
                 $itemcell = new html_table_cell();
-                $itemcell->header = true;
-                $itemcell->attributes['class'] .= ' header range';
+                $itemcell->attributes['class'] .= ' range i'. $itemid;
 
                 $hidden = '';
                 if ($item->is_hidden()) {
@@ -1371,6 +1370,7 @@ class grade_report_grader extends grade_report {
 
                 if ($item->needsupdate) {
                     $avgcell = new html_table_cell();
+                    $avgcell->attributes['class'] = 'i'. $itemid;
                     $avgcell->text = $OUTPUT->container(get_string('error'), 'gradingerror');
                     $avgrow->cells[] = $avgcell;
                     continue;
@@ -1414,6 +1414,7 @@ class grade_report_grader extends grade_report {
 
                 if (!isset($sumarray[$item->id]) || $meancount == 0) {
                     $avgcell = new html_table_cell();
+                    $avgcell->attributes['class'] = 'i'. $itemid;
                     $avgcell->text = '-';
                     $avgrow->cells[] = $avgcell;
 
@@ -1428,6 +1429,7 @@ class grade_report_grader extends grade_report {
                     }
 
                     $avgcell = new html_table_cell();
+                    $avgcell->attributes['class'] = 'i'. $itemid;
                     $avgcell->text = $gradehtml.$numberofgrades;
                     $avgrow->cells[] = $avgcell;
                 }
index 8d5f090..f0ff184 100644 (file)
@@ -183,18 +183,19 @@ M.gradereport_grader.classes.report.prototype.table_highlight_row = function (e,
     tr.all('.cell').toggleClass('hmarked');
 };
 /**
- * Highlights a cell in the table
+ * Highlights a column in the table
  *
  * @function
  * @param {Event} e
  * @param {Y.Node} cell
  */
 M.gradereport_grader.classes.report.prototype.table_highlight_column = function(e, cell) {
-    var column = 0;
-    while (cell = cell.previous('.cell')) {
-        column += parseFloat(cell.getAttribute('colspan')) || 1;
+    // Among cell classes find the one that matches pattern / i[\d]+ /
+    var itemclass = (' '+cell.getAttribute('class')+' ').match(/ (i[\d]+) /);
+    if (itemclass) {
+        // Toggle class .vmarked for all cells in the table with the same class
+        this.table.all('.cell.'+itemclass[1]).toggleClass('vmarked');
     }
-    this.table.all('.c'+column).toggleClass('vmarked');
 };
 /**
  * Builds an object containing information at the relevant cell given either
index 32d12f1..59906d7 100644 (file)
 .path-grade-report-grader .flexible th {
-white-space:normal;
+    white-space: normal;
 }
-
 .gradestable {
-    margin-bottom:0;
+    margin-bottom: 0;
 }
-
 .gradestable th.user img {
-width:20px;
-height:20px;
+    width: 20px;
+    height: 20px;
 }
-
 .gradestable th img {
     vertical-align: text-bottom;
     padding-bottom: 0;
 }
-.gradestable th .grade_icons { margin-top: .3em; }
-.gradestable th img.sorticon { margin-left: .3em; }
-.dir-rtl .gradestable th img.sorticon { margin-left: 0; margin-right: .3em; }
-
+.gradestable th .grade_icons {
+    margin-top: .3em;
+}
+.gradestable th img.sorticon {
+    margin-left: .3em;
+}
+.dir-rtl .gradestable th img.sorticon {
+    margin-left: 0;
+    margin-right: .3em;
+}
 table#user-grades .catlevel2 {
-background-color:#f9f9f9;
+    background-color: #f9f9f9;
+}
+table#user-grades tr.range td.cell {
+    font-weight: 700;
 }
-
 table#user-grades tr.avg td.cell {
-background-color:#efefff;
-font-weight:700;
-color:#00008B;
+    background-color: #efefff;
+    font-weight: 700;
+    color: #00008B;
 }
-
 table#user-grades tr.odd td.cell {
-background-color:#efefef;
-white-space:nowrap;
+    background-color: #efefef;
+    white-space: nowrap;
+}
+table#user-grades tr td.overridden {
+    background-color: #F3E4C0;
+}
+table#user-grades tr.odd td.overridden {
+    background-color: #EFD9A4;
+}
+table#user-grades tr td.ajaxoverridden {
+    background-color: #FFE3A0;
+}
+table#user-grades tr.odd td.ajaxoverridden {
+    background-color: #FFDA83;
 }
-
-table#user-grades tr td.overridden {background-color:#F3E4C0;}
-table#user-grades tr.odd td.overridden {background-color:#EFD9A4;}
-
-table#user-grades tr td.ajaxoverridden {background-color:#FFE3A0;}
-table#user-grades tr.odd td.ajaxoverridden {background-color:#FFDA83;}
-
 table#user-grades tr.even td.excluded {
-background-color:#EABFFF;
+    background-color: #EABFFF;
 }
-
 table#user-grades tr.odd td.excluded {
-background-color:#E5AFFF;
+    background-color: #E5AFFF;
 }
-
 table#user-grades tr.odd th.header {
-background-color:#efefef;
-background-image:none;
+    background-color: #efefef;
+    background-image: none;
+}
+table#user-grades tr.even th.header {
+    background-image: none;
 }
-
 table#user-grades tr.groupavg td.cell {
-background-color:#efffef;
-font-weight:700;
-color:#006400;
+    background-color: #efffef;
+    font-weight: 700;
+    color: #006400;
 }
-
 table#user-grades td.cat,
 table#user-grades td.course {
-font-weight:700;
+    font-weight: 700;
 }
-
 table#user-grades {
-font-size:10px;
-width:auto;
-background-color:transparent;
-border-style:solid;
-border-width:1px;
-margin:20px 0 0;
+    font-size: 10px;
+    width: auto;
+    background-color: transparent;
+    border-style: solid;
+    border-width: 1px;
+    margin: 20px 0 0;
 }
-
 .path-grade-report-grader #overDiv table {
-margin:0;
+    margin: 0;
 }
-
 .path-grade-report-grader #overDiv table td.feedback {
-border:0;
+    border: 0;
 }
-
 .path-grade-report-grader #overDiv .feedback {
-font-size:70%;
-background-color:#ABF;
-color:#000;
-font-family:Verdana;
-font-weight:400;
+    font-size: 70%;
+    background-color: #ABF;
+    color: #000;
+    font-family: Verdana;
+    font-weight: 400;
 }
-
 .path-grade-report-grader #overDiv .caption {
-font-size:70%;
-background-color:#56C;
-color:#CCF;
-font-family:Arial;
-font-weight:700;
+    font-size: 70%;
+    background-color: #56C;
+    color: #CCF;
+    font-family: Arial;
+    font-weight: 700;
 }
-
 .path-grade-report-grader #overDiv .intersection {
-font-size:70%;
-background-color:#ABF;
-color:#000;
-font-family:Verdana;
-font-weight:400;
+    font-size: 70%;
+    background-color: #ABF;
+    color: #000;
+    font-family: Verdana;
+    font-weight: 400;
 }
-
 .path-grade-report-grader #overDiv .intersectioncaption {
-background-color:#56C;
-color:#CCF;
-font-family:Arial;
-font-weight:700;
+    background-color: #56C;
+    color: #CCF;
+    font-family: Arial;
+    font-weight: 700;
 }
-
 .path-grade-report-grader div.submit {
-margin-top:20px;
-text-align:center;
+    margin-top: 20px;
+    text-align: center;
 }
-
 table#user-grades td {
-text-align:right;
-border-style:solid;
-border-width:0 1px 1px 0;
+    text-align: right;
+    border-style: solid;
+    border-width: 0 1px 1px 0;
 }
-
 table#user-grades th.category {
-vertical-align:top;
-border-style:solid;
-border-width:1px 1px 0;
+    vertical-align: top;
+    border-style: solid;
+    border-width: 1px 1px 0;
 }
-
 table#user-grades th.user {
-text-align:left;
-border-style:solid;
-border-width:0 0 1px;
+    text-align: left;
+    border-style: solid;
+    border-width: 1px 0;
 }
-
 table#user-grades th.userfield {
-border-style:solid;
-border-width:0 0 1px 1px;
+    border-style: solid;
+    border-width: 1px;
 }
-
 table#user-grades th.categoryitem,
 table#user-grades td.topleft {
-vertical-align: bottom;
-border-style:solid;
-border-width:0 1px;
+    vertical-align: bottom;
+    border-style: solid;
+    border-width: 0 1px;
 }
-
 .path-grade-report-grader td,.path-grade-report-grader th {
-border-color:#CECECE;
+    border-color: #CECECE;
 }
-
 .path-grade-report-grader table#participants th {
-vertical-align:top;
-width:auto;
+    vertical-align: top;
+    width: auto;
 }
-
 table#user-grades td.fillerfirst {
-border-style:solid;
-border-width:0 0 0 1px;
+    border-style: solid;
+    border-width: 0 0 0 1px;
 }
-
 table#user-grades td.fillerlast {
-border-style:solid;
-border-width:0 1px 0 0;
+    border-style: solid;
+    border-width: 0 1px 0 0;
 }
-
-table#user-grades th.item ,
+table#user-grades th.item,
 table#user-grades th.categoryitem,
 table#user-grades th.courseitem {
-border-bottom-color:#000;
-vertical-align:bottom;
-border-style:solid;
-border-width:1px;
+    border-bottom-color: #000;
+    vertical-align: bottom;
+    border-style: solid;
+    border-width: 1px;
 }
-
 div.gradertoggle {
-display:inline;
-margin-left:20px;
+    display: inline;
+    margin-left: 20px;
 }
-
 table#user-grades th.range {
-text-align:right;
-border-style:solid;
-border-width:1px;
+    text-align: right;
+    border-style: solid;
+    border-width: 1px;
 }
-
 table#user-grades .userpic {
-display:inline;
-margin-right:10px;
+    display: inline;
+    margin-right: 10px;
 }
-
 table#user-grades .quickfeedback {
-border:1px dashed #000;
-margin-left: 10px;
+    border: 1px dashed #000;
+    margin-left: 10px;
+}
+.dir-rtl table#user-grades .quickfeedback {
+    margin-left: 0;
+    margin-right: 10px;
 }
-.dir-rtl table#user-grades .quickfeedback { margin-left: 0; margin-right: 10px;}
-
 .path-grade-report-grader #siteconfiglink {
-text-align:right;
+    text-align: right;
 }
-
 table#user-grades .datesubmitted {
-font-size:.7em;
+    font-size: .7em;
 }
-
 table#user-grades td.cell {
-padding-left:5px;
-padding-right:5px;
-vertical-align:middle;
+    padding-left: 5px;
+    padding-right: 5px;
+    vertical-align: middle;
 }
-
 .path-grade-report-grader table {
-border-collapse:collapse;
-background-color:#fff;
-border-color:#cecece;
+    border-collapse: collapse;
+    background-color: #fff;
+    border-color: #cecece;
 }
-
 .path-grade-report-grader th {
-padding:1px 10px;
+    padding: 1px 10px;
 }
-
 .path-grade-report-grader span.inclusion-links {
-margin:0 5px 0 10px;
+    margin: 0 5px 0 10px;
 }
-
 table#user-grades .item {
-background-color:#e9e9e9;
+    background-color: #e9e9e9;
 }
-
 .path-grade-report-grader table tr.odd th.header {
-background-color:#efefef;
-background-image:none;
-border-width:0 0 1px;
+    background-color: #efefef;
+    background-image: none;
+    border-width: 0 0 1px;
 }
-
 .path-grade-report-grader table tr.heading th.header {
-border-top:1px solid #cecece;
+    border-top: 1px solid #cecece;
 }
-
 table#user-grades tr.heading th.categoryitem,
 table#user-grades tr.heading th.courseitem {
-border-width:0 0 0 1px;
+    border-width: 0 0 0 1px;
 }
-
 table#user-grades th.category.header.catlevel1 {
-vertical-align:top;
-border-style:solid;
-border-width:1px 1px 0 0;
+    vertical-align: top;
+    border-style: solid;
+    border-width: 1px 1px 0 0;
 }
-
 .path-grade-report-grader div.left_scroller th.user a {
-vertical-align:middle;
-margin:0;
-padding:0;
+    vertical-align: middle;
+    margin: 0;
+    padding: 0;
 }
-
 table#user-grades th.categoryitem,
 table#user-grades th.courseitem,
 .path-grade-report-grader table td.topleft {
-vertical-align:bottom;
-border-color:#cecece #cecece #000;
-border-style:solid;
-border-width:0 1px 1px;
+    vertical-align: bottom;
+    border-color: #cecece #cecece #000;
+    border-style: solid;
+    border-width: 0 1px 1px;
 }
-
 .path-grade-report-grader table td.topleft {
-border-bottom:0;
+    border-bottom: 0;
 }
-
 table#user-grades td.topleft {
-background-color:#fff;
+    background-color: #fff;
 }
-
 .path-grade-report-grader th.user img.userpicture {
-border:3px double #cecece;
-vertical-align:top;
-width:2.7em;
-height:2.7em;
-margin-right:10px;
+    border: 3px double #cecece;
+    vertical-align: top;
+    width: 2.7em;
+    height: 2.7em;
+    margin-right: 10px;
 }
-
 .path-grade-report-grader a.quickedit {
-line-height:1em;
-display:block;
-float:right;
-clear:none;
-font-size:9px;
-background-color:transparent;
-margin:.1em 0 0;
+    line-height: 1em;
+    display: block;
+    float: right;
+    clear: none;
+    font-size: 9px;
+    background-color: transparent;
+    margin: .1em 0 0;
 }
-
 .path-grade-report-grader a.quickedit2 {
-display:block;
-float:right;
-clear:none;
-background-color:transparent;
-margin:1.3em 0 0;
+    display: block;
+    float: right;
+    clear: none;
+    background-color: transparent;
+    margin: 1.3em 0 0;
 }
-
 .path-grade-report-grader table#quick_edit {
-border:1px solid #cecece;
-margin:0 auto;
+    border: 1px solid #cecece;
+    margin: 0 auto;
 }
-
 .path-grade-report-grader table#quick_edit td {
-vertical-align:middle;
-border:1px solid #cecece;
-text-align:left;
-margin:0;
-padding:5px;
+    vertical-align: middle;
+    border: 1px solid #cecece;
+    text-align: left;
+    margin: 0;
+    padding: 5px;
 }
-
 .path-grade-report-grader table#quick_edit td img {
-border:3px double #cecece;
-vertical-align:middle;
-padding:0;
+    border: 3px double #cecece;
+    vertical-align: middle;
+    padding: 0;
 }
-
 .path-grade-report-grader td input.text {
-border:1px solid #666;
+    border: 1px solid #666;
 }
-
 .path-grade-report-grader td input.submit {
-margin: 10px 10px 0px 10px;
+    margin: 10px 10px 0px 10px;
 }
-
 .path-grade-report-grader table#quick_edit td.fullname {
-border-left:0;
-padding-left:5px;
+    border-left: 0;
+    padding-left: 5px;
 }
-
 .path-grade-report-grader table#quick_edit td.picture {
-border-right:0;
+    border-right: 0;
 }
-
 .path-grade-report-grader table#quick_edit td.finalgrade input {
-width:5em;
+    width: 5em;
 }
-
 .path-grade-report-grader h1 {
-text-align:center;
-clear:both;
+    text-align: center;
+    clear: both;
 }
-
 .path-grade-report-grader input.center {
-margin:10px auto 0;
+    margin: 10px auto 0;
 }
-
 .path-grade-report-grader .lefttbody {
-width:auto;
-vertical-align:middle;
+    width: auto;
+    vertical-align: middle;
 }
-
 table#user-grades th.fixedcolumn {
-border:1px solid #cecece;
-vertical-align:middle;
+    border: 1px solid #cecece;
+    vertical-align: middle;
 }
-
 .path-grade-report-grader table#fixed_column th {
-border:1px solid #cecece;
-vertical-align:middle;
-border-right-color:#000;
+    border: 1px solid #cecece;
+    vertical-align: middle;
+    border-right-color: #000;
 }
-
 .path-grade-report-grader table#fixed_column th.user{
-border-right-color:#cecece;
+    border-right-color: #cecece;
 }
-
 .path-grade-report-grader table#fixed_column {
-padding-top:20px;
-border-top:1px solid #cecece;
-background-color:#fff;
+    padding-top: 20px;
+    border-top: 1px solid #cecece;
+    background-color: #fff;
 }
-
 .path-grade-report-grader .left_scroller {
-float:left;
-clear:none;
-padding-top:20px;
+    float: left;
+    clear: none;
+    padding-top: 20px;
+}
+.path-grade-report-grader.dir-rtl .left_scroller {
+    float: right;
 }
-.path-grade-report-grader.dir-rtl .left_scroller {float:right;}
-
-
 .path-grade-report-grader .right_scroller {
-width:auto;
-clear:none;
-overflow-x:scroll;
+    width: auto;
+    clear: none;
+    overflow-x: scroll;
 }
-
 .path-grade-report-grader table tr.avg,
 .path-grade-report-grader table tr.groupavg td,
 .path-grade-report-grader table tr.avg td,
@@ -398,48 +352,39 @@ overflow-x:scroll;
 .path-grade-report-grader table tr.range_row,
 .path-grade-report-grader table tr.range_row th,
 div.right_scroller tr {
-height:2em;
+    height: 2em;
 }
-
 table#user-grades tr.groupavg td.cell,
 tr.groupavg th.header {
-background-color:#efffef;
+    background-color: #efffef;
 }
-
 .path-grade-report-grader form td.excluded {
-color:red;
+    color: red;
 }
-
 .path-grade-report-grader .excludedfloater {
-font-weight:700;
-color:red;
-font-size:9px;
-float:left;
+    font-weight: 700;
+    color: red;
+    font-size: 9px;
+    float: left;
 }
-
 .path-grade-report-grader span.gradepass {
-color:#298721;
+    color: #298721;
 }
-
 .path-grade-report-grader span.gradefail {
-color:#890d0d;
+    color: #890d0d;
 }
-
 .path-grade-report-grader .gradeweight {
-color:#461d7c;
-font-weight:700;
+    color: #461d7c;
+    font-weight: 700;
 }
-
 .path-grade-report-grader td select {
-font-size:100%;
-padding:0;
+    font-size: 100%;
+    padding: 0;
 }
-
 .path-grade-report-grader .right_scroller td select {
-font-size:86%;
-padding:0;
+    font-size: 86%;
+    padding: 0;
 }
-
 .path-grade-report-grader tr.avg,
 .path-grade-report-grader tr.controls,
 .path-grade-report-grader td.controls,
@@ -449,24 +394,21 @@ padding:0;
 .path-grade-report-grader th.range,
 .path-grade-report-grader td.range,
 .path-grade-report-grader tr.heading th.range {
-height:2em!important;
-white-space:nowrap;
+    height: 2em!important;
+    white-space: nowrap;
 }
-
 .path-grade-report-grader .heading_name_row th {
-white-space:nowrap;
-width:2000px;
+    white-space: nowrap;
+    width: 2000px;
 }
 
 /*MDL-21088 - IE 7 ignores nowraps on tds or ths so we put a span within it with a nowrap on it*/
 .path-grade-report-grader heading_name_row th span {
-    white-space:nowrap;
+    white-space: nowrap;
 }
-
 .path-grade-report-grader .grade_icons img.ajax {
-float:right;
+    float: right;
 }
-
 .path-grade-report-grader .gradestable th.user,
 .path-grade-report-grader .gradestable th.range,
 .path-grade-report-grader .flexible th,
@@ -475,112 +417,97 @@ float:right;
 .path-grade-report-grader .flexible td a,
 .path-grade-report-grader .gradestable th.range,
 .path-grade-report-grader td {
-white-space:nowrap;
+    white-space: nowrap;
 }
-
 table#user-grades .catlevel1,
 table#user-grades .r1,
 .path-grade-report-grader table tr.even td.cell,
 .path-grade-report-grader table tr.even th {
-background-color:#fff;
+    background-color: #fff;
 }
-
 table#user-grades .catlevel3,
 .path-grade-report-grader table tr.odd td.cell {
-background-color:#efefef;
+    background-color: #efefef;
 }
-
 table#fixed_column tr.odd th ,
 table#user-grades tr.odd th {
-background-color:#efefef;
+    background-color: #efefef;
 }
-
-table#user-grades td.vmarked,
-table#user-grades tr.odd td.vmarked,
 table#user-grades td.vmarked,
 table#user-grades tr.odd td.vmarked,
-table#user-grades tr.even td.vmarked {
-background-color:#fc3;
+table#user-grades tr.avg td.vmarked,
+table#user-grades tr.controls td.vmarked,
+table#user-grades .catlevel1.vmarked,
+table#user-grades .catlevel2.vmarked,
+table#user-grades .catlevel3.vmarked,
+table#user-grades tr.range td.vmarked,
+table#user-grades tr.groupavg td.vmarked {
+    background-color: #fc3;
 }
-
-table#user-grades td.hmarked,
-table#user-grades tr.odd td.hmarked,
 table#user-grades td.hmarked,
 table#user-grades tr.odd td.hmarked,
-table#user-grades tr.even td.hmarked {
-background-color:#ff9;
+table#user-grades tr.even td.hmarked,
+table#user-grades tr.odd th.hmarked,
+table#user-grades tr.even th.hmarked {
+    background-color: #ff9;
 }
-
 table#user-grades td.hmarked.vmarked,
 table#user-grades tr.odd td.hmarked.vmarked,
-table#user-grades td.hmarked.vmarked,
-table#user-grades tr.even td.hmarked.vmarked,
-table#user-grades tr.odd td.hmarked.vmarked {
-background-color:#fc9;
+table#user-grades tr.even td.hmarked.vmarked {
+    background-color: #fc9;
 }
-
 table#user-grades tr.heading,
 table#user-grades .heading td {
-border-style:solid;
-border-width:0;
+    border-style: solid;
+    border-width: 0;
 }
-
 table#user-grades td.userfield,
 table#user-grades th,
 .path-grade-report-grader div.gradeparent,
 .path-grade-report-grader .ie6 form,
 table#user-grades td.ajax {
-text-align:left;
+    text-align: left;
 }
-
 .dir-rtl table#user-grades td.userfield,
 .dir-rtl table#user-grades th,
 .path-grade-report-grader.dir-rtl  div.gradeparent,
 .path-grade-report-grader.dir-rtl  .ie6 form,
 .dir-rtl table#user-grades td.ajax {
-text-align:right;
+    text-align: right;
 }
-
 .path-grade-report-grader .gradeparent {
-    overflow:auto;
+    overflow: auto;
 }
-
-.path-grade-report-grader table tr.avg td.cell,
 table#user-grades td.controls,
-.path-grade-report-grader table tr.avg,
-.path-grade-report-grader table tr.avg td,
-.path-grade-report-grader table tr.avg th {
-background-color:#f3ead8;
+.path-grade-report-grader table tr.avg .cell,
+.path-grade-report-grader table tr.range .cell {
+    background-color: #f3ead8;
 }
-
 .path-grade-report-grader div.left_scroller tr,
 .path-grade-report-grader div.right_scroller tr,
 .path-grade-report-grader div.left_scroller td,
 .path-grade-report-grader div.right_scroller td,
 .path-grade-report-grader div.left_scroller th,
 .path-grade-report-grader div.right_scroller th {
-    height:4.5em;
-    font-size:10px;
+    height: 4.5em;
+    font-size: 10px;
 }
-
 .path-grade-report-grader table th.user,
 .path-grade-report-grader table td.userfield {
-    text-align:left;
-    vertical-align:middle;
+    text-align: left;
+    vertical-align: middle;
 }
-
-.path-grade-report-grader .usersuspended a:link,
-.path-grade-report-grader .usersuspended a:visited {
+.path-grade-report-grader .usersuspended a: link,
+.path-grade-report-grader .usersuspended a: visited {
     color: #666;
 }
-
 .path-grade-report-grader table th.usersuspended img.usersuspendedicon {
     vertical-align: text-bottom;
     margin-left: .45em;
 }
-
-.path-grade-report-grader .grade_icons { margin-bottom: .3em;}
-
+.path-grade-report-grader .grade_icons {
+    margin-bottom: .3em;
+}
 .path-grade-report-grader .yui3-overlay {
     background-color: #FFEE69;
     border-color: #D4C237 #A6982B #A6982B;
@@ -590,7 +517,6 @@ background-color:#f3ead8;
     padding: 2px 5px;
     font-size: 0.7em;
 }
-
 .path-grade-report-grader .yui3-overlay .fullname {
     color: #5F3E00;
     font-weight: bold;
@@ -605,39 +531,49 @@ background-color:#f3ead8;
 /* table#user-grades td */
 /* .grader-report-grader table#user-grades td .yui-panel div.hd { */
 .path-grade-report-grader #tooltipPanel {
-  text-align: left;
+    text-align: left;
 }
-
 .path-grade-report-grader .yui3-overlay a.container-close {
-  margin-top: -3px;
+    margin-top: -3px;
 }
-
-.path-grade-report-grader #hiddentooltiproot, .tooltipDiv {
-  display: none;
+.path-grade-report-grader #hiddentooltiproot,
+.tooltipDiv {
+    display: none;
 }
-
 .path-grade-report-grader.ie .right_scroller {
-overflow-y:hidden;
+    overflow-y: hidden;
 }
-
 .path-grade-report-grader.ie table#fixed_column th {
-height:4.5em;
+    height: 4.5em;
 }
-
 .path-grade-report-grader.ie table#fixed_column tr.avg th {
-height:2.1em;
+    height: 2.1em;
 }
-
 .path-grade-report-grader.ie div.left_scroller td {
-height:4.5em;
+    height: 4.5em;
 }
-
 .path-grade-report-grader.ie6 div.right_scroller {
-margin-top:4em;
-width:auto;
-position:absolute;
+    margin-top: 4em;
+    width: auto;
+    position: absolute;
 }
-
 .path-grade-report-grader.ie6 .excludedfloater {
-font-size:7px;
+    font-size: 7px;
+}
+
+/** MDL-40071 **/
+.path-grade-report-grader.dir-rtl table th.user,
+.path-grade-report-grader.dir-rtl table td.userfield {
+    text-align: right;
 }
+
+/** MDL-40180 **/
+.dir-rtl table#user-grades th.category,
+.dir-rtl table#user-grades th#studentheader,
+.dir-rtl table#user-grades th.user {
+    text-align: right;
+}
+.path-grade-report-grader.dir-rtl th.user img.userpicture {
+    margin-left: 0.5em;
+}
+
index 8c67714..c31ade1 100644 (file)
@@ -46,7 +46,30 @@ class autogroup_form extends moodleform {
 
         $mform =& $this->_form;
 
-        $mform->addElement('header', 'autogroup', get_string('autocreategroups', 'group'));
+        $mform->addElement('header', 'autogroup', get_string('general'));
+
+        $mform->addElement('text', 'namingscheme', get_string('namingscheme', 'group'));
+        $mform->addHelpButton('namingscheme', 'namingscheme', 'group');
+        $mform->addRule('namingscheme', get_string('required'), 'required', null, 'client');
+        $mform->setType('namingscheme', PARAM_TEXT);
+        // There must not be duplicate group names in course.
+        $template = get_string('grouptemplate', 'group');
+        $gname = groups_parse_name($template, 0);
+        if (!groups_get_group_by_name($COURSE->id, $gname)) {
+            $mform->setDefault('namingscheme', $template);
+        }
+
+        $options = array('groups' => get_string('numgroups', 'group'),
+                         'members' => get_string('nummembers', 'group'));
+        $mform->addElement('select', 'groupby', get_string('groupby', 'group'), $options);
+
+        $mform->addElement('text', 'number', get_string('number', 'group'),'maxlength="4" size="4"');
+        $mform->setType('number', PARAM_INT);
+        $mform->addRule('number', null, 'numeric', null, 'client');
+        $mform->addRule('number', get_string('required'), 'required', null, 'client');
+
+        $mform->addElement('header', 'groupmembershdr', get_string('groupmembers', 'group'));
+        $mform->setExpanded('groupmembershdr', true);
 
         $options = array(0=>get_string('all'));
         $options += $this->_customdata['roles'];
@@ -76,20 +99,6 @@ class autogroup_form extends moodleform {
             $mform->setType('cohortid', PARAM_INT);
             $mform->setConstant('cohortid', '0');
         }
-
-        $options = array('groups' => get_string('numgroups', 'group'),
-                         'members' => get_string('nummembers', 'group'));
-        $mform->addElement('select', 'groupby', get_string('groupby', 'group'), $options);
-
-        $mform->addElement('text', 'number', get_string('number', 'group'),'maxlength="4" size="4"');
-        $mform->setType('number', PARAM_INT);
-        $mform->addRule('number', null, 'numeric', null, 'client');
-        $mform->addRule('number', get_string('required'), 'required', null, 'client');
-
-        $mform->addElement('checkbox', 'nosmallgroups', get_string('nosmallgroups', 'group'));
-        $mform->disabledIf('nosmallgroups', 'groupby', 'noteq', 'members');
-        $mform->setAdvanced('nosmallgroups');
-
         $options = array('no'        => get_string('noallocation', 'group'),
                          'random'    => get_string('random', 'group'),
                          'firstname' => get_string('byfirstname', 'group'),
@@ -97,20 +106,13 @@ class autogroup_form extends moodleform {
                          'idnumber'  => get_string('byidnumber', 'group'));
         $mform->addElement('select', 'allocateby', get_string('allocateby', 'group'), $options);
         $mform->setDefault('allocateby', 'random');
-        $mform->setAdvanced('allocateby');
 
-        $mform->addElement('text', 'namingscheme', get_string('namingscheme', 'group'));
-        $mform->addHelpButton('namingscheme', 'namingscheme', 'group');
-        $mform->addRule('namingscheme', get_string('required'), 'required', null, 'client');
-        $mform->setType('namingscheme', PARAM_TEXT);
-        // there must not be duplicate group names in course
-        $template = get_string('grouptemplate', 'group');
-        $gname = groups_parse_name($template, 0);
-        if (!groups_get_group_by_name($COURSE->id, $gname)) {
-            $mform->setDefault('namingscheme', $template);
-        }
+        $mform->addElement('checkbox', 'nosmallgroups', get_string('nosmallgroups', 'group'));
+        $mform->disabledIf('nosmallgroups', 'groupby', 'noteq', 'members');
+
+        $mform->addElement('header', 'groupinghdr', get_string('grouping', 'group'));
 
-        $options = array('0' => get_string('no'),
+        $options = array('0' => get_string('nogrouping', 'group'),
                          '-1'=> get_string('newgrouping', 'group'));
         if ($groupings = groups_get_all_groupings($COURSE->id)) {
             foreach ($groupings as $grouping) {
index 4e0d8b0..b6aa753 100644 (file)
@@ -79,8 +79,10 @@ $returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id;
 // Prepare the description editor: We do support files for group descriptions
 $editoroptions = array('maxfiles'=>EDITOR_UNLIMITED_FILES, 'maxbytes'=>$course->maxbytes, 'trust'=>false, 'context'=>$context, 'noclean'=>true);
 if (!empty($group->id)) {
+    $editoroptions['subdirs'] = file_area_contains_subdirs($context, 'group', 'description', $group->id);
     $group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', $group->id);
 } else {
+    $editoroptions['subdirs'] = false;
     $group = file_prepare_standard_editor($group, 'description', $editoroptions, $context, 'group', 'description', null);
 }
 
index 6818ca2..72e6849 100644 (file)
@@ -55,7 +55,6 @@ class group_form extends moodleform {
         $mform->addElement('text','idnumber', get_string('idnumbergroup'), 'maxlength="100" size="10"');
         $mform->addHelpButton('idnumber', 'idnumbergroup');
         $mform->setType('idnumber', PARAM_RAW);
-        $mform->setAdvanced('idnumber');
         if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
             $mform->hardFreeze('idnumber');
         }
index d44444b..eb597bb 100644 (file)
@@ -57,7 +57,6 @@ class grouping_form extends moodleform {
         $mform->addElement('text','idnumber', get_string('idnumbergrouping'), 'maxlength="100" size="10"');
         $mform->addHelpButton('idnumber', 'idnumbergrouping');
         $mform->setType('idnumber', PARAM_RAW);
-        $mform->setAdvanced('idnumber');
         if (!has_capability('moodle/course:changeidnumber', $coursecontext)) {
             $mform->hardFreeze('idnumber');
         }
index abd8c72..e877b5f 100644 (file)
@@ -46,7 +46,7 @@ class groups_import_form extends moodleform {
 
         //fill in the data depending on page params
         //later using set_data
-        $mform->addElement('header', 'general');
+        $mform->addElement('header', 'general', get_string('general'));
 
         $filepickeroptions = array();
         $filepickeroptions['filetypes'] = '*';
index 4c9b0ca..1653b50 100644 (file)
@@ -43,7 +43,7 @@ Feature: Automatic creation of groups
     And I expand all fieldsets
     And I fill the moodle form with:
       | Group/member count | 2 |
-      | Create in grouping | New grouping |
+      | Grouping of auto-created groups | New grouping |
       | Grouping name | Grouping name |
     And I press "Preview"
     Then I should see "Group members"
index d230f67..ef68ec9 100644 (file)
@@ -570,8 +570,9 @@ $string['gradeexport'] = 'Primary grade export methods';
 $string['guestroleid'] = 'Role for guest';
 $string['guestroleid_help'] = 'This role is automatically assigned to the guest user. It is also temporarily assigned to not enrolled users that enter the course via guest enrolment plugin.';
 $string['helpadminseesall'] = 'Do admins see all calendar events or just those that apply to themselves?';
-$string['helpcalendarsettings'] = 'Configure various calendar and date/time-related aspects of Moodle';
 $string['helpcalendarcustomexport'] = 'Enable custom date range export option in calendar exports. Calendar exports must be enabled before this is effective.';
+$string['helpcalendarsettings'] = 'Configure various calendar and date/time-related aspects of Moodle';
+$string['helpcalendartype'] = 'This is the calendar type that will be used throughout your site.';
 $string['helpexportlookahead'] = 'How many days in the future does the calendar look for events during export for the custom export option?';
 $string['helpexportlookback'] = 'How many days in the past does the calendar look for events during export for the custom export option?';
 $string['helpforcetimezone'] = 'You can allow users to individually select their timezone, or force a timezone for everyone.';
index bf1c97c..5937859 100644 (file)
@@ -86,9 +86,9 @@ $string['entrybodyonlydesc'] = 'Entry description';
 $string['entryerrornotyours'] = 'This entry is not yours';
 $string['entrysaved'] = 'Your entry has been saved';
 $string['entrytitle'] = 'Entry title';
-$string['entryupdated'] = 'Blog entry updated';
 $string['evententryadded'] = 'Blog entry added';
 $string['evententrydeleted'] = 'Blog entry deleted';
+$string['evententryupdated'] = 'Blog entry updated';
 $string['externalblogcrontime'] = 'External blog cron schedule';
 $string['externalblogdeleteconfirm'] = 'Unregister this external blog?';
 $string['externalblogdeleted'] = 'External blog unregistered';
index 47403f2..2ec0c97 100644 (file)
@@ -42,6 +42,7 @@ $string['cachedef_coursecat'] = 'Course categories lists for particular user';
 $string['cachedef_coursecatrecords'] = 'Course categories records';
 $string['cachedef_coursecontacts'] = 'List of course contacts';
 $string['cachedef_coursecattree'] = 'Course categories tree';
+$string['cachedef_coursemodinfo'] = 'Accumulated information about modules and sections for each course';
 $string['cachedef_databasemeta'] = 'Database meta information';
 $string['cachedef_eventinvalidation'] = 'Event invalidation';
 $string['cachedef_externalbadges'] = 'External badges for particular user';
index 75d51b9..7a6a6af 100644 (file)
@@ -93,6 +93,7 @@ $string['export'] = 'Export';
 $string['exportbutton'] = 'Export';
 $string['exportcalendar'] = 'Export calendar';
 $string['for'] = 'for';
+$string['forcecalendartype'] = 'Force calendar';
 $string['fri'] = 'Fri';
 $string['friday'] = 'Friday';
 $string['generateurlbutton'] = 'Get calendar URL';
@@ -138,6 +139,7 @@ $string['pollinterval'] = 'Update interval';
 $string['pollinterval_help'] = 'How often you would like the calendar to update with new events.';
 $string['preferences'] = 'Preferences';
 $string['preferences_available'] = 'Your personal preferences';
+$string['preferredcalendar'] = 'Preferred calendar';
 $string['pref_lookahead'] = 'Upcoming events look-ahead';
 $string['pref_lookahead_help'] = 'This sets the (maximum) number of days in the future that an event has to start in in order to be displayed as an upcoming event. Events that start beyond this will never be displayed as upcoming. Please note that <strong>there is no guarantee</strong> that all events starting in this time frame will be displayed; if there are too many (more than the "Maximum upcoming events" preference) then the most distant events will not be shown.';
 $string['pref_maxevents'] = 'Maximum upcoming events';
index db4c617..beaab45 100644 (file)
@@ -42,7 +42,7 @@ $string['createautomaticgrouping'] = 'Create automatic grouping';
 $string['creategroup'] = 'Create group';
 $string['creategrouping'] = 'Create grouping';
 $string['creategroupinselectedgrouping'] = 'Create group in grouping';
-$string['createingrouping'] = 'Create in grouping';
+$string['createingrouping'] = 'Grouping of auto-created groups';
 $string['createorphangroup'] = 'Create orphan group';
 $string['databaseupgradegroups'] = 'Groups version is now {$a}';
 $string['defaultgrouping'] = 'Default grouping';
@@ -80,7 +80,7 @@ $string['filtergroups'] = 'Filter groups by:';
 $string['group'] = 'Group';
 $string['groupaddedsuccesfully'] = 'Group {$a} added successfully';
 $string['groupaddedtogroupingsuccesfully'] = 'Group {$a->groupname} added to grouping {$a->groupingname} successfully';
-$string['groupby'] = 'Specify';
+$string['groupby'] = 'Auto create based on';
 $string['groupdescription'] = 'Group description';
 $string['groupinfo'] = 'Info about selected group';
 $string['groupinfomembers'] = 'Info about selected members';
@@ -149,6 +149,7 @@ $string['newgrouping'] = 'New grouping';
 $string['newpicture'] = 'New picture';
 $string['newpicture_help'] = 'Select an image in JPG or PNG format. The image will be cropped to a square and resized to 100x100 pixels.';
 $string['noallocation'] = 'No allocation';
+$string['nogrouping'] = 'No grouping';
 $string['nogroups'] = 'There are no groups set up in this course yet';
 $string['nogroupsassigned'] = 'No groups assigned';
 $string['nopermissionforcreation'] = 'Can\'t create group "{$a}" as you don\'t have the required permissions';
@@ -169,7 +170,7 @@ $string['removefromgroupconfirm'] = 'Do you really want to remove user "{$a->use
 $string['removegroupingsmembers'] = 'Remove all groups from groupings';
 $string['removegroupsmembers'] = 'Remove all group members';
 $string['removeselectedusers'] = 'Remove selected users';
-$string['selectfromrole'] = 'Select members from role';
+$string['selectfromrole'] = 'Select members with role';
 $string['showgroupsingrouping'] = 'Show groups in grouping';
 $string['showmembersforgroup'] = 'Show members for group';
 $string['toomanygroups'] = 'Insufficient users to populate this number of groups - there are only {$a} users in the selected role.';
index e65e1ea..f1e7958 100644 (file)
@@ -91,6 +91,8 @@ $string['type_cachelock'] = 'Cache lock handler';
 $string['type_cachelock_plural'] = 'Cache lock handlers';
 $string['type_cachestore'] = 'Cache store';
 $string['type_cachestore_plural'] = 'Cache stores';
+$string['type_calendartype'] = 'Calendar type';
+$string['type_calendartype_plural'] = 'Calendar types';
 $string['type_coursereport'] = 'Course report';
 $string['type_coursereport_plural'] = 'Course reports';
 $string['type_editor'] = 'Editor';
index 9359ab8..efcf9a1 100644 (file)
@@ -194,13 +194,13 @@ function uninstall_plugin($type, $name) {
             }
         }
 
-        // clear course.modinfo for courses that used this module
-        $sql = "UPDATE {course}
-                   SET modinfo=''
-                 WHERE id IN (SELECT DISTINCT course
+        // Increment course.cacherev for courses that used this module.
+        // This will force cache rebuilding on the next request.
+        increment_revision_number('course', 'cacherev',
+                "id IN (SELECT DISTINCT course
                                 FROM {course_modules}
-                               WHERE module=?)";
-        $DB->execute($sql, array($module->id));
+                               WHERE module=?)",
+                array($module->id));
 
         // delete all the course module records
         $DB->delete_records('course_modules', array('module' => $module->id));
index c24c5be..f8fac6e 100644 (file)
@@ -707,7 +707,7 @@ function login_lock_account($user) {
         }
 
         $site = get_site();
-        $supportuser = generate_email_supportuser();
+        $supportuser = core_user::get_support_user();
 
         $data = new stdClass();
         $data->firstname = $user->firstname;
index 061ecd3..1a300b9 100644 (file)
@@ -895,38 +895,29 @@ function badges_add_course_navigation(navigation_node $coursenode, stdClass $cou
 
     $coursecontext = context_course::instance($course->id);
     $isfrontpage = (!$coursecontext || $course->id == $SITE->id);
+    $canmanage = has_any_capability(array('moodle/badges:viewawarded',
+                                          'moodle/badges:createbadge',
+                                          'moodle/badges:awardbadge',
+                                          'moodle/badges:configurecriteria',
+                                          'moodle/badges:configuremessages',
+                                          'moodle/badges:configuredetails',
+                                          'moodle/badges:deletebadge'), $coursecontext);
 
-    if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage) {
-        if (has_capability('moodle/badges:configuredetails', $coursecontext)) {
-            $coursenode->add(get_string('coursebadges', 'badges'), null,
-                    navigation_node::TYPE_CONTAINER, null, 'coursebadges',
-                    new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+    if (!empty($CFG->enablebadges) && !empty($CFG->badges_allowcoursebadges) && !$isfrontpage && $canmanage) {
+        $coursenode->add(get_string('coursebadges', 'badges'), null,
+                navigation_node::TYPE_CONTAINER, null, 'coursebadges',
+                new pix_icon('i/badge', get_string('coursebadges', 'badges')));
 
-            if (has_capability('moodle/badges:viewawarded', $coursecontext)) {
-                $url = new moodle_url('/badges/index.php',
-                        array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+        $url = new moodle_url('/badges/index.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
 
-                $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
-                    navigation_node::TYPE_SETTING, null, 'coursebadges');
-            }
-
-            if (has_capability('moodle/badges:createbadge', $coursecontext)) {
-                $url = new moodle_url('/badges/newbadge.php',
-                        array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
-
-                $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
-                        navigation_node::TYPE_SETTING, null, 'newbadge');
-            }
-        } else if (has_capability('moodle/badges:awardbadge', $coursecontext)) {
-            $coursenode->add(get_string('coursebadges', 'badges'), null,
-                    navigation_node::TYPE_CONTAINER, null, 'coursebadges',
-                    new pix_icon('i/badge', get_string('coursebadges', 'badges')));
+        $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
+            navigation_node::TYPE_SETTING, null, 'coursebadges');
 
-            $url = new moodle_url('/badges/index.php',
-                    array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
+        if (has_capability('moodle/badges:createbadge', $coursecontext)) {
+            $url = new moodle_url('/badges/newbadge.php', array('type' => BADGE_TYPE_COURSE, 'id' => $course->id));
 
-            $coursenode->get('coursebadges')->add(get_string('managebadges', 'badges'), $url,
-                    navigation_node::TYPE_SETTING, null, 'coursebadges');
+            $coursenode->get('coursebadges')->add(get_string('newbadge', 'badges'), $url,
+                    navigation_node::TYPE_SETTING, null, 'newbadge');
         }
     }
 }
@@ -1145,13 +1136,20 @@ function profile_display_badges($userid, $courseid = 0) {
     global $CFG, $PAGE, $USER, $SITE;
     require_once($CFG->dirroot . '/badges/renderer.php');
 
-    if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', context_user::instance($USER->id))) {
+    // Determine context.
+    if (isloggedin()) {
+        $context = context_user::instance($USER->id);
+    } else {
+        $context = context_system::instance();
+    }
+
+    if ($USER->id == $userid || has_capability('moodle/badges:viewotherbadges', $context)) {
         $records = badges_get_user_badges($userid, $courseid, null, null, null, true);
         $renderer = new core_badges_renderer($PAGE, '');
 
         // Print local badges.
         if ($records) {
-            $left = get_string('localbadgesp', 'badges', $SITE->fullname);
+            $left = get_string('localbadgesp', 'badges', format_string($SITE->fullname));
             $right = $renderer->print_badges_list($records, $userid, true);
             echo html_writer::tag('dt', $left);
             echo html_writer::tag('dd', $right);
index 64727eb..68592ae 100644 (file)
@@ -42,6 +42,11 @@ require_once(__DIR__ . '/../../filelib.php');
  */
 class behat_util extends testing_util {
 
+    /**
+     * The behat test site fullname and shortname.
+     */
+    const BEHATSITENAME = "Acceptance test site";
+
     /**
      * @var array Files to skip when resetting dataroot folder
      */
@@ -70,8 +75,8 @@ class behat_util extends testing_util {
         $options = array();
         $options['adminuser'] = 'admin';
         $options['adminpass'] = 'admin';
-        $options['fullname'] = 'Acceptance test site';
-        $options['shortname'] = 'Acceptance test site';
+        $options['fullname'] = self::BEHATSITENAME;
+        $options['shortname'] = self::BEHATSITENAME;
 
         install_cli_database($options, false);
 
index d5ec8ac..51db6e9 100644 (file)
@@ -43,6 +43,8 @@ class core_component {
     protected static $classmap = null;
     /** @var null list of some known files that can be included. */
     protected static $filemap = null;
+    /** @var int|float core version. */
+    protected static $version = null;
     /** @var array list of the files to map. */
     protected static $filestomap = array('lib.php', 'settings.php');
 
@@ -133,8 +135,11 @@ class core_component {
                 include($cachefile);
                 if (!is_array($cache)) {
                     // Something is very wrong.
-                } else if (!isset($cache['plugintypes']) or !isset($cache['plugins']) or !isset($cache['subsystems']) or !isset($cache['classmap'])) {
+                } else if (!isset($cache['version'])) {
                     // Something is very wrong.
+                } else if ((float) $cache['version'] !== (float) self::fetch_core_version()) {
+                    // Outdated cache. We trigger an error log to track an eventual repetitive failure of float comparison.
+                    error_log('Resetting core_component cache after core upgrade to version ' . self::fetch_core_version());
                 } else if ($cache['plugintypes']['mod'] !== "$CFG->dirroot/mod") {
                     // $CFG->dirroot was changed.
                 } else {
@@ -227,6 +232,7 @@ class core_component {
             'plugins'     => self::$plugins,
             'classmap'    => self::$classmap,
             'filemap'     => self::$filemap,
+            'version'     => self::$version,
         );
 
         return '<?php
@@ -249,6 +255,23 @@ $cache = '.var_export($cache, true).';
 
         self::fill_classmap_cache();
         self::fill_filemap_cache();
+        self::fetch_core_version();
+    }
+
+    /**
+     * Get the core version.
+     *
+     * In order for this to work properly, opcache should be reset beforehand.
+     *
+     * @return float core version.
+     */
+    protected static function fetch_core_version() {
+        global $CFG;
+        if (self::$version === null) {
+            require($CFG->dirroot . '/version.php');
+            self::$version = $version;
+        }
+        return self::$version;
     }
 
     /**
@@ -341,6 +364,7 @@ $cache = '.var_export($cache, true).';
             'qtype'         => $CFG->dirroot.'/question/type',
             'mod'           => $CFG->dirroot.'/mod',
             'auth'          => $CFG->dirroot.'/auth',
+            'calendartype'  => $CFG->dirroot.'/calendar/type',
             'enrol'         => $CFG->dirroot.'/enrol',
             'message'       => $CFG->dirroot.'/message/output',
             'block'         => $CFG->dirroot.'/blocks',
@@ -870,9 +894,7 @@ $cache = '.var_export($cache, true).';
         $versions = array();
 
         // Main version first.
-        $version = null;
-        include($CFG->dirroot.'/version.php');
-        $versions['core'] = $version;
+        $versions['core'] = self::fetch_core_version();
 
         // The problem here is tha the component cache might be stable,
         // we want this to work also on frontpage without resetting the component cache.
@@ -894,12 +916,12 @@ $cache = '.var_export($cache, true).';
                     $module = new stdClass();
                     $module->version = null;
                     include($fullplug.'/version.php');
-                    $versions[$plug] = $module->version;
+                    $versions[$type.'_'.$plug] = $module->version;
                 } else {
                     $plugin = new stdClass();
                     $plugin->version = null;
                     @include($fullplug.'/version.php');
-                    $versions[$plug] = $plugin->version;
+                    $versions[$type.'_'.$plug] = $plugin->version;
                 }
             }
         }
index 0660cf2..531eb59 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Base event class.
  *
@@ -24,7 +26,6 @@ namespace core\event;
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-
 /**
  * All other event classes must extend this class.
  *
index 8f68647..7ba305e 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event for when a new blog entry is added..
  *
@@ -107,6 +109,7 @@ class blog_entry_created extends \core\event\base {
      * @return array of parameters to be passed to legacy add_to_log() function.
      */
     protected function get_legacy_logdata() {
-        return array (SITEID, 'blog', 'add', 'index.php?userid='.$this->userid.'&entryid='.$this->objectid, $this->customobject->subject);
+        return array (SITEID, 'blog', 'add', 'index.php?userid=' . $this->relateduserid . '&entryid=' . $this->objectid,
+                $this->customobject->subject);
     }
 }
index 9d02aab..b632082 100644 (file)
@@ -22,6 +22,8 @@
  */
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * class blog_entry_deleted
  *
@@ -98,6 +100,7 @@ class blog_entry_deleted extends \core\event\base {
      * @return array of parameters to be passed to legacy add_to_log() function.
      */
     protected function get_legacy_logdata() {
-        return array (SITEID, 'blog', 'delete', 'index.php?userid='.$this->userid, 'deleted blog entry with entry id# '. $this->objectid);
+        return array (SITEID, 'blog', 'delete', 'index.php?userid=' . $this->relateduserid, 'deleted blog entry with entry id# '.
+                $this->objectid);
     }
 }
diff --git a/lib/classes/event/blog_entry_updated.php b/lib/classes/event/blog_entry_updated.php
new file mode 100644 (file)
index 0000000..5859f4b
--- /dev/null
@@ -0,0 +1,114 @@
+<?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/>.
+
+/**
+ * Event to be triggered when a blog entry is updated.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class blog_entry_updated
+ *
+ * Event to be triggered when a blog entry is updated.
+ *
+ * @package    core
+ * @copyright  2013 Ankit Agarwal
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class blog_entry_updated extends base {
+
+    /** @var \blog_entry A reference to the active blog_entry object. */
+    protected $customobject;
+
+    /**
+     * Set basic event properties.
+     */
+    protected function init() {
+        $this->context = \context_system::instance();
+        $this->data['objecttable'] = 'post';
+        $this->data['crud'] = 'u';
+        $this->data['level'] = self::LEVEL_PARTICIPATING;
+    }
+
+    /**
+     * Set custom data of the event.
+     *
+     * @param \blog_entry $data A reference to the active blog_entry object.
+     */
+    public function set_custom_data($data) {
+        $this->customobject = $data;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('evententryupdated', 'core_blog');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'User with id {$this->userid} updated blog entry {$this->other["subject"]';
+    }
+
+    /**
+     * Returns relevant URL.
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/blog/index.php', array('entryid' => $this->objectid, 'userid' => $this->userid));
+    }
+
+    /**
+     * Legacy event data if get_legacy_eventname() is not empty.
+     *
+     * @return \blog_entry
+     */
+    protected function get_legacy_eventdata() {
+        return $this->customobject;
+    }
+
+    /**
+     * Legacy event name.
+     *
+     * @return string legacy event name
+     */
+    public static function get_legacy_eventname() {
+        return 'blog_entry_edited';
+    }
+
+    /**
+     * Replace legacy add_to_log() statement.
+     *
+     * @return array of parameters to be passed to legacy add_to_log() function.
+     */
+    protected function get_legacy_logdata() {
+        return array(SITEID, 'blog', 'update', 'index.php?userid=' . $this->relateduserid . '&entryid=' . $this->objectid,
+                 $this->other['subject']);
+    }
+}
+
index 531b3ae..4c03cf0 100644 (file)
@@ -23,6 +23,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Class content_viewed.
  *
index 8f0b4d3..ff7ecf3 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * category deleted event.
  *
index 78bc557..b77219a 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event when course completed.
  *
index 9cb3309..92fc9a6 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Course content_deleted event.
  *
index 040f830..deeacb4 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Course created event.
  *
index 2cf551f..db17175 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Course deleted event.
  *
index af292a4..72c0276 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event when course module completion is updated.
  *
index f684463..2f8fa4f 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Course restored event.
  *
index b76dd65..c037d20 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Course updated event.
  *
index 400723d..738bb7d 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * New event manager class.
  *
@@ -282,6 +284,7 @@ class manager {
                 if ($CFG->admin !== 'admin' and strpos($observer['includefile'], '/admin/') === 0) {
                     $observer['includefile'] = preg_replace('|^/admin/|', '/'.$CFG->admin.'/', $observer['includefile']);
                 }
+                $observer['includefile'] = $CFG->dirroot . '/' . ltrim($observer['includefile'], '/');
                 if (!file_exists($observer['includefile'])) {
                     debugging("Invalid 'includefile' detected in $file observer definition", DEBUG_DEVELOPER);
                     continue;
index ad4ac9a..abf1f10 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event when role allow assignments is updated.
  *
index fc7c5ce..0d16517 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event when role allow override is updated.
  *
index 20e45eb..602f2af 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Event when role allow switch is updated.
  *
index 1f763ab..fc9e9a8 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Role assigned event.
  *
@@ -54,7 +56,7 @@ class role_assigned extends base {
      * @return \moodle_url
      */
     public function get_url() {
-        return new moodle_url('/admin/roles/assign.php', array('contextid'=>$this->contextid, 'roleid'=>$this->objectid));
+        return new moodle_url('/admin/roles/assign.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
     }
 
     /**
index 5d1f89c..d0f188b 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Role updated event.
  *
@@ -64,7 +66,8 @@ class role_capabilities_updated extends base {
         if ($this->contextlevel === CONTEXT_SYSTEM) {
             return new \moodle_url('admin/roles/define.php', array('action' => 'view', 'roleid' => $this->objectid));
         } else {
-            return new \moodle_url('/admin/roles/override.php', array('contextid' => $this->contextid, 'roleid' => $this->objectid));
+            return new \moodle_url('/admin/roles/override.php', array('contextid' => $this->contextid,
+                'roleid' => $this->objectid));
         }
     }
 
index 969ebbb..d11dab8 100644 (file)
@@ -16,6 +16,8 @@
 
 namespace core\event;
 
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Role assigned event.
  *
@@ -67,6 +69,7 @@ class role_deleted extends base {
      * @return array
      */
     protected function get_legacy_logdata() {
-        return array(SITEID, 'role', 'delete', 'admin/roles/manage.php?action=delete&roleid='.$this->objectid, $this->other['shortname'], '');
+        return array(SITEID, 'role', 'delete', 'admin/roles/manage.php?action=delete&roleid=' . $this->objectid,
+            $this->other['shortname']