Merge branch 'MDL-54026-master' of git://github.com/andrewnicols/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 16 May 2016 03:52:02 +0000 (11:52 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 16 May 2016 03:52:02 +0000 (11:52 +0800)
266 files changed:
admin/tests/behat/enable_multiple_accounts_use_same_email.feature [new file with mode: 0644]
admin/tool/lp/amd/build/dragdrop-reorder.min.js
admin/tool/lp/amd/build/tree.min.js
admin/tool/lp/amd/build/user_competency_course_navigation.min.js
admin/tool/lp/amd/src/dragdrop-reorder.js
admin/tool/lp/amd/src/tree.js
admin/tool/lp/amd/src/user_competency_course_navigation.js
admin/tool/lp/classes/external/course_summary_exporter.php
admin/tool/lp/classes/external/user_competency_summary_in_course_exporter.php
admin/tool/lp/classes/form/competency.php
admin/tool/lp/classes/form/competency_framework.php
admin/tool/lp/classes/output/competency_plan_navigation.php
admin/tool/lp/classes/output/user_competency_course_navigation.php
admin/tool/lp/db/services.php
admin/tool/lp/styles.css
admin/tool/lp/templates/competencies_move_tree.mustache
admin/tool/lp/templates/competencies_tree.mustache
admin/tool/lp/templates/competencies_tree_root.mustache
admin/tool/lp/templates/competency_path.mustache
admin/tool/lp/templates/competency_picker.mustache
admin/tool/lp/templates/competency_picker_competencyform.mustache
admin/tool/lp/templates/competency_picker_user_plans.mustache
admin/tool/lp/templates/competency_plan_navigation.mustache
admin/tool/lp/templates/competency_rule_points.mustache
admin/tool/lp/templates/competency_summary.mustache
admin/tool/lp/templates/course_competencies_page.mustache
admin/tool/lp/templates/course_competency_statistics.mustache
admin/tool/lp/templates/manage_competencies_page.mustache
admin/tool/lp/templates/manage_competency_frameworks_page.mustache
admin/tool/lp/templates/manage_templates_page.mustache
admin/tool/lp/templates/plan_page.mustache
admin/tool/lp/templates/plans_page.mustache
admin/tool/lp/templates/related_competencies.mustache
admin/tool/lp/templates/template_competencies_page.mustache
admin/tool/lp/templates/template_statistics.mustache
admin/tool/lp/templates/user_competency_course_navigation.mustache
admin/tool/lp/templates/user_competency_summary_in_plan.mustache
admin/tool/lp/templates/user_evidence_list_page.mustache
admin/tool/lp/templates/user_evidence_page.mustache
admin/tool/lp/version.php
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-debug.js
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js
admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder.js
admin/tool/lp/yui/src/dragdrop-reorder/js/dragdropreorder.js
admin/tool/lpmigrate/templates/migrate_frameworks_results.mustache
admin/tool/recyclebin/classes/category_bin.php
admin/tool/recyclebin/classes/course_bin.php
admin/tool/recyclebin/tests/course_bin_test.php
admin/tool/uploadcourse/classes/course.php
admin/tool/uploadcourse/tests/course_test.php
admin/tool/uploadcourse/tests/helper_test.php
admin/tool/uploadcourse/tests/processor_test.php
auth/ldap/auth.php
auth/shibboleth/logout.php
backup/controller/backup_controller.class.php
backup/controller/restore_controller.class.php
backup/moodle2/backup_root_task.class.php
backup/moodle2/backup_settingslib.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_root_task.class.php
backup/moodle2/restore_settingslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_course_format_test.php
backup/moodle2/tests/moodle2_test.php
backup/upgrade.txt
backup/util/checks/tests/checks_test.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_helper.class.php
backup/util/loggers/base_logger.class.php
backup/util/loggers/file_logger.class.php
backup/util/loggers/tests/logger_test.php
backup/util/plan/tests/plan_test.php
backup/util/plan/tests/step_test.php
backup/util/plan/tests/task_test.php
badges/lib.php
blocks/badges/tests/behat/block_badges.feature [new file with mode: 0644]
blocks/badges/tests/behat/block_badges_course.feature [new file with mode: 0644]
blocks/badges/tests/behat/block_badges_dashboard.feature [new file with mode: 0644]
blocks/badges/tests/behat/block_badges_frontpage.feature [new file with mode: 0644]
blocks/badges/tests/fixtures/badge.png [new file with mode: 0644]
blocks/calendar_month/tests/behat/block_calendar_month.feature [new file with mode: 0644]
blocks/calendar_month/tests/behat/block_calendar_month_course.feature [new file with mode: 0644]
blocks/calendar_month/tests/behat/block_calendar_month_dashboard.feature [new file with mode: 0644]
blocks/calendar_month/tests/behat/block_calendar_month_frontpage.feature [new file with mode: 0644]
blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_course.feature [new file with mode: 0644]
blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_dashboard.feature [new file with mode: 0644]
blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_frontpage.feature [new file with mode: 0644]
blocks/comments/tests/behat/block_comment_activity.feature [new file with mode: 0644]
blocks/comments/tests/behat/block_comment_course.feature [new file with mode: 0644]
blocks/comments/tests/behat/block_comment_frontpage.feature [new file with mode: 0644]
blocks/completionstatus/tests/behat/block_completionstatus.feature [new file with mode: 0644]
blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature [new file with mode: 0644]
blocks/completionstatus/tests/behat/block_completionstatus_manual_other.feature [new file with mode: 0644]
blocks/completionstatus/tests/behat/block_completionstatus_manual_self.feature [new file with mode: 0644]
blocks/globalsearch/styles.css [new file with mode: 0644]
blocks/lp/templates/competencies_to_review_page.mustache
blocks/lp/templates/plans_to_review_page.mustache
blocks/lp/templates/summary.mustache
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/styles.css
competency/classes/competency.php
competency/classes/competency_framework.php
competency/classes/external/user_summary_exporter.php
competency/classes/plan_competency.php
competency/classes/template_competency.php
competency/classes/user_competency.php
competency/classes/user_competency_course.php
competency/classes/user_competency_plan.php
competency/tests/api_test.php
competency/tests/external_test.php
composer.json
composer.lock
config-dist.php
course/classes/search/mycourse.php [new file with mode: 0644]
course/externallib.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/tests/restore_test.php [new file with mode: 0644]
course/tests/search_test.php [new file with mode: 0644]
enrol/editinstance.php
enrol/externallib.php
enrol/guest/classes/external.php
enrol/guest/db/services.php
enrol/guest/version.php
enrol/lti/classes/helper.php
enrol/lti/lang/en/enrol_lti.php
enrol/lti/lib.php
enrol/lti/settings.php
enrol/lti/tool.php
enrol/self/db/services.php
enrol/self/externallib.php
enrol/self/version.php
enrol/tests/enrollib_test.php
grade/grading/form/guide/db/upgrade.php [new file with mode: 0644]
grade/grading/form/guide/lib.php
grade/grading/form/guide/renderer.php
grade/grading/form/guide/version.php
grade/grading/form/rubric/tests/behat/behat_gradingform_rubric.php
grade/report/user/db/services.php
grade/report/user/version.php
install/lang/br/langconfig.php [new file with mode: 0644]
install/lang/es_mx/install.php
install/lang/fr/admin.php
install/lang/oc_lnc/error.php
lang/en/backup.php
lang/en/moodle.php
lang/en/search.php
lib/adminlib.php
lib/ajax/service.php
lib/amd/build/form-autocomplete.min.js
lib/amd/build/form-course-selector.min.js
lib/amd/build/str.min.js
lib/amd/build/tree.min.js
lib/amd/src/form-autocomplete.js
lib/amd/src/form-course-selector.js
lib/amd/src/str.js
lib/amd/src/tree.js
lib/classes/date.php
lib/classes/session/manager.php
lib/classes/session/memcached.php
lib/classes/session/redis.php [deleted file]
lib/classes/update/code_manager.php
lib/classes/user.php
lib/datalib.php
lib/db/install.php
lib/db/services.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/enrollib.php
lib/externallib.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/form/course.php
lib/form/url.js
lib/formslib.php
lib/grade/grade_scale.php
lib/ldaplib.php
lib/moodlelib.php
lib/tests/date_legacy_test.php
lib/tests/date_test.php
lib/tests/externallib_test.php
lib/tests/moodlelib_test.php
lib/tests/questionlib_test.php
lib/tests/redis_session_test.php [deleted file]
lib/tests/unoconv_test.php
lib/tests/user_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js
lib/yui/src/dragdrop/js/dragdrop.js
message/output/airnotifier/db/services.php
message/output/airnotifier/version.php
mod/assign/amd/build/grading_navigation_user_info.min.js
mod/assign/amd/src/grading_navigation_user_info.js
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/editpdf/classes/renderer.php
mod/assign/feedback/editpdf/pix/drag.png [new file with mode: 0644]
mod/assign/feedback/editpdf/pix/drag.svg [new file with mode: 0644]
mod/assign/feedback/editpdf/styles.css
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/edit.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/globals.js
mod/assign/feedback/file/tests/behat/feedback_file.feature
mod/assign/gradeform.php
mod/assign/gradingtable.php
mod/assign/locallib.php
mod/assign/renderer.php
mod/assign/styles.css
mod/assign/tests/behat/online_submissions.feature
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/version.php
mod/data/field/checkbox/field.class.php
mod/data/field/menu/field.class.php
mod/data/field/multimenu/field.class.php
mod/data/field/radiobutton/field.class.php
mod/data/lib.php
mod/feedback/classes/course_map_form.php
mod/feedback/item/info/info_form.php
mod/feedback/item/info/lib.php
mod/forum/index.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/subscribe.php
mod/forum/subscribe_ajax.php
mod/lti/classes/external.php
mod/lti/styles.css
mod/lti/tests/service_exception_handler_test.php
mod/quiz/classes/external.php
mod/quiz/tests/external_test.php
mod/scorm/player.js
mod/scorm/view.js
mod/wiki/classes/search/collaborative_page.php
question/classes/external.php
question/type/multichoice/tests/behat/export.feature
report/competency/classes/output/user_course_navigation.php
report/competency/templates/report.mustache
report/competency/templates/user_course_navigation.mustache
repository/filepicker.php
repository/filesystem/lib.php
repository/lib.php
repository/repository_ajax.php
search/classes/area/base.php
search/tests/manager_test.php
tag/classes/external.php
tag/tests/events_test.php
theme/base/style/user.css
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/less/moodle/yui_fixes.less [deleted file]
theme/bootstrapbase/style/moodle.css
user/edit_form.php
user/profile/lib.php
user/tests/behat/name_fields.feature
user/tests/profilelib_test.php
user/tests/userlib_test.php
version.php

diff --git a/admin/tests/behat/enable_multiple_accounts_use_same_email.feature b/admin/tests/behat/enable_multiple_accounts_use_same_email.feature
new file mode 100644 (file)
index 0000000..30d21b9
--- /dev/null
@@ -0,0 +1,57 @@
+@core @core_admin
+Feature: Enable multiple accounts to have the same email address
+  In order to have multiple accounts registerd on the system with the same email address
+  As an admin
+  I need to enable multiple accounts to be registered with the same email address and verify it is applied
+
+  Background:
+    Given I log in as "admin"
+
+  Scenario: Enable registration of multiple accounts with the same email address
+    Given the following config values are set as admin:
+      | allowaccountssameemail | 1 |
+    When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
+    And I set the following fields to these values:
+      | Username                        | testmultiemailuser1             |
+      | Choose an authentication method | Manual accounts                 |
+      | New password                    | test@User1                      |
+      | First name                      | Test                            |
+      | Surname                         | Multi1                          |
+      | Email address                   | testmultiemailuser@example.com  |
+    And I press "Create user"
+    And I should see "Test Multi1"
+    And I press "Add a new user"
+    And I set the following fields to these values:
+      | Username                        | testmultiemailuser2             |
+      | Choose an authentication method | Manual accounts                 |
+      | New password                    | test@User2                      |
+      | First name                      | Test                            |
+      | Surname                         | Multi2                          |
+      | Email address                   | testmultiemailuser@example.com  |
+    And I press "Create user"
+    Then I should see "Test Multi2"
+    And I should not see "This email address is already registered"
+
+  Scenario: Disable registration of multiple accounts with the same email address
+    Given the following config values are set as admin:
+      | allowaccountssameemail | 0 |
+    When I navigate to "Add a new user" node in "Site administration>Users>Accounts"
+    And I set the following fields to these values:
+      | Username                        | testmultiemailuser1             |
+      | Choose an authentication method | Manual accounts                 |
+      | New password                    | test@User1                      |
+      | First name                      | Test                            |
+      | Surname                         | Multi1                          |
+      | Email address                   | testmultiemailuser@example.com  |
+    And I press "Create user"
+    And I should see "Test Multi1"
+    And I press "Add a new user"
+    And I set the following fields to these values:
+      | Username                        | testmultiemailuser2             |
+      | Choose an authentication method | Manual accounts                 |
+      | New password                    | test@User2                      |
+      | First name                      | Test                            |
+      | Surname                         | Multi2                          |
+      | Email address                   | testmultiemailuser@example.com  |
+    And I press "Create user"
+    Then I should see "This email address is already registered"
\ No newline at end of file
index 77c33ba..48751b5 100644 (file)
Binary files a/admin/tool/lp/amd/build/dragdrop-reorder.min.js and b/admin/tool/lp/amd/build/dragdrop-reorder.min.js differ
index e03f666..4cc8611 100644 (file)
Binary files a/admin/tool/lp/amd/build/tree.min.js and b/admin/tool/lp/amd/build/tree.min.js differ
index 2168ebd..e11b466 100644 (file)
Binary files a/admin/tool/lp/amd/build/user_competency_course_navigation.min.js and b/admin/tool/lp/amd/build/user_competency_course_navigation.min.js differ
index 433604d..d14c9dd 100644 (file)
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 define(['core/str', 'core/yui'], function(str, Y) {
-
     // Private variables and functions.
 
+    /**
+     * Store the current instance of the core drag drop.
+     *
+     * @property dragDropInstance M.tool_lp.dragdrop_reorder
+     */
+    var dragDropInstance = null;
+
     /**
      * Translate the drophit event from YUI
      * into simple drag and drop nodes.
@@ -70,7 +76,10 @@ define(['core/str', 'core/yui'], function(str, Y) {
                     var context = {
                         callback: callback
                     };
-                    M.tool_lp.dragdrop_reorder({
+                    if (dragDropInstance) {
+                        dragDropInstance.destroy();
+                    }
+                    dragDropInstance = M.tool_lp.dragdrop_reorder({
                         group: group,
                         dragHandleText: dragHandleText,
                         sameNodeText: sameNodeText,
index a8ac21b..f52934d 100644 (file)
@@ -69,6 +69,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
             up:       38,
             right:    39,
             down:     40,
+            eight:    56,
             asterisk: 106
         };
 
@@ -274,6 +275,8 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
     Tree.prototype.handleKeyDown = function(item, e) {
         var currentIndex = this.visibleItems.index(item);
         var newItem = null;
+        var hasKeyModifier = e.shiftKey || e.ctrlKey || e.metaKey || e.altKey;
+        var thisObj = this;
 
         switch (e.keyCode) {
             case this.keys.home: {
@@ -282,7 +285,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                 newItem.focus();
                 if (e.shiftKey) {
                     this.multiSelectItem(newItem);
-                } else if (!e.ctrlKey) {
+                } else if (!hasKeyModifier) {
                     this.selectItem(newItem);
                 }
 
@@ -295,7 +298,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                 newItem.focus();
                 if (e.shiftKey) {
                     this.multiSelectItem(newItem);
-                } else if (!e.ctrlKey) {
+                } else if (!hasKeyModifier) {
                     this.selectItem(newItem);
                 }
 
@@ -307,7 +310,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
 
                 if (e.shiftKey) {
                     this.multiSelectItem(item);
-                } else if (e.ctrlKey) {
+                } else if (e.metaKey || e.ctrlKey) {
                     this.toggleItem(item);
                 } else {
                     this.selectItem(item);
@@ -327,7 +330,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                         itemParent.focus();
                         if (e.shiftKey) {
                             this.multiSelectItem(itemParent);
-                        } else if (!e.ctrlKey) {
+                        } else if (!hasKeyModifier) {
                             this.selectItem(itemParent);
                         }
                     }
@@ -346,7 +349,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                         newItem.focus();
                         if (e.shiftKey) {
                             this.multiSelectItem(newItem);
-                        } else if (!e.ctrlKey) {
+                        } else if (!hasKeyModifier) {
                             this.selectItem(newItem);
                         }
                     }
@@ -362,7 +365,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                     prev.focus();
                     if (e.shiftKey) {
                         this.multiSelectItem(prev);
-                    } else if (!e.ctrlKey) {
+                    } else if (!hasKeyModifier) {
                         this.selectItem(prev);
                     }
                 }
@@ -377,7 +380,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                     next.focus();
                     if (e.shiftKey) {
                         this.multiSelectItem(next);
-                    } else if (!e.ctrlKey) {
+                    } else if (!hasKeyModifier) {
                         this.selectItem(next);
                     }
                 }
@@ -386,9 +389,6 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
             }
             case this.keys.asterisk: {
                 // Expand all groups.
-
-                var thisObj = this;
-
                 this.parents.each(function() {
                     thisObj.expandGroup($(this));
                 });
@@ -396,6 +396,18 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
                 e.stopPropagation();
                 return false;
             }
+            case this.keys.eight: {
+                if (e.shiftKey) {
+                    // Expand all groups.
+                    this.parents.each(function() {
+                        thisObj.expandGroup($(this));
+                    });
+
+                    e.stopPropagation();
+                }
+
+                return false;
+            }
         }
 
         return true;
@@ -409,7 +421,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      * @param {Event} e The event.
      */
     Tree.prototype.handleKeyPress = function(item, e) {
-        if (e.altKey || e.ctrlKey || e.shiftKey) {
+        if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
             // Do nothing.
             return true;
         }
@@ -498,7 +510,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
      */
     Tree.prototype.handleDblClick = function(item, e) {
 
-        if (e.altKey || e.ctrlKey || e.shiftKey) {
+        if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
             // Do nothing.
             return true;
         }
@@ -540,7 +552,7 @@ define(['jquery', 'core/url', 'core/log'], function($, url, log) {
 
         if (e.shiftKey) {
             this.multiSelectItem(item);
-        } else if (e.ctrlKey) {
+        } else if (e.metaKey || e.ctrlKey) {
             this.toggleItem(item);
         } else {
             this.selectItem(item);
index 7cfa93d..a7a43ba 100644 (file)
@@ -38,8 +38,6 @@ define(['jquery'], function($) {
         this._userId = userId + '';
         this._competencyId = competencyId + '';
         this._courseId = courseId;
-        this._ignoreFirstUser = true;
-        this._ignoreFirstCompetency = true;
 
         $(userSelector).on('change', this._userChanged.bind(this));
         $(competencySelector).on('change', this._competencyChanged.bind(this));
@@ -52,11 +50,6 @@ define(['jquery'], function($) {
      * @param {Event} e
      */
     UserCompetencyCourseNavigation.prototype._userChanged = function(e) {
-        if (this._ignoreFirstUser) {
-            this._ignoreFirstUser = false;
-            return;
-        }
-
         var newUserId = $(e.target).val();
         var queryStr = '?userid=' + newUserId + '&courseid=' + this._courseId + '&competencyid=' + this._competencyId;
         document.location = this._baseUrl + queryStr;
@@ -69,10 +62,6 @@ define(['jquery'], function($) {
      * @param {Event} e
      */
     UserCompetencyCourseNavigation.prototype._competencyChanged = function(e) {
-        if (this._ignoreFirstCompetency) {
-            this._ignoreFirstCompetency = false;
-            return;
-        }
         var newCompetencyId = $(e.target).val();
         var queryStr = '?userid=' + this._userId + '&courseid=' + this._courseId + '&competencyid=' + newCompetencyId;
         document.location = this._baseUrl + queryStr;
index 4f44a7d..ace8117 100644 (file)
@@ -58,7 +58,7 @@ class course_summary_exporter extends \core_competency\external\exporter {
                 'type' => PARAM_TEXT,
             ),
             'idnumber' => array(
-                'type' => PARAM_TEXT,
+                'type' => PARAM_RAW,
             )
         );
     }
index 0055734..f3be693 100644 (file)
@@ -24,6 +24,7 @@
 namespace tool_lp\external;
 
 use core_competency\api;
+use core_competency\user_competency;
 use context_course;
 use renderer_base;
 use stdClass;
@@ -72,6 +73,8 @@ class user_competency_summary_in_course_exporter extends \core_competency\extern
         $related['usercompetency'] = null;
         $exporter = new user_competency_summary_exporter(null, $related);
         $result->usercompetencysummary = $exporter->export($output);
+        $result->usercompetencysummary->cangrade = user_competency::can_grade_user_in_course($this->related['user']->id,
+            $this->related['course']->id);
 
         $context = context_course::instance($this->related['course']->id);
         $exporter = new course_summary_exporter($this->related['course'], array('context' => $context));
index 915ea6f..32136bc 100644 (file)
@@ -102,7 +102,7 @@ class competency extends persistent {
         $mform->setType('description', PARAM_RAW);
         // ID number.
         $mform->addElement('text', 'idnumber', get_string('idnumber', 'tool_lp'), 'maxlength="100"');
-        $mform->setType('idnumber', PARAM_TEXT);
+        $mform->setType('idnumber', PARAM_RAW);
         $mform->addRule('idnumber', null, 'required', null, 'client');
         $mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'client');
 
index 6dde66c..6d4b6bc 100644 (file)
@@ -65,7 +65,7 @@ class competency_framework extends persistent {
         $mform->setType('description', PARAM_RAW);
         // ID number.
         $mform->addElement('text', 'idnumber', get_string('idnumber', 'tool_lp'), 'maxlength="100"');
-        $mform->setType('idnumber', PARAM_TEXT);
+        $mform->setType('idnumber', PARAM_RAW);
         $mform->addRule('idnumber', null, 'required', null, 'client');
         $mform->addRule('idnumber', get_string('maximumchars', '', 100), 'maxlength', 100, 'client');
 
index 4ffde89..58ab38b 100644 (file)
@@ -79,7 +79,6 @@ class competency_plan_navigation implements renderable, templatable {
         $data->competencyid = $this->competencyid;
         $data->planid = $this->planid;
         $data->baseurl = $this->baseurl;
-        $data->jumptocompetency = get_string('jumptocompetency', 'tool_lp');
 
         $plancompetencies = \core_competency\api::list_plan_competencies($data->planid);
         $data->competencies = array();
index 411a296..3d716b5 100644 (file)
@@ -84,8 +84,6 @@ class user_competency_course_navigation implements renderable, templatable {
         $data->courseid = $this->courseid;
         $data->baseurl = $this->baseurl;
         $data->groupselector = '';
-        $data->jumptocompetency = get_string('jumptocompetency', 'tool_lp');
-        $data->jumptouser = get_string('jumptouser', 'tool_lp');
 
         if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'),
                 $context)) {
index 209db55..258f382 100644 (file)
@@ -73,6 +73,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:coursecompetencyview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_template_competencies_page' => array(
         'classname'    => 'tool_lp\external',
@@ -100,6 +101,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:planviewown',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_plan_page' => array(
         'classname'    => 'tool_lp\external',
@@ -109,6 +111,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:planview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_related_competencies_section' => array(
         'classname'    => 'tool_lp\external',
@@ -147,6 +150,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:userevidenceview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_user_evidence_page' => array(
         'classname'    => 'tool_lp\external',
@@ -156,6 +160,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:userevidenceview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
 
     // User competency.
@@ -167,6 +172,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:planview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_user_competency_summary_in_plan' => array(
         'classname'    => 'tool_lp\external',
@@ -176,6 +182,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:planview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'tool_lp_data_for_user_competency_summary_in_course' => array(
         'classname'    => 'tool_lp\external',
@@ -185,6 +192,7 @@ $functions = array(
         'type'         => 'read',
         'capabilities' => 'moodle/competency:coursecompetencyview',
         'ajax'         => true,
+        'services'     => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
 );
 
index 75dd6b9..fab384d 100644 (file)
     display: table-cell;
 }
 .path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-outcome"] select,
-.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select {
+.path-admin-tool-lp [data-region="competencyruleconfig"] [data-region="rule-type"] select,
+.path-admin-tool-lp [data-region="competencylinktree"] select {
     width: 100%;
 }
 
index 0a51aef..d6ae8e6 100644 (file)
@@ -31,7 +31,7 @@
 
 <ul data-enhance="movetree" style="display: none;">
     <li>
-        <span>{{framework.shortname}}</span>
+        <span>{{{framework.shortname}}}</span>
         <ul>
             {{#competencies}}
                 {{> tool_lp/competencies_tree }}
index 5e30b8a..75c2282 100644 (file)
@@ -1,12 +1,12 @@
 <li data-id="{{id}}">
     {{#canmanage}}
     <span draggable="true">
-        {{shortname}}
+        {{{shortname}}}
     </span>
     {{/canmanage}}
     {{^canmanage}}
     <span>
-        {{shortname}}
+        {{{shortname}}}
     </span>
     {{/canmanage}}
     {{#haschildren}}
index c163870..d138fe1 100644 (file)
@@ -1,5 +1,5 @@
 <ul data-enhance="tree">
-    <li><span>{{shortname}}</span>
+    <li><span>{{{shortname}}}</span>
         <ul>
             {{#competencies}}
                 {{> tool_lp/competencies_tree }}
index 01e21c6..f756098 100644 (file)
 }}
 <nav id="competency-path-{{uniqid}}">
     <small>
-        <a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >{{framework.name}}</a>
+        <a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{framework.id}}&pagecontextid={{pagecontextid}}" >{{{framework.name}}}</a>
         /
         {{#ancestors}}
-            <a data-action="competency-dialogue" href="#" data-id="{{id}}">{{name}}</a>
+            <a data-action="competency-dialogue" href="#" data-id="{{id}}">{{{name}}}</a>
             {{^last}}<span> / </span>{{/last}}
         {{/ancestors}}
     </small>
index fcb4464..916522b 100644 (file)
@@ -3,7 +3,7 @@
 <h3>{{#str}}competencyframeworks, tool_lp{{/str}}</h3>
 <select data-action="chooseframework">
 {{#frameworks}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} <em>{{idnumber}}</em></option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} <em>{{idnumber}}</em></option>
 {{/frameworks}}
 </select>
 {{/singleFramework}}
@@ -15,7 +15,7 @@
     <button>{{#pix}}a/search, ,{{#str}}search{{/str}}{{/pix}}</button>
 </form>
 <ul data-enhance="linktree" style="display: none;">
-    <li><span>{{framework.shortname}}</span>
+    <li><span>{{{framework.shortname}}}</span>
         <ul>
             {{#competencies}}
                 {{> tool_lp/competencies_tree }}
index f3f1ca0..e255dfd 100644 (file)
@@ -7,7 +7,7 @@
     <button>{{#pix}}a/search, ,{{#str}}search{{/str}}{{/pix}}</button>
 </form>
 <ul data-enhance="linktree" style="display: none;">
-    <li data-id="0"><span>{{framework.shortname}}</span>
+    <li data-id="0"><span>{{{framework.shortname}}}</span>
         <ul>
             {{#competencies}}
                 {{> tool_lp/competencies_tree }}
index 1e052b9..bddebc9 100644 (file)
@@ -3,7 +3,7 @@
     <h3>{{#str}}learningplans, tool_lp{{/str}}</h3>
     <select data-action="chooseplan">
     {{#plans}}
-        <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{name}}</em></option>
+        <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{name}}}</option>
     {{/plans}}
     </select>
 {{/singlePlan}}
@@ -17,7 +17,7 @@
 </form>
 
 <ul data-enhance="linktree" style="display: none;">
-    <li><span>{{plan.name}}</span>
+    <li><span>{{{plan.name}}}</span>
         <ul>
             {{#competencies}}
                 {{> tool_lp/competencies_tree }}
index bf70ee9..d3eb766 100644 (file)
@@ -1,10 +1,10 @@
 <div class="pull-right well">
 {{#hascompetencies}}
 <span>
-<label for="competency-nav-{{uniqid}}" class="accesshide">{{jumptocompetency}}</label>
+<label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
 <select id="competency-nav-{{uniqid}}">
 {{#competencies}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} {{idnumber}}</option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
 {{/competencies}}
 </select>
 </span>
@@ -15,7 +15,7 @@
 require(['core/form-autocomplete', 'tool_lp/competency_plan_navigation'], function(autocomplete, nav) {
     (new nav('#competency-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{competencyid}}, {{planid}}));
 {{#hascompetencies}}
-    autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, '{{jumptocompetency}}');
+    autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptocompetency, tool_lp{{/ str }}{{/ quote }});
 {{/hascompetencies}}
 
 });
index ec8c60f..907e731 100644 (file)
         <tbody>
             {{#children}}
             <tr data-competency="{{id}}">
-                <th scope="row">{{shortname}}</th>
-                <td><input type="number" min="0" value="{{points}}" name="points" aria-label="{{#str}}pointsgivenfor, tool_lp, {{competency.shortname}}{{/str}}"></td>
-                <td><input type="checkbox" value="1" name="required" {{#required}}checked{{/required}} aria-label="{{#str}}aisrequired, tool_lp, {{competency.shortname}}{{/str}}"></td>
+                <th scope="row">{{{shortname}}}</th>
+                <td>
+                    <label class="accesshide" for="pointsforcompetency-{{id}}">{{#str}}pointsgivenfor, tool_lp, {{{competency.shortname}}}{{/str}}</label>
+                    <input id="pointsforcompetency-{{id}}" type="number" min="0" value="{{points}}" name="points" />
+                </td>
+                <td>
+                    <label class="accesshide" for="competency-{{id}}-isrequired">{{#str}}aisrequired, tool_lp, {{{competency.shortname}}}{{/str}}</label>
+                    <input id="competency-{{id}}-isrequired" type="checkbox" value="1" name="required" {{#required}}checked{{/required}} />
+                </td>
             </tr>
             {{/children}}
         </tbody>
index 918b6ff..b1bca28 100644 (file)
@@ -1,10 +1,10 @@
 <div class='competency-heading'>
-    <h4 id="competency_link_{{competency.id}}">{{competency.shortname}}
+    <h4 id="competency_link_{{competency.id}}">{{{competency.shortname}}}
         <small>{{competency.idnumber}}</small>
     </h4>
     {{#framework}}
         <div class='competency-origin'>
-            <p><small>{{framework.shortname}} - {{taxonomyterm}}</small>
+            <p><small>{{{framework.shortname}}} - {{taxonomyterm}}</small>
         </div>
     {{/framework}}
 </div>
index 0a65312..963f019 100644 (file)
@@ -66,7 +66,7 @@
             <a href="{{pluginbaseurl}}user_competency_in_course.php?courseid={{courseid}}&competencyid={{competency.id}}&userid={{gradableuserid}}"
                    id="competency-info-link-{{competency.id}}"
                    title="{{#str}}viewdetails, tool_lp{{/str}}">
-                <p><strong>{{competency.shortname}} <em>{{competency.idnumber}}</em></strong></p>
+                <p><strong>{{{competency.shortname}}} <em>{{competency.idnumber}}</em></strong></p>
             </a>
             <p>{{{competency.description}}}</p>
         {{/competency}}
index 9202052..e57159b 100644 (file)
@@ -68,7 +68,7 @@
         <div>
             {{#leastproficient}}
                 <a href="#competency-info-link-{{id}}">
-                <div><p>{{shortname}} <em>{{idnumber}}</em></p></div>
+                <div><p>{{{shortname}}} <em>{{idnumber}}</em></p></div>
                 </a>
             {{/leastproficient}}
         </div>
index c57d629..fce2b3d 100644 (file)
 }}
 <div data-region="managecompetencies">
 <h2>
-    {{framework.shortname}}
+    {{{framework.shortname}}}
     {{#canmanage}}
             <a href="{{pluginbaseurl}}/editcompetencyframework.php?id={{framework.id}}&pagecontextid={{pagecontextid}}&return=competencies">{{#pix}}t/edit, core, {{#str}}editcompetencyframework, tool_lp{{/str}}{{/pix}}</a>
     {{/canmanage}}
 </h2>
+<div>{{{framework.description}}}</div>
 <h3>{{#str}}competencies, core_competency{{/str}}</h3>
 <div class="row-fluid">
 <div class="span6">
@@ -120,7 +121,7 @@ require(['tool_lp/tree', 'tool_lp/competencytree', 'tool_lp/competencyactions',
         function(ariatree, treeModel, actions, $) {
 
     treeModel.init({{framework.id}},
-                   '{{framework.shortname}}',
+                   {{#quote}} {{{framework.shortname}}} {{/quote}},
                    '{{search}}',
                    '[data-enhance=tree]',
                    {{canmanage}});
index ee99906..93c7605 100644 (file)
@@ -52,7 +52,7 @@
     <tbody class="drag-parentnode">
         {{#competencyframeworks}}
         <tr class="drag-samenode" data-frameworkid="{{id}}">
-            <td><span class="drag-handlecontainer"></span><span><a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{id}}&amp;pagecontextid={{pagecontextid}}">{{shortname}} ({{idnumber}})</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
+            <td><span class="drag-handlecontainer"></span><span><a href="{{pluginbaseurl}}/competencies.php?competencyframeworkid={{id}}&amp;pagecontextid={{pagecontextid}}">{{{shortname}}} ({{idnumber}})</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
             <td>{{competenciescount}}</td>
             <td>{{contextnamenoprefix}}</td>
             <td>
index 82d2b76..bb5fc34 100644 (file)
@@ -53,7 +53,7 @@
     <tbody class="drag-parentnode">
         {{#templates}}
         <tr class="drag-samenode" data-templateid="{{id}}">
-            <td><a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&amp;pagecontextid={{pagecontextid}}">{{shortname}}</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
+            <td><a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&amp;pagecontextid={{pagecontextid}}">{{{shortname}}}</a></span> {{^visible}}{{#str}}hiddenhint, tool_lp{{/str}}{{/visible}}</td>
             <td>{{contextnamenoprefix}}</td>
             <td><a class="template-cohorts" href="{{pluginbaseurl}}/template_cohorts.php?id={{id}}&amp;pagecontextid={{pagecontextid}}">{{cohortscount}}</a></td>
             <td><a class="template-userplans" href="{{pluginbaseurl}}/template_plans.php?id={{id}}&amp;pagecontextid={{pagecontextid}}">{{planscount}}</a></td>
index aec4a7d..ee25b5c 100644 (file)
@@ -19,7 +19,7 @@
 }}
 <div data-region="plan-page" data-id="{{plan.id}}" data-userid="{{plan.userid}}">
     <h2>
-        {{plan.name}}
+        {{{plan.name}}}
         {{#plan.canbeedited}}
             <a href="{{pluginbaseurl}}/editplan.php?id={{plan.id}}&amp;userid={{plan.userid}}">{{#pix}}t/edit, core, {{#str}}editplan, tool_lp{{/str}}{{/pix}}</a>
         {{/plan.canbeedited}}
@@ -33,6 +33,7 @@
     </div>
     {{/plan.canbeedited}}
     <div data-region="plan-summary">
+        {{{plan.description}}}
         <dl>
             <dt>{{#str}}status, tool_lp{{/str}}</dt>
             <dd>
@@ -78,7 +79,7 @@
                     {{#canread}}
                         <a href="{{pluginbaseurl}}/templatecompetencies.php?templateid={{id}}&amp;pagecontextid={{contextid}}">
                     {{/canread}}
-                    {{plan.template.shortname}}{{#canread}}</a>{{/canread}}
+                    {{{plan.template.shortname}}}{{#canread}}</a>{{/canread}}
                     {{#plan.isunlinkallowed}}
                         (<a data-action="plan-unlink" href="#">{{#str}}unlinkplantemplate, tool_lp{{/str}}</a>)
                     {{/plan.isunlinkallowed}}
                         {{#plan.canbeedited}}
                         <span class="drag-handlecontainer pull-left"></span>
                         {{/plan.canbeedited}}
-                        <a data-usercompetency="true" href="#">{{competency.shortname}}</a>
+                        <a data-usercompetency="true" href="#">{{{competency.shortname}}}</a>
                         <em>{{competency.idnumber}}</em>
                         {{#comppath}}
                             <br>
index 78d1c1d..935b76a 100644 (file)
@@ -53,7 +53,7 @@
         {{#plans}}
         <tr data-region="plan-node" data-id="{{id}}" data-userid="{{userid}}">
             <td>
-                <span><a href="{{pluginbaseurl}}/plan.php?id={{id}}">{{name}}</a></span>
+                <span><a href="{{pluginbaseurl}}/plan.php?id={{id}}">{{{name}}}</a></span>
             </td>
              <td>
                {{#isbasedontemplate}}
index f9b2c51..71967d7 100644 (file)
@@ -12,7 +12,7 @@
             {{/showdeleterelatedaction}}
             <p>
                 <a href="#" data-action="competency-dialogue" data-id="{{id}}">
-                    {{shortname}}{{#idnumber}} {{idnumber}}{{/idnumber}}
+                    {{{shortname}}}{{#idnumber}} {{idnumber}}{{/idnumber}}
                 </a>
             </p>
         </li>
index 6095c71..ba31740 100644 (file)
 }}
 <div data-region="templatecompetenciespage">
     <h2>
-        {{template.shortname}}
+        {{{template.shortname}}}
         {{#template.canmanage}}
             <a href="{{pluginbaseurl}}/edittemplate.php?id={{template.id}}&amp;pagecontextid={{pagecontextid}}">{{#pix}}t/edit, core, {{#str}}edittemplate, tool_lp{{/str}}{{/pix}}</a>
         {{/template.canmanage}}
     </h2>
+    <div>{{{template.description}}}</div>
     {{#canmanagetemplatecompetencies}}
     <div data-region="actions" class="clearfix">
         <div class="pull-left">
@@ -54,7 +55,7 @@
                         {{#hascourses}}
                         <ul class="inline">
                         {{#linkedcourses}}
-                            <li><a href="{{viewurl}}?id={{id}}">{{fullname}} ({{shortname}})</a></li>
+                            <li><a href="{{viewurl}}?id={{id}}">{{{fullname}}} ({{{shortname}}})</a></li>
                         {{/linkedcourses}}
                         </ul>
                         {{/hascourses}}
index 66caf50..0a8bdae 100644 (file)
                 {{#showcompetencylinks}}
                 <a href="#competency_link_{{id}}">
                 {{/showcompetencylinks}}
-                <div><p>{{shortname}} <em>{{idnumber}}</em></p></div>
+                <div><p>{{{shortname}}} <em>{{idnumber}}</em></p></div>
                 {{#showcompetencylinks}}
                 </a>
                 {{/showcompetencylinks}}
index 6ebc4c1..a81c0f8 100644 (file)
@@ -3,7 +3,7 @@
 <form class="user-competency-course-navigation">
 {{#hasusers}}
 <span>
-<label for="user-nav-{{uniqid}}" class="accesshide">{{jumptouser}}</label>
+<label for="user-nav-{{uniqid}}" class="accesshide">{{#str}}jumptouser, tool_lp{{/str}}</label>
 <select id="user-nav-{{uniqid}}">
 {{#users}}
 <option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{fullname}}</option>
 <br>
 {{#hascompetencies}}
 <span>
-<label for="competency-nav-{{uniqid}}" class="accesshide">{{jumptocompetency}}</label>
+<label for="competency-nav-{{uniqid}}" class="accesshide">{{#str}}jumptocompetency, tool_lp{{/str}}</label>
 <select id="competency-nav-{{uniqid}}">
 {{#competencies}}
-<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{shortname}} {{idnumber}}</option>
+<option value="{{id}}" {{#selected}}selected="selected"{{/selected}}>{{{shortname}}} {{idnumber}}</option>
 {{/competencies}}
 </select>
 </span>
 require(['core/form-autocomplete', 'tool_lp/user_competency_course_navigation'], function(autocomplete, nav) {
     (new nav('#user-nav-{{uniqid}}', '#competency-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{competencyid}}, {{courseid}}));
 {{#hasusers}}
-    autocomplete.enhance('#user-nav-{{uniqid}}', false, false, '{{jumptouser}}');
+    autocomplete.enhance('#user-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptouser, tool_lp{{/ str }}{{/ quote }});
 {{/hasusers}}
 {{#hascompetencies}}
-    autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, '{{jumptocompetency}}');
+    autocomplete.enhance('#competency-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptocompetency, tool_lp{{/ str }}{{/ quote }});
 {{/hascompetencies}}
 
 });
index 34ab5e2..7c81ad2 100644 (file)
@@ -42,6 +42,7 @@
 }}
 {{#usercompetencysummary}}
 <div data-region="user-competency-full-info" data-node="user-competency" data-competencyid="{{usercompetency.competencyid}}" data-userid="{{usercompetency.userid}}" data-region-id="{{uniqid}}">
+<div>{{{plan.description}}}</div>
 {{#plan.iscompleted}}
 <div class="alert alert-info" role="alert">
     {{#str}} usercompetencyfrozen, tool_lp {{/str}}
index 4b11153..18b501b 100644 (file)
@@ -53,7 +53,7 @@
     <tbody>
         {{#evidence}}
         <tr data-region='user-evidence-node' data-id="{{id}}" data-userid="{{userid}}">
-            <td><a href="{{pluginbaseurl}}/user_evidence.php?id={{id}}">{{name}}</a></td>
+            <td><a href="{{pluginbaseurl}}/user_evidence.php?id={{id}}">{{{name}}}</a></td>
             <td>
                 {{^hasurlorfiles}}
                     -
@@ -77,7 +77,7 @@
                     <ul class="user-evidence-competencies">
                     {{#usercompetencies}}
                         <li>
-                            {{competency.shortname}} <small><em>{{competency.idnumber}}</em></small> ({{usercompetency.statusname}}{{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}})
+                            {{{competency.shortname}}} <small><em>{{competency.idnumber}}</em></small> ({{usercompetency.statusname}}{{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}})
                         </li>
                     {{/usercompetencies}}
                     </ul>
index 40876be..e0582b2 100644 (file)
@@ -31,7 +31,7 @@
 {{#userevidence}}
 <div data-region="user-evidence-page" data-id="{{id}}" data-userid="{{userid}}">
     <h2>
-        {{name}}
+        {{{name}}}
         {{#canmanage}}
             <a href="{{pluginbaseurl}}/user_evidence_edit.php?id={{id}}&amp;userid={{userid}}">{{#pix}}t/edit, core, {{#str}}editthisuserevidence, tool_lp{{/str}}{{/pix}}</a>
         {{/canmanage}}
@@ -78,7 +78,7 @@
                 {{#usercompetencies}}
                 <tr data-id="{{competency.id}}">
                     <td>
-                        <a href="{{pluginbaseurl}}/user_competency.php?id={{usercompetency.id}}" data-id="{{usercompetency.id}}">{{competency.shortname}}</a>
+                        <a href="{{pluginbaseurl}}/user_competency.php?id={{usercompetency.id}}" data-id="{{usercompetency.id}}">{{{competency.shortname}}}</a>
                     </td>
                      <td>
                         {{usercompetency.statusname}} {{#usercompetency.reviewer.fullname}} / {{usercompetency.reviewer.fullname}}{{/usercompetency.reviewer.fullname}}
index b0351dc..f409b47 100644 (file)
@@ -25,6 +25,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$plugin->version   = 2016020925; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2016050400; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2014110400; // Requires this Moodle version.
 $plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).
index caebb22..159ec86 100644 (file)
Binary files a/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-debug.js and b/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-debug.js differ
index f91699e..47c197c 100644 (file)
Binary files a/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js and b/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder-min.js differ
index caebb22..159ec86 100644 (file)
Binary files a/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder.js and b/admin/tool/lp/yui/build/moodle-tool_lp-dragdrop-reorder/moodle-tool_lp-dragdrop-reorder.js differ
index c2feccb..3624631 100644 (file)
@@ -63,5 +63,5 @@ Y.extend(DRAGREORDER, M.core.dragdrop, {
 
 M.tool_lp = M.tool_lp || {};
 M.tool_lp.dragdrop_reorder = function(params) {
-    new DRAGREORDER(params);
+    return new DRAGREORDER(params);
 };
index a5527c2..a444850 100644 (file)
     <h4>{{#str}}unmappedin, tool_lpmigrate, {{frameworkfrom.shortname}}{{/str}}</h4>
     <ul>
         {{#unmappedfrom}}
-            <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{shortname}}</a> <em>{{idnumber}}</em></li>
+            <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{{shortname}}}</a> <em>{{idnumber}}</em></li>
         {{/unmappedfrom}}
     </ul>
     {{/hasunmappedfrom}}
     <h4>{{#str}}unmappedin, tool_lpmigrate, {{frameworkto.shortname}}{{/str}}</h4>
     <ul>
         {{#unmappedto}}
-            <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{shortname}}</a> <em>{{idnumber}}</em></li>
+            <li><a href="#" data-id="{{id}}" data-action="competency-dialogue" data-includecourses="true">{{{shortname}}}</a> <em>{{idnumber}}</em></li>
         {{/unmappedto}}
     </ul>
     {{/hasunmappedto}}
index 06b06de..2acb3b2 100644 (file)
@@ -126,6 +126,9 @@ class category_bin extends base_bin {
             throw new \moodle_exception('Failed to backup activity prior to deletion.');
         }
 
+        // Have finished with the controller, let's destroy it, freeing mem and resources.
+        $controller->destroy();
+
         // Grab the filename.
         $file = $result['backup_destination'];
         if (!$file->get_contenthash()) {
@@ -259,6 +262,9 @@ class category_bin extends base_bin {
         // Run the import.
         $controller->execute_plan();
 
+        // Have finished with the controller, let's destroy it, freeing mem and resources.
+        $controller->destroy();
+
         // Fire event.
         $event = \tool_recyclebin\event\category_bin_item_restored::create(array(
             'objectid' => $item->id,
index a05a7cf..67e1a21 100644 (file)
@@ -130,6 +130,9 @@ class course_bin extends base_bin {
             throw new \moodle_exception('Failed to backup activity prior to deletion.');
         }
 
+        // Have finished with the controller, let's destroy it, freeing mem and resources.
+        $controller->destroy();
+
         // Grab the filename.
         $file = $result['backup_destination'];
         if (!$file->get_contenthash()) {
@@ -246,6 +249,9 @@ class course_bin extends base_bin {
         // Run the import.
         $controller->execute_plan();
 
+        // Have finished with the controller, let's destroy it, freeing mem and resources.
+        $controller->destroy();
+
         // Fire event.
         $event = \tool_recyclebin\event\course_bin_item_restored::create(array(
             'objectid' => $item->id,
index edfa1a2..1f83de3 100644 (file)
@@ -85,6 +85,8 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
     public function test_restore() {
         global $DB;
 
+        $startcount = $DB->count_records('course_modules');
+
         // Delete the course module.
         course_delete_module($this->quiz->cmid);
 
@@ -95,7 +97,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
         }
 
         // Check that it was restored and removed from the recycle bin.
-        $this->assertEquals(1, $DB->count_records('course_modules'));
+        $this->assertEquals($startcount, $DB->count_records('course_modules'));
         $this->assertEquals(0, count($recyclebin->get_items()));
     }
 
@@ -105,6 +107,8 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
     public function test_delete() {
         global $DB;
 
+        $startcount = $DB->count_records('course_modules');
+
         // Delete the course module.
         course_delete_module($this->quiz->cmid);
 
@@ -115,7 +119,7 @@ class tool_recyclebin_course_bin_tests extends advanced_testcase {
         }
 
         // Item was deleted, so no course module was restored.
-        $this->assertEquals(0, $DB->count_records('course_modules'));
+        $this->assertEquals($startcount - 1, $DB->count_records('course_modules'));
         $this->assertEquals(0, count($recyclebin->get_items()));
     }
 
index 4486cde..7b111cb 100644 (file)
@@ -740,7 +740,6 @@ class tool_uploadcourse_course {
                 $this->error('errorwhilerestoringcourse', new lang_string('errorwhilerestoringthecourse', 'tool_uploadcourse'));
             }
             $rc->destroy();
-            unset($rc); // File logging is a mess, we can only try to rely on gc to close handles.
         }
 
         // Proceed with enrolment data.
index 20bbaa3..0b62e87 100644 (file)
@@ -35,13 +35,6 @@ global $CFG;
  */
 class tool_uploadcourse_course_testcase extends advanced_testcase {
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     public function test_proceed_without_prepare() {
         $this->resetAfterTest(true);
         $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
index 41a838c..6768507 100644 (file)
@@ -129,7 +129,6 @@ class tool_uploadcourse_helper_testcase extends advanced_testcase {
         $this->assertTrue(isset($result['backup_destination']));
         $c1backupfile = $result['backup_destination']->copy_content_to_temp();
         $bc->destroy();
-        unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
 
         // Creating backup file.
         $bc = new backup_controller(backup::TYPE_1COURSE, $c2->id, backup::FORMAT_MOODLE,
@@ -139,7 +138,6 @@ class tool_uploadcourse_helper_testcase extends advanced_testcase {
         $this->assertTrue(isset($result['backup_destination']));
         $c2backupfile = $result['backup_destination']->copy_content_to_temp();
         $bc->destroy();
-        unset($bc); // File logging is a mess, we can only try to rely on gc to close handles.
 
         $oldcfg = isset($CFG->keeptempdirectoriesonbackup) ? $CFG->keeptempdirectoriesonbackup : false;
         $CFG->keeptempdirectoriesonbackup = true;
index 2d2ec04..1dd20dc 100644 (file)
@@ -36,13 +36,6 @@ require_once($CFG->libdir . '/csvlib.class.php');
  */
 class tool_uploadcourse_processor_testcase extends advanced_testcase {
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     public function test_basic() {
         global $DB;
         $this->resetAfterTest(true);
index 30536e6..75b0d84 100644 (file)
@@ -937,7 +937,9 @@ class auth_plugin_ldap extends auth_plugin_base {
                 // It isn't possible to just rely on the configured suspension attribute since
                 // things like active directory use bit masks, other things using LDAP might
                 // do different stuff as well.
-                $user->suspended = $this->is_user_suspended($user);
+                //
+                // The cast to int is a workaround for MDL-53959.
+                $user->suspended = (int)$this->is_user_suspended($user);
                 if (empty($user->lang)) {
                     $user->lang = $CFG->lang;
                 }
@@ -1012,7 +1014,8 @@ class auth_plugin_ldap extends auth_plugin_base {
             if (!empty($updatekeys)) {
                 $newuser = new stdClass();
                 $newuser->id = $userid;
-                $newuser->suspended = $this->is_user_suspended((object) $newinfo);
+                // The cast to int is a workaround for MDL-53959.
+                $newuser->suspended = (int)$this->is_user_suspended((object) $newinfo);
 
                 foreach ($updatekeys as $key) {
                     if (isset($newinfo[$key])) {
index 60f44c3..997f757 100644 (file)
@@ -23,6 +23,7 @@ if (!is_enabled_auth('shibboleth')) {
 }
 
 // Front channel logout.
+$inputstream = file_get_contents("php://input");
 if ($action == 'logout' && !empty($redirect)) {
 
     if ($USER->auth == 'shibboleth') {
@@ -32,7 +33,7 @@ if ($action == 'logout' && !empty($redirect)) {
         redirect($redirect);
     }
 
-} else if (!file_get_contents("php://input")) {
+} else if (!empty($inputstream)) {
 
     // Back channel logout.
     // Set SOAP header.
index e270f9e..35edd9c 100644 (file)
@@ -158,6 +158,8 @@ class backup_controller extends base_controller {
     public function destroy() {
         // Only need to destroy circulars under the plan. Delegate to it.
         $this->plan->destroy();
+        // Loggers may have also chained references, destroy them. Also closing resources when needed.
+        $this->logger->destroy();
     }
 
     public function finish_ui() {
@@ -184,7 +186,7 @@ class backup_controller extends base_controller {
             $this->save_controller();
             $tbc = self::load_controller($this->backupid);
             $this->logger = $tbc->logger; // wakeup loggers
-            $tbc->destroy(); // Clean temp controller structures
+            $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive.
 
         } else if ($status == backup::STATUS_FINISHED_OK) {
             // If the operation has ended without error (backup::STATUS_FINISHED_OK)
index 8c72c58..2829b99 100644 (file)
@@ -170,6 +170,8 @@ class restore_controller extends base_controller {
     public function destroy() {
         // Only need to destroy circulars under the plan. Delegate to it.
         $this->plan->destroy();
+        // Loggers may have also chained references, destroy them. Also closing resources when needed.
+        $this->logger->destroy();
     }
 
     public function finish_ui() {
@@ -196,7 +198,7 @@ class restore_controller extends base_controller {
             $this->save_controller();
             $tbc = self::load_controller($this->restoreid);
             $this->logger = $tbc->logger; // wakeup loggers
-            $tbc->destroy(); // Clean temp controller structures
+            $tbc->plan->destroy(); // Clean plan controller structures, keeping logger alive.
 
         } else if ($status == backup::STATUS_FINISHED_OK) {
             // If the operation has ended without error (backup::STATUS_FINISHED_OK)
index 45e792d..6366c8f 100644 (file)
@@ -163,5 +163,10 @@ class backup_root_task extends backup_task {
         $groups = new backup_groups_setting('groups', base_setting::IS_BOOLEAN, true);
         $groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
         $this->add_setting($groups);
+
+        // Define competencies inclusion setting if competencies are enabled.
+        $competencies = new backup_competencies_setting();
+        $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+        $this->add_setting($competencies);
     }
 }
index f799e4a..d2e1e5f 100644 (file)
@@ -130,6 +130,27 @@ class backup_calendarevents_setting extends backup_anonymize_setting {}
  */
 class backup_userscompletion_setting extends backup_anonymize_setting {}
 
+/**
+ * root setting to control if backup will include competencies or not.
+ */
+class backup_competencies_setting extends backup_generic_setting {
+
+    /**
+     * backup_competencies_setting constructor.
+     */
+    public function __construct() {
+        $defaultvalue = false;
+        $visibility = base_setting::HIDDEN;
+        $status = base_setting::LOCKED_BY_CONFIG;
+        if (\core_competency\api::is_enabled()) {
+            $defaultvalue = true;
+            $visibility = base_setting::VISIBLE;
+            $status = base_setting::NOT_LOCKED;
+        }
+        parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+    }
+}
+
 // Section backup settings
 
 /**
index 2ace0f3..da219a1 100644 (file)
@@ -1585,6 +1585,21 @@ class backup_course_competencies_structure_step extends backup_structure_step {
 
         return $wrapper;
     }
+
+    /**
+     * Execute conditions.
+     *
+     * @return bool
+     */
+    protected function execute_condition() {
+
+        // Do not execute if competencies are not included.
+        if (!$this->get_setting_value('competencies')) {
+            return false;
+        }
+
+        return true;
+    }
 }
 
 /**
@@ -1612,6 +1627,21 @@ class backup_activity_competencies_structure_step extends backup_structure_step
 
         return $wrapper;
     }
+
+    /**
+     * Execute conditions.
+     *
+     * @return bool
+     */
+    protected function execute_condition() {
+
+        // Do not execute if competencies are not included.
+        if (!$this->get_setting_value('competencies')) {
+            return false;
+        }
+
+        return true;
+    }
 }
 
 /**
index 922e203..42b7c80 100644 (file)
@@ -266,5 +266,11 @@ class restore_root_task extends restore_task {
         $groups->set_ui(new backup_setting_ui_checkbox($groups, get_string('rootsettinggroups', 'backup')));
         $groups->get_ui()->set_changeable($changeable);
         $this->add_setting($groups);
+
+        // Competencies restore setting. Show when competencies is enabled and the setting is available.
+        $hascompetencies = !empty($rootsettings['competencies']);
+        $competencies = new restore_competencies_setting($hascompetencies);
+        $competencies->set_ui(new backup_setting_ui_checkbox($competencies, get_string('rootsettingcompetencies', 'backup')));
+        $this->add_setting($competencies);
     }
 }
index 3ac0e2b..005449f 100644 (file)
@@ -78,6 +78,30 @@ class restore_comments_setting extends restore_role_assignments_setting {}
  */
 class restore_badges_setting extends restore_generic_setting {}
 
+/**
+ * root setting to control if competencies will also be restored.
+ */
+class restore_competencies_setting extends restore_generic_setting {
+
+    /**
+     * restore_competencies_setting constructor.
+     * @param bool $hascompetencies Flag whether to set the restore setting as checked and unlocked.
+     */
+    public function __construct($hascompetencies) {
+        $defaultvalue = false;
+        $visibility = base_setting::HIDDEN;
+        $status = base_setting::LOCKED_BY_CONFIG;
+        if (\core_competency\api::is_enabled()) {
+            $visibility = base_setting::VISIBLE;
+            if ($hascompetencies) {
+                $defaultvalue = true;
+                $status = base_setting::NOT_LOCKED;
+            }
+        }
+        parent::__construct('competencies', base_setting::IS_BOOLEAN, $defaultvalue, $visibility, $status);
+    }
+}
+
 /**
  * root setting to control if restore will create
  * events or no, depends of @restore_users_setting
index 861dcae..3c1f547 100644 (file)
@@ -1703,27 +1703,54 @@ class restore_course_structure_step extends restore_structure_step {
      */
     public function process_course($data) {
         global $CFG, $DB;
+        $context = context::instance_by_id($this->task->get_contextid());
+        $userid = $this->task->get_userid();
+        $target = $this->get_task()->get_target();
+        $isnewcourse = $target != backup::TARGET_CURRENT_ADDING && $target != backup::TARGET_EXISTING_ADDING;
+
+        // When restoring to a new course we can set all the things except for the ID number.
+        $canchangeidnumber = $isnewcourse || has_capability('moodle/course:changeidnumber', $context, $userid);
+        $canchangeshortname = $isnewcourse || has_capability('moodle/course:changeshortname', $context, $userid);
+        $canchangefullname = $isnewcourse || has_capability('moodle/course:changefullname', $context, $userid);
+        $canchangesummary = $isnewcourse || has_capability('moodle/course:changesummary', $context, $userid);
 
         $data = (object)$data;
+        $data->id = $this->get_courseid();
 
         $fullname  = $this->get_setting_value('course_fullname');
         $shortname = $this->get_setting_value('course_shortname');
         $startdate = $this->get_setting_value('course_startdate');
 
-        // Calculate final course names, to avoid dupes
+        // Calculate final course names, to avoid dupes.
         list($fullname, $shortname) = restore_dbops::calculate_course_names($this->get_courseid(), $fullname, $shortname);
 
-        // Need to change some fields before updating the course record
-        $data->id = $this->get_courseid();
-        $data->fullname = $fullname;
-        $data->shortname= $shortname;
+        if ($canchangefullname) {
+            $data->fullname = $fullname;
+        } else {
+            unset($data->fullname);
+        }
+
+        if ($canchangeshortname) {
+            $data->shortname = $shortname;
+        } else {
+            unset($data->shortname);
+        }
+
+        if (!$canchangesummary) {
+            unset($data->summary);
+            unset($data->summaryformat);
+        }
 
         // Only allow the idnumber to be set if the user has permission and the idnumber is not already in use by
         // another course on this site.
-        $context = context::instance_by_id($this->task->get_contextid());
-        if (!empty($data->idnumber) && has_capability('moodle/course:changeidnumber', $context, $this->task->get_userid()) &&
-                $this->task->is_samesite() && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
+        if (!empty($data->idnumber) && $canchangeidnumber && $this->task->is_samesite()
+                && !$DB->record_exists('course', array('idnumber' => $data->idnumber))) {
             // Do not reset idnumber.
+
+        } else if (!$isnewcourse) {
+            // Prevent override when restoring as merge.
+            unset($data->idnumber);
+
         } else {
             $data->idnumber = '';
         }
@@ -3186,8 +3213,8 @@ class restore_course_competencies_structure_step extends restore_structure_step
      */
     protected function execute_condition() {
 
-        // Do not restore when competencies are disabled.
-        if (!\core_competency\api::is_enabled()) {
+        // Do not execute if competencies are not included.
+        if (!$this->get_setting_value('competencies')) {
             return false;
         }
 
@@ -3261,8 +3288,8 @@ class restore_activity_competencies_structure_step extends restore_structure_ste
      */
     protected function execute_condition() {
 
-        // Do not restore when competencies are disabled.
-        if (!\core_competency\api::is_enabled()) {
+        // Do not execute if competencies are not included.
+        if (!$this->get_setting_value('competencies')) {
             return false;
         }
 
index 47b9971..bac5859 100644 (file)
@@ -39,13 +39,6 @@ require_once($CFG->libdir . '/completionlib.php');
  */
 class core_backup_moodle2_course_format_testcase extends advanced_testcase {
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     /**
      * Tests a backup and restore adds the required section option data
      * when the same course format is used.
@@ -269,4 +262,4 @@ class format_test_cs2_options extends format_test_cs_options {
             ),
         ) + parent::section_format_options($foreditform);
     }
-}
\ No newline at end of file
+}
index 4842d4a..5231082 100644 (file)
@@ -38,13 +38,6 @@ require_once($CFG->libdir . '/completionlib.php');
  */
 class core_backup_moodle2_testcase extends advanced_testcase {
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     /**
      * Tests the availability field on modules and sections is correctly
      * backed up and restored.
@@ -161,11 +154,6 @@ class core_backup_moodle2_testcase extends advanced_testcase {
                     $thrown->getFile() . ':' . $thrown->getLine(). "]\n\n";
         }
 
-        // Must set restore_controller variable to null so that php
-        // garbage-collects it; otherwise the file will be left open and
-        // attempts to delete it will cause a permission error on Windows
-        // systems, breaking unit tests.
-        $rc = null;
         $this->assertNull($thrown);
 
         // Get information about the resulting course and check that it is set
index 31e7f03..88a9c88 100644 (file)
@@ -1,6 +1,14 @@
 This files describes API changes in /backup/*,
 information provided here is intended especially for developers.
 
+=== 3.1 ===
+
+* New close() method added to loggers so they can close any open resource. Previously
+  any backup and restore operation using the file logger may be leaving unclosed files.
+* New destroy() method added to loggers, normally called from backup and restore controllers
+  own destroy() method to ensure that all references in the chained loggers are deleted
+  and any open resource within them is closed properly.
+
 === 3.0 ===
 
 * The backup_auto_keep setting, in automated backups configuration, is now
index 6bcbda4..8e4ced3 100644 (file)
@@ -131,6 +131,7 @@ class backup_check_testcase extends advanced_testcase {
             backup::INTERACTIVE_NO, backup::MODE_GENERAL, $this->userid);
         $this->assertTrue(backup_check::check_security($bc, true));
         $this->assertTrue($bc instanceof backup_controller);
+        $bc->destroy();
 
     }
 }
index 120ea6c..6d48d36 100644 (file)
@@ -101,9 +101,11 @@ abstract class restore_dbops {
 
             // If included, add it
             if ($included) {
-                $includedtasks[] = $task;
+                $includedtasks[] = clone($task); // A clone is enough. In fact we only need the basepath.
             }
         }
+        $rc->destroy(); // Always need to destroy.
+
         return $includedtasks;
     }
 
@@ -1510,8 +1512,12 @@ abstract class restore_dbops {
         // Calculate the context we are going to use for capability checking
         $context = context_course::instance($courseid);
 
+        // TODO: Some day we must kill this dependency and change the process
+        // to pass info around without loading a controller copy.
         // When conflicting users are detected we may need original site info.
-        $restoreinfo = restore_controller_dbops::load_controller($restoreid)->get_info();
+        $rc = restore_controller_dbops::load_controller($restoreid);
+        $restoreinfo = $rc->get_info();
+        $rc->destroy(); // Always need to destroy.
 
         // Calculate if we have perms to create users, by checking:
         // to 'moodle/restore:createuser' and 'moodle/restore:userinfo'
index 4096371..a5a7faa 100644 (file)
@@ -302,6 +302,7 @@ abstract class backup_helper {
                     $bc = backup_controller::load_controller($backupid);
                     $bc->log('Attempt to copy backup file to the specified directory using filesystem failed - ',
                             backup::LOG_WARNING, $dir);
+                    $bc->destroy();
                 }
                 // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
             }
index ed2125c..32b0c06 100644 (file)
@@ -71,6 +71,30 @@ abstract class base_logger implements checksumable {
         return $this->level;
     }
 
+    /**
+     * Destroy (nullify) the chain of loggers references, also closing resources when needed.
+     *
+     * @since Moodle 3.1
+     */
+    public final function destroy() {
+        // Recursively destroy the chain.
+        if ($this->next !== null) {
+            $this->next->destroy();
+            $this->next = null;
+        }
+        // And close every logger.
+        $this->close();
+    }
+
+    /**
+     * Close any resource the logger may have open.
+     *
+     * @since Moodle 3.1
+     */
+    public function close() {
+        // Nothing to do by default. Only loggers using resources (files, own connections...) need to override this.
+    }
+
 // checksumable interface methods
 
     public function calculate_checksum() {
index 5c05380..98ff74a 100644 (file)
@@ -66,6 +66,19 @@ class file_logger extends base_logger {
         }
     }
 
+    /**
+     * Close the logger resources (file handle) if still open.
+     *
+     * @since Moodle 3.1
+     */
+    public function close() {
+        // Close the file handle if hasn't been closed already.
+        if (is_resource($this->fhandle)) {
+            fclose($this->fhandle);
+            $this->fhandle = null;
+        }
+    }
+
 // Protected API starts here
 
     protected function action($message, $level, $options = null) {
index 67f8e74..70a65f9 100644 (file)
@@ -108,6 +108,16 @@ class backup_logger_testcase extends basic_testcase {
         $this->assertEquals($lo1->get_levelstr(backup::LOG_WARNING), 'warn');
         $this->assertEquals($lo1->get_levelstr(backup::LOG_INFO), 'info');
         $this->assertEquals($lo1->get_levelstr(backup::LOG_DEBUG), 'debug');
+
+        // Test destroy.
+        $lo1 = new mock_base_logger1(backup::LOG_ERROR);
+        $lo2 = new mock_base_logger2(backup::LOG_ERROR);
+        $lo1->set_next($lo2);
+        $this->assertInstanceOf('base_logger', $lo1->get_next());
+        $this->assertNull($lo2->get_next());
+        $lo1->destroy();
+        $this->assertNull($lo1->get_next());
+        $this->assertNull($lo2->get_next());
     }
 
     /**
@@ -249,9 +259,9 @@ class backup_logger_testcase extends basic_testcase {
         $result = $lo2->process($message2, backup::LOG_WARNING, $options);
         $this->assertTrue($result);
 
-        // Destruct loggers
-        $lo1 = null;
-        $lo2 = null;
+        // Destroy loggers.
+        $lo1->destroy();
+        $lo2->destroy();
 
         // Load file results to analyze them
         $fcontents = file_get_contents($file);
@@ -275,6 +285,7 @@ class backup_logger_testcase extends basic_testcase {
         $this->assertTrue(file_exists($file));
         $message = 'testing file_logger';
         $result = $lo->process($message, backup::LOG_ERROR, $options);
+        $lo->close(); // Closes logger.
         // Get file contents and inspect them
         $fcontents = file_get_contents($file);
         $this->assertTrue($result);
@@ -282,7 +293,6 @@ class backup_logger_testcase extends basic_testcase {
         $this->assertTrue(strpos($fcontents, '[error]') !== false);
         $this->assertTrue(strpos($fcontents, '&nbsp;&nbsp;') !== false);
         $this->assertTrue(substr_count($fcontents , '] ') >= 2);
-        $lo->__destruct(); // closes file handle
         unlink($file); // delete file
 
         // Instantiate, write something, force deletion, try to write again
@@ -292,7 +302,8 @@ class backup_logger_testcase extends basic_testcase {
         $this->assertTrue(file_exists($file));
         $message = 'testing file_logger';
         $result = $lo->process($message, backup::LOG_ERROR);
-        fclose($lo->get_fhandle()); // close file
+        $lo->close();
+        $this->assertNull($lo->get_fhandle());
         try {
             $result = @$lo->process($message, backup::LOG_ERROR); // Try to write again
             $this->assertTrue(false, 'base_logger_exception expected');
@@ -326,6 +337,7 @@ class backup_logger_testcase extends basic_testcase {
         $lo = new file_logger(backup::LOG_NONE, true, true, $file);
         $this->assertTrue($lo instanceof file_logger);
         $this->assertFalse(file_exists($file));
+        $lo->close();
 
         // Remove the test dir and any content
         @remove_dir(dirname($file));
index bfc98f3..3e9b2fe 100644 (file)
@@ -89,6 +89,8 @@ class backup_plan_testcase extends advanced_testcase {
         // Calculate checksum and check it
         $checksum = $bp->calculate_checksum();
         $this->assertTrue($bp->is_checksum_correct($checksum));
+
+        $bc->destroy();
     }
 
     /**
index 826b349..0b1ba6d 100644 (file)
@@ -88,6 +88,7 @@ class backup_step_testcase extends advanced_testcase {
         $this->assertTrue($bs instanceof backup_step);
         $this->assertEquals($bs->get_name(), 'stepname');
 
+        $bc->destroy();
     }
 
     /**
@@ -128,6 +129,8 @@ class backup_step_testcase extends advanced_testcase {
         $this->assertTrue(strpos($contents, '<field2>value2</field2>') !== false);
         $this->assertTrue(strpos($contents, '</test>') !== false);
 
+        $bc->destroy();
+
         unlink($file); // delete file
 
         // Remove the test dir and any content
index efc1b60..40ee6ea 100644 (file)
@@ -93,6 +93,7 @@ class backup_task_testcase extends advanced_testcase {
         $checksum = $bt->calculate_checksum();
         $this->assertTrue($bt->is_checksum_correct($checksum));
 
+        $bc->destroy();
     }
 
     /**
index 6e1be22..4c2a0ff 100644 (file)
@@ -45,13 +45,7 @@ function core_badges_myprofile_navigation(\core_user\output\myprofile\tree $tree
     // Add category. This node should appear after 'contact' so that administration block appears towards the end. Refer MDL-49928.
     $category = new core_user\output\myprofile\category('badges', get_string('badges', 'badges'), 'contact');
     $tree->add_category($category);
-
-    // Determine context.
-    if (isloggedin()) {
-        $context = context_user::instance($USER->id);
-    } else {
-        $context = context_system::instance();
-    }
+    $context = context_user::instance($user->id);
     $courseid = empty($course) ? 0 : $course->id;
 
     if ($USER->id == $user->id || has_capability('moodle/badges:viewotherbadges', $context)) {
@@ -78,4 +72,4 @@ function core_badges_myprofile_navigation(\core_user\output\myprofile\tree $tree
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/blocks/badges/tests/behat/block_badges.feature b/blocks/badges/tests/behat/block_badges.feature
new file mode 100644 (file)
index 0000000..e9da78a
--- /dev/null
@@ -0,0 +1,34 @@
+@block @block_badges
+Feature: Enable Block Badges in a course without badges
+  In order to view the badges block in a course
+  As a teacher
+  I can add badges block to a course and view the contents
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+
+  Scenario: Add the block to a the course when badges are disabled
+    Given I log in as "admin"
+    And the following config values are set as admin:
+      | enablebadges | 0 |
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Latest badges" block
+    Then I should see "Badges are not enabled on this site." in the "Latest badges" "block"
+
+  Scenario: Add the block to a the course when badges are enabled
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Latest badges" block
+    Then I should see "You have no badges to display" in the "Latest badges" "block"
diff --git a/blocks/badges/tests/behat/block_badges_course.feature b/blocks/badges/tests/behat/block_badges_course.feature
new file mode 100644 (file)
index 0000000..75942ff
--- /dev/null
@@ -0,0 +1,73 @@
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges in a course
+  In order to enable the badges block in a course
+  As a teacher
+  I can add badges block to a course
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    # Issue badge 1 of 2
+    And I navigate to "Add a new badge" node in "Badges"
+    And I set the following fields to these values:
+      | id_name | Badge 1 |
+      | id_description | Badge 1 |
+      | id_issuername | Teacher 1 |
+    And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I select "Manual issue by role" from the "Add badge criteria" singleselect
+    And I set the field "Teacher" to "1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I follow "Recipients (0)"
+    And I press "Award badge"
+    And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+    And I press "Award badge"
+    # Issue Badge 2 of 2
+    And I navigate to "Add a new badge" node in "Badges"
+    And I set the following fields to these values:
+      | id_name | Badge 2 |
+      | id_description | Badge 2 |
+      | id_issuername | Teacher 1 |
+    And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I select "Manual issue by role" from the "Add badge criteria" singleselect
+    And I set the field "Teacher" to "1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I follow "Recipients (0)"
+    And I press "Award badge"
+    And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+    And I press "Award badge"
+    And I log out
+
+  Scenario: Add the recent badges block to a course.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Latest badges" block
+    Then I should see "Badge 1" in the "Latest badges" "block"
+    And I should see "Badge 2" in the "Latest badges" "block"
+
+  Scenario: Add the recent badges block to a course and limit it to only display 1 badge.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Latest badges" block
+    And I configure the "Latest badges" block
+    And I set the following fields to these values:
+      | id_config_numberofbadges | 1 |
+    And I press "Save changes"
+    Then I should see "Badge 2" in the "Latest badges" "block"
+    And I should not see "Badge 1" in the "Latest badges" "block"
diff --git a/blocks/badges/tests/behat/block_badges_dashboard.feature b/blocks/badges/tests/behat/block_badges_dashboard.feature
new file mode 100644 (file)
index 0000000..6bc3d1f
--- /dev/null
@@ -0,0 +1,39 @@
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the dashboard and view awarded badges
+  In order to view recent badges on the dashboard
+  As a teacher
+  I can add badges block to the dashboard
+
+  Scenario: Add the recent badges block to a course.
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    # Issue badge 1 of 2
+    And I navigate to "Add a new badge" node in "Badges"
+    And I set the following fields to these values:
+      | id_name | Badge 1 |
+      | id_description | Badge 1 |
+      | id_issuername | Teacher 1 |
+    And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I select "Manual issue by role" from the "Add badge criteria" singleselect
+    And I set the field "Teacher" to "1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I follow "Recipients (0)"
+    And I press "Award badge"
+    And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+    And I press "Award badge"
+    And I log out
+    When I log in as "teacher1"
+    And I click on "Dashboard" "link" in the "Navigation" "block"
+    Then I should see "Badge 1" in the "Latest badges" "block"
diff --git a/blocks/badges/tests/behat/block_badges_frontpage.feature b/blocks/badges/tests/behat/block_badges_frontpage.feature
new file mode 100644 (file)
index 0000000..8b40357
--- /dev/null
@@ -0,0 +1,44 @@
+@block @block_badges @core_badges @_file_upload @javascript
+Feature: Enable Block Badges on the frontpage and view awarded badges
+  In order to enable the badges block on the frontpage
+  As a admin
+  I can add badges block to the frontpage
+
+  Scenario: Add the recent badges block on the frontpage and view recent badges
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    And I log in as "admin"
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    And I add the "Latest badges" block
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    # Issue badge 1 of 2
+    And I navigate to "Add a new badge" node in "Badges"
+    And I set the following fields to these values:
+      | id_name | Badge 1 |
+      | id_description | Badge 1 |
+      | id_issuername | Teacher 1 |
+    And I upload "blocks/badges/tests/fixtures/badge.png" file to "Image" filemanager
+    And I press "Create badge"
+    And I select "Manual issue by role" from the "Add badge criteria" singleselect
+    And I set the field "Teacher" to "1"
+    And I press "Save"
+    And I press "Enable access"
+    And I press "Continue"
+    And I follow "Recipients (0)"
+    And I press "Award badge"
+    And I set the field "potentialrecipients[]" to "Teacher 1 (teacher1@example.com)"
+    And I press "Award badge"
+    And I log out
+    When I log in as "teacher1"
+    And I am on site homepage
+    Then I should see "Badge 1" in the "Latest badges" "block"
diff --git a/blocks/badges/tests/fixtures/badge.png b/blocks/badges/tests/fixtures/badge.png
new file mode 100644 (file)
index 0000000..73f2c07
Binary files /dev/null and b/blocks/badges/tests/fixtures/badge.png differ
diff --git a/blocks/calendar_month/tests/behat/block_calendar_month.feature b/blocks/calendar_month/tests/behat/block_calendar_month.feature
new file mode 100644 (file)
index 0000000..1dd28e9
--- /dev/null
@@ -0,0 +1,197 @@
+@block @block_calendar_month
+Feature: Enable the calendar block in a course and test it's functionality
+  In order to enable the calendar block in a course
+  As a teacher
+  I can add the calendar block to a course
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+      | student1 | Student | 1 | student1@example.com | S1 |
+      | student2 | Student | 2 | student2@example.com | S2 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+
+  Scenario: Add the block to a the course
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Calendar" block
+    Then I should see "Events key" in the "Calendar" "block"
+
+  @javascript
+  Scenario: View a global event in the calendar block
+    Given I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | Site Event |
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I hover over today in the calendar
+    Then I should see "Site Event"
+
+  @javascript
+  Scenario: Filter site events in the calendar block
+    Given I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | Site Event |
+    And I log out
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Course |
+      | id_name | Course Event |
+    And I follow "Course 1"
+    And I follow "Hide global events"
+    And I hover over today in the calendar
+    Then I should not see "Site Event"
+    And I should see "Course Event"
+
+  @javascript
+  Scenario: View a course event in the calendar block
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Course |
+      | id_name | Course Event |
+    When I follow "Course 1"
+    And I hover over today in the calendar
+    Then I should see "Course Event"
+
+  @javascript
+  Scenario: Filter course events in the calendar block
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Course |
+      | id_name | Course Event |
+    And I follow "Course 1"
+    And I create a calendar event with form data:
+      | id_eventtype | User |
+      | id_name | User Event |
+    When I click on "Dashboard" "link" in the "Navigation" "block"
+    And I follow "Course 1"
+    And I follow "Hide course events"
+    And I hover over today in the calendar
+    Then I should not see "Course Event"
+    And I should see "User Event"
+
+  @javascript
+  Scenario: View a user event in the calendar block
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | User |
+      | id_name | User Event |
+    When I click on "Dashboard" "link" in the "Navigation" "block"
+    And I follow "Course 1"
+    And I hover over today in the calendar
+    Then I should see "User Event"
+
+  @javascript
+  Scenario: Filter user events in the calendar block
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Course |
+      | id_name | Course Event |
+    And I follow "Course 1"
+    And I create a calendar event with form data:
+      | id_eventtype | User |
+      | id_name | User Event |
+    When I click on "Dashboard" "link" in the "Navigation" "block"
+    And I follow "Course 1"
+    And I follow "Hide user events"
+    And I hover over today in the calendar
+    Then I should not see "User Event"
+    And I should see "Course Event"
+
+  @javascript
+  Scenario: View a group event in the calendar block
+    Given the following "groups" exist:
+      | name    | course | idnumber |
+      | Group 1 | C1     | G1       |
+      | Group 2 | C1     | G2       |
+    And the following "group members" exist:
+      | user     | group   |
+      | student1 | G1 |
+      | student2 | G2 |
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I set the following fields to these values:
+      | id_groupmode | Separate groups |
+      | id_groupmodeforce | Yes |
+    And I press "Save and display"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Group |
+      | id_groupid | Group 1 |
+      | id_name | Group Event |
+    And I log out
+    Then I log in as "student1"
+    And I follow "Course 1"
+    And I hover over today in the calendar
+    And I should see "Group Event"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I hover over today in the calendar
+    And I should not see "Group Event"
+
+  @javascript
+  Scenario: Filter group events in the calendar block
+    Given the following "groups" exist:
+      | name    | course | idnumber |
+      | Group 1 | C1     | G1       |
+      | Group 2 | C1     | G2       |
+    And the following "group members" exist:
+      | user     | group   |
+      | student1 | G1 |
+      | student2 | G2 |
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I set the following fields to these values:
+      | id_groupmode | Separate groups |
+      | id_groupmodeforce | Yes |
+    And I press "Save and display"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I create a calendar event with form data:
+      | id_eventtype | Course |
+      | id_name | Course Event 1 |
+    And I follow "Course 1"
+    And I create a calendar event with form data:
+      | id_eventtype | Group |
+      | id_groupid | Group 1 |
+      | id_name | Group Event 1 |
+    And I log out
+    Then I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Hide group events"
+    And I hover over today in the calendar
+    And I should not see "Group Event 1"
+    And I should see "Course Event 1"
diff --git a/blocks/calendar_month/tests/behat/block_calendar_month_course.feature b/blocks/calendar_month/tests/behat/block_calendar_month_course.feature
new file mode 100644 (file)
index 0000000..1c59a60
--- /dev/null
@@ -0,0 +1,28 @@
+@block @block_calendar_month
+Feature: Enable the calendar block in a course
+  In order to enable the calendar block in a course
+  As a teacher
+  I can add the calendar block to a course
+
+  @javascript
+  Scenario: View a global event in the calendar block in a course
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    When I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | Site Event |
+    And I log out
+    Then I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Calendar" block
+    And I hover over today in the calendar
+    And I should see "Site Event"
diff --git a/blocks/calendar_month/tests/behat/block_calendar_month_dashboard.feature b/blocks/calendar_month/tests/behat/block_calendar_month_dashboard.feature
new file mode 100644 (file)
index 0000000..aa74c43
--- /dev/null
@@ -0,0 +1,19 @@
+@block @block_calendar_month
+Feature: View a site event on the dashboard
+  In order to view a site event
+  As a student
+  I can view the event in the calendar
+
+  @javascript
+  Scenario: View a global event in the calendar block on the dashboard
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | Site Event |
+    And I log out
+    When I log in as "student1"
+    And I hover over today in the calendar
+    Then I should see "Site Event"
diff --git a/blocks/calendar_month/tests/behat/block_calendar_month_frontpage.feature b/blocks/calendar_month/tests/behat/block_calendar_month_frontpage.feature
new file mode 100644 (file)
index 0000000..ae09aa8
--- /dev/null
@@ -0,0 +1,21 @@
+@block @block_calendar_month
+Feature: Enable the calendar block on the site front page
+  In order to enable the calendar block on the site front page
+  As an admin
+  I can add the calendar block on the site front page
+
+  @javascript
+  Scenario: View a global event in the calendar block on the front page
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And I log in as "admin"
+    And I am on site homepage
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | Site Event |
+    And I log out
+    When I log in as "student1"
+    And I am on site homepage
+    And I hover over today in the calendar
+    Then I should see "Site Event"
diff --git a/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_course.feature b/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_course.feature
new file mode 100644 (file)
index 0000000..d517c5f
--- /dev/null
@@ -0,0 +1,23 @@
+@block  @block_calendar_upcoming
+Feature: Enable the upcoming events block in a course
+  In order to enable the calendar block in a course
+  As a teacher
+  I can view the event in the upcoming events block
+
+  Scenario: View a global event in the upcoming events block in a course
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+    When I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | My Site Event |
+    And I log out
+    When I log in as "teacher1"
+    Then I should see "My Site Event" in the "Upcoming events" "block"
diff --git a/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_dashboard.feature b/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_dashboard.feature
new file mode 100644 (file)
index 0000000..ea2bcd1
--- /dev/null
@@ -0,0 +1,17 @@
+@block @block_calendar_upcoming
+Feature: View a site event on the dashboard
+  In order to view a site event
+  As a student
+  I can view the event in the upcoming events block
+
+  Scenario: View a global event in the upcoming events block on the dashboard
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | My Site Event |
+    And I log out
+    When I log in as "student1"
+    Then I should see "My Site Event"
diff --git a/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_frontpage.feature b/blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_frontpage.feature
new file mode 100644 (file)
index 0000000..a101952
--- /dev/null
@@ -0,0 +1,21 @@
+@block @block_calendar_upcoming
+Feature: View a site event on the frontpage
+  In order to view a site event
+  As a teacher
+  I can view the event in the upcoming events block
+
+  Scenario: View a global event in the upcoming events block on the frontpage
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And I log in as "admin"
+    And I create a calendar event with form data:
+      | id_eventtype | Site |
+      | id_name | My Site Event |
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    And I add the "Upcoming events" block
+    And I log out
+    When I log in as "teacher1"
+    And I am on site homepage
+    Then I should see "My Site Event" in the "Upcoming events" "block"
diff --git a/blocks/comments/tests/behat/block_comment_activity.feature b/blocks/comments/tests/behat/block_comment_activity.feature
new file mode 100644 (file)
index 0000000..d2a3180
--- /dev/null
@@ -0,0 +1,34 @@
+@block @block_comments
+Feature: Enable Block comments on an activity page and view comments
+  In order to enable the comments block on an activity page
+  As a teacher
+  I can add the comments block to an activity page
+
+  Scenario: Add the comments block on an activity page and add comments
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | Frist | teacher1@example.com |
+      | student1 | Student | First | student1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And the following "activities" exist:
+      | activity | course | idnumber | name           | intro                 |
+      | page    | C1      | page1    | Test page name | Test page description |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I follow "Test page name"
+    And I add the "Comments" block
+    And I follow "Show comments"
+    And I add "I'm a comment from the teacher" comment to comments block
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test page name"
+    And I follow "Show comments"
+    Then I should see "I'm a comment from the teacher"
diff --git a/blocks/comments/tests/behat/block_comment_course.feature b/blocks/comments/tests/behat/block_comment_course.feature
new file mode 100644 (file)
index 0000000..c3acbc4
--- /dev/null
@@ -0,0 +1,29 @@
+@block @block_comments
+Feature: Enable Block comments on a course page and view comments
+  In order to enable the comments block on a course page
+  As a teacher
+  I can add the comments block to the course page
+
+  Scenario: Add the comments block on the course page and add comments
+    Given the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | Frist | teacher1@example.com |
+      | student1 | Student | First | student1@example.com |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Comments" block
+    And I follow "Show comments"
+    And I add "I'm a comment from the teacher" comment to comments block
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Show comments"
+    Then I should see "I'm a comment from the teacher"
diff --git a/blocks/comments/tests/behat/block_comment_frontpage.feature b/blocks/comments/tests/behat/block_comment_frontpage.feature
new file mode 100644 (file)
index 0000000..5d949ab
--- /dev/null
@@ -0,0 +1,21 @@
+@block @block_comments
+Feature: Enable Block comments on the frontpage and view comments
+  In order to enable the comments block on the frontpage
+  As a admin
+  I can add the comments block to the frontpage
+
+  Scenario: Add the comments block on the frontpage and add comments
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+    And I log in as "admin"
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    And I add the "Comments" block
+    And I follow "Show comments"
+    And I add "I'm a comment from admin" comment to comments block
+    And I log out
+    When I log in as "teacher1"
+    And I am on site homepage
+    And I follow "Show comments"
+    Then I should see "I'm a comment from admin"
diff --git a/blocks/completionstatus/tests/behat/block_completionstatus.feature b/blocks/completionstatus/tests/behat/block_completionstatus.feature
new file mode 100644 (file)
index 0000000..58b7cc7
--- /dev/null
@@ -0,0 +1,57 @@
+@block @block_completionstatus
+Feature: Enable Block Completion in a course
+  In order to view the completion block in a course
+  As a teacher
+  I can add completion block to a course
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And the following "courses" exist:
+      | fullname | shortname | category | enablecompletion |
+      | Course 1 | C1        | 0        | 1                |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | student1 | C1     | student        |
+
+  Scenario: Add the block to a the course where completion is disabled
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I navigate to "Edit settings" node in "Course administration"
+    And I set the following fields to these values:
+      | Enable completion tracking | No |
+    And I press "Save and display"
+    When I add the "Course completion status" block
+    Then I should see "Completion is not enabled for this course" in the "Course completion status" "block"
+
+  Scenario: Add the block to a the course where completion is not set
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    When I add the "Course completion status" block
+    Then I should see "No completion criteria set for this course" in the "Course completion status" "block"
+
+  Scenario: Add the block to a course with criteria and view as an untracked role.
+    Given the following "activities" exist:
+      | activity | course | idnumber | name           | intro                 |
+      | page     | C1     | page1    | Test page name | Test page description |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I follow "Test page name"
+    And I navigate to "Edit settings" node in "Page module administration"
+    And I set the following fields to these values:
+      | Completion tracking | Show activity as complete when conditions are met |
+      | Require view | 1 |
+    And I press "Save and return to course"
+    When I add the "Course completion status" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Test page name | 1 |
+    And I press "Save changes"
+    Then I should see "You are currently not being tracked by completion in this course" in the "Course completion status" "block"
diff --git a/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature b/blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature
new file mode 100644 (file)
index 0000000..7d3285f
--- /dev/null
@@ -0,0 +1,73 @@
+@block @block_completionstatus
+Feature: Enable Block Completion in a course using activity completion
+  In order to view the completion block in a course
+  As a teacher
+  I can add completion block to a course and set up activity completion
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And the following "courses" exist:
+      | fullname | shortname | category | enablecompletion |
+      | Course 1 | C1        | 0        | 1                |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | student1 | C1     | student        |
+    And the following "activities" exist:
+      | activity | course | idnumber | name           | intro                 |
+      | page     | C1     | page1    | Test page name | Test page description |
+
+  Scenario: Add the block to a the course and add course completion items
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I follow "Test page name"
+    And I navigate to "Edit settings" node in "Page module administration"
+    And I set the following fields to these values:
+      | Completion tracking | Show activity as complete when conditions are met |
+      | Require view | 1 |
+    And I press "Save and return to course"
+    And I add the "Course completion status" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Test page name | 1 |
+    And I press "Save changes"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "Status: Not yet started" in the "Course completion status" "block"
+    And I should see "0 of 1" in the "Activity completion" "table_row"
+
+  Scenario: Add the block to a the course and add course completion items
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I follow "Test page name"
+    And I navigate to "Edit settings" node in "Page module administration"
+    And I set the following fields to these values:
+      | Completion tracking | Show activity as complete when conditions are met |
+      | Require view | 1 |
+    And I press "Save and return to course"
+    And I add the "Course completion status" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Test page name | 1 |
+    And I press "Save changes"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test page name"
+    And I follow "C1"
+    Then I should see "Status: Pending" in the "Course completion status" "block"
+    And I should see "0 of 1" in the "Activity completion" "table_row"
+    And I trigger cron
+    And I am on site homepage
+    And I follow "Course 1"
+    And I should see "1 of 1" in the "Activity completion" "table_row"
+    And I follow "More details"
+    And I should see "Yes" in the "Activity completion" "table_row"
diff --git a/blocks/completionstatus/tests/behat/block_completionstatus_manual_other.feature b/blocks/completionstatus/tests/behat/block_completionstatus_manual_other.feature
new file mode 100644 (file)
index 0000000..1aaed0a
--- /dev/null
@@ -0,0 +1,100 @@
+@block @block_completionstatus
+Feature: Enable Block Completion in a course using manual completion by others
+  In order to view the completion block in a course
+  As a teacher
+  I can add completion block to a course and set up manual completion by others
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+      | teacher2 | Teacher | 2 | teacher1@example.com | T2 |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And the following "courses" exist:
+      | fullname | shortname | category | enablecompletion |
+      | Course 1 | C1        | 0        | 1                |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | teacher2 | C1     | teacher        |
+      | student1 | C1     | student        |
+
+  Scenario: Add the block to a the course and mark a student complete.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Course completion status" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Teacher | 1 |
+    And I press "Save changes"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I should see "Status: Not yet started" in the "Course completion status" "block"
+    And I should see "No" in the "Teacher" "table_row"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Course completion" node in "Reports"
+    And I follow "Click to mark user complete"
+    And I trigger cron
+    And I am on site homepage
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "Status: Complete" in the "Course completion status" "block"
+    And I should see "Yes" in the "Teacher" "table_row"
+    And I follow "More details"
+    And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+
+
+  Scenario: Add the block to a the course and require multiple roles to mark a student complete.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Course completion status" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | Teacher             | 1 |
+      | Non-editing teacher | 1 |
+      | id_role_aggregation | ALL selected roles to mark when the condition is met |
+    And I press "Save changes"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I should see "Status: Not yet started" in the "Course completion status" "block"
+    And I should see "No" in the "Teacher" "table_row"
+    And I should see "No" in the "Non-editing teacher" "table_row"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Course completion" node in "Reports"
+    And I follow "Click to mark user complete"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I should see "Status: In progress" in the "Course completion status" "block"
+    And I should see "Yes" in the "Teacher" "table_row"
+    And I should see "No" in the "Non-editing teacher" "table_row"
+    And I follow "More details"
+    And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+    And I should see "No" in the "Marked complete by Non-editing teacher" "table_row"
+    And I log out
+    And I log in as "teacher2"
+    And I follow "Course 1"
+    And I navigate to "Course completion" node in "Reports"
+    And I follow "Click to mark user complete"
+    And I trigger cron
+    And I am on site homepage
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "Status: Complete" in the "Course completion status" "block"
+    And I should see "Yes" in the "Teacher" "table_row"
+    And I should see "Yes" in the "Non-editing teacher" "table_row"
+    And I follow "More details"
+    And I should see "Yes" in the "Marked complete by Teacher" "table_row"
+    And I should see "Yes" in the "Marked complete by Non-editing teacher" "table_row"
diff --git a/blocks/completionstatus/tests/behat/block_completionstatus_manual_self.feature b/blocks/completionstatus/tests/behat/block_completionstatus_manual_self.feature
new file mode 100644 (file)
index 0000000..8441d37
--- /dev/null
@@ -0,0 +1,44 @@
+@block @block_completionstatus @block_selfcompletion
+Feature: Enable Block Completion in a course using manual self completion
+  In order to view the completion block in a course
+  As a teacher
+  I can add completion block to a course and set up manual self completion
+
+  Scenario: Add the block to a the course and manually complete the course
+    Given the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+      | student1 | Student | 1 | student1@example.com | S1 |
+    And the following "courses" exist:
+      | fullname | shortname | category | enablecompletion |
+      | Course 1 | C1        | 0        | 1                |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+      | student1 | C1     | student        |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Course completion status" block
+    And I add the "Self completion" block
+    And I navigate to "Course completion" node in "Course administration"
+    And I expand all fieldsets
+    And I set the following fields to these values:
+      | id_criteria_self | 1 |
+    And I press "Save changes"
+    And I log out
+    When I log in as "student1"
+    And I follow "Course 1"
+    And I should see "Status: Not yet started" in the "Course completion status" "block"
+    And I should see "No" in the "Self completion" "table_row"
+    And I follow "Complete course"
+    And I should see "Confirm self completion"
+    And I press "Yes"
+    And I should see "Status: In progress" in the "Course completion status" "block"
+    And I trigger cron
+    And I am on site homepage
+    And I follow "Course 1"
+    Then I should see "Status: Complete" in the "Course completion status" "block"
+    And I should see "Yes" in the "Self completion" "table_row"
+    And I follow "More details"
+    And I should see "Yes" in the "Self completion" "table_row"
diff --git a/blocks/globalsearch/styles.css b/blocks/globalsearch/styles.css
new file mode 100644 (file)
index 0000000..b8b5c69
--- /dev/null
@@ -0,0 +1,2 @@
+.block_globalsearch .searchform {text-align: center;}
+.block_globalsearch .footer {text-align: center;}
index 43e8378..9038d47 100644 (file)
@@ -39,7 +39,7 @@
     <tbody>
         {{#competencies}}
         <tr>
-            <td><a href="{{usercompetency.url}}">{{competency.shortname}}</a></td>
+            <td><a href="{{usercompetency.url}}">{{{competency.shortname}}}</a></td>
             <td>{{user.fullname}}</td>
             <td>{{usercompetency.statusname}}</td>
         </tr>
index 9267a35..124dcd3 100644 (file)
@@ -39,7 +39,7 @@
     <tbody>
         {{#plans}}
         <tr>
-            <td><a href="{{plan.url}}">{{plan.name}}</a></td>
+            <td><a href="{{plan.url}}">{{{plan.name}}}</a></td>
             <td>{{user.fullname}}</td>
             <td>{{plan.statusname}}</td>
         </tr>
index cc25143..8ad8aa4 100644 (file)
@@ -42,7 +42,7 @@
             {{#hasactiveplans}}
                 <ul>
                     {{#activeplans}}
-                        <li><a href="{{url}}">{{name}}</a></li>
+                        <li><a href="{{url}}">{{{name}}}</a></li>
                     {{/activeplans}}
                     {{#hasmoreplans}}
                         <li class="more"><a href="{{plansurl}}">{{#str}}viewmore, block_lp{{/str}}</a></li>
@@ -60,7 +60,7 @@
             <ul>
                 {{#compstoreview}}
                     <li>
-                        <a href="{{usercompetency.url}}">{{competency.shortname}}</a> ({{user.fullname}}) - {{usercompetency.statusname}}
+                        <a href="{{usercompetency.url}}">{{{competency.shortname}}}</a> ({{user.fullname}}) - {{usercompetency.statusname}}
                     </li>
                 {{/compstoreview}}
                 {{#hasmorecompstoreview}}
@@ -75,7 +75,7 @@
             <ul>
                 {{#planstoreview}}
                     <li>
-                        <a href="{{plan.url}}">{{plan.name}}</a> ({{user.fullname}}) - {{plan.statusname}}
+                        <a href="{{plan.url}}">{{{plan.name}}}</a> ({{user.fullname}}) - {{plan.statusname}}
                     </li>
                 {{/planstoreview}}
                 {{#hasmoreplanstoreview}}
index 866047d..26bbf00 100644 (file)
@@ -75,7 +75,7 @@ class block_site_main_menu extends block_list {
                         $content = $cm->get_formatted_content(array('overflowdiv' => true, 'noclean' => true));
                     }
 
-                    $this->content->items[] = $indent . $content;
+                    $this->content->items[] = $indent . html_writer::div($content, 'main-menu-content');
                 }
             }
             return $this->content;
@@ -142,7 +142,7 @@ class block_site_main_menu extends block_list {
                     } else {
                         $content = html_writer::div($courserenderer->course_section_cm_name($mod), ' activity');
                     }
-                    $this->content->items[] = $indent. $content . $editbuttons;
+                    $this->content->items[] = $indent . html_writer::div($content . $editbuttons, 'main-menu-content');
                 }
             }
         }
index f437bae..6d43c31 100644 (file)
@@ -1,9 +1,13 @@
 .block_site_main_menu li { clear: both; }
-.block_site_main_menu li .column { width: 100%; }
+.block_site_main_menu.block.list_block .unlist > li > .column {
+    /* Made specific to win over .block.list_block .unlist > li > .column. */
+    width: 100%;
+    display: table;
+}
 .block_site_main_menu li .buttons { float: right; margin: 0; }
 .dir-rtl .block_site_main_menu li .buttons { float: left; }
 .block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
 .block_site_main_menu .footer { margin-top: 1em; }
 .block_site_main_menu .section_add_menus noscript div { display: inline;}
 .block_site_main_menu .mod-indent,
-.block_site_main_menu .activity { display: table-cell; }
+.block_site_main_menu .main-menu-content { display: table-cell; }
index 05f193d..8af4927 100644 (file)
@@ -64,7 +64,7 @@ class competency extends persistent {
                 'type' => PARAM_TEXT
             ),
             'idnumber' => array(
-                'type' => PARAM_TEXT
+                'type' => PARAM_RAW
             ),
             'description' => array(
                 'default' => '',
index 5e51dcc..b1f323a 100644 (file)
@@ -87,7 +87,7 @@ class competency_framework extends persistent {
                 'type' => PARAM_TEXT
             ),
             'idnumber' => array(
-                'type' => PARAM_TEXT
+                'type' => PARAM_RAW
             ),
             'description' => array(
                 'type' => PARAM_RAW,
index d09b142..ab9bbbb 100644 (file)
@@ -69,30 +69,30 @@ class user_summary_exporter extends exporter {
     public static function define_properties() {
         return array(
             'id' => array(
-                'type' => PARAM_INT,
+                'type' => \core_user::get_property_type('id'),
             ),
             'email' => array(
-                'type' => PARAM_TEXT,
+                'type' => \core_user::get_property_type('email'),
                 'default' => ''
             ),
             'idnumber' => array(
-                'type' => PARAM_NOTAGS,
+                'type' => \core_user::get_property_type('idnumber'),
                 'default' => ''
             ),
             'phone1' => array(
-                'type' => PARAM_NOTAGS,
+                'type' => \core_user::get_property_type('phone1'),
                 'default' => ''
             ),
             'phone2' => array(
-                'type' => PARAM_NOTAGS,
+                'type' => \core_user::get_property_type('phone2'),
                 'default' => ''
             ),
             'department' => array(
-                'type' => PARAM_TEXT,
+                'type' => \core_user::get_property_type('department'),
                 'default' => ''
             ),
             'institution' => array(
-                'type' => PARAM_TEXT,
+                'type' => \core_user::get_property_type('institution'),
                 'default' => ''
             )
         );
@@ -101,10 +101,10 @@ class user_summary_exporter extends exporter {
     public static function define_other_properties() {
         return array(
             'fullname' => array(
-                'type' => PARAM_TEXT
+                'type' => PARAM_RAW
             ),
             'identity' => array(
-                'type' => PARAM_TEXT
+                'type' => PARAM_RAW
             ),
             'profileurl' => array(
                 'type' => PARAM_URL
index d45ec01..57a72ed 100644 (file)
@@ -71,7 +71,8 @@ class plan_competency extends persistent {
                   JOIN {' . self::TABLE . '} plancomp
                     ON plancomp.competencyid = comp.id
                  WHERE plancomp.planid = ?
-              ORDER BY plancomp.sortorder ASC';
+              ORDER BY plancomp.sortorder ASC,
+                       plancomp.id ASC';
         $params = array($planid);
 
         // TODO MDL-52229 Handle hidden competencies.
index 41019e2..62a1ca7 100644 (file)
@@ -202,7 +202,8 @@ class template_competency extends persistent {
                   JOIN {' . self::TABLE . '} tplcomp
                     ON tplcomp.competencyid = comp.id
                  WHERE tplcomp.templateid = ?
-              ORDER BY tplcomp.sortorder ASC';
+              ORDER BY tplcomp.sortorder ASC,
+                       tplcomp.id ASC';
         $params = array($templateid);
 
         $results = $DB->get_records_sql($sql, $params);
index f003d38..1cf1272 100644 (file)
@@ -508,7 +508,8 @@ class user_competency extends persistent {
             $sql = "competencyid $insql";
         }
 
-        return self::get_records_select("userid = :userid AND $sql", $params);
+        // Order by ID to prevent random ordering.
+        return self::get_records_select("userid = :userid AND $sql", $params, 'id ASC');
     }
 
     /**
index e731ca6..c139061 100644 (file)
@@ -227,7 +227,8 @@ class user_competency_course extends persistent {
             $sql = "competencyid $insql";
         }
 
-        return self::get_records_select("userid = :userid AND courseid = :courseid AND $sql", $params);
+        // Order by ID to prevent random ordering.
+        return self::get_records_select("userid = :userid AND courseid = :courseid AND $sql", $params, 'id ASC');
     }
 
     /**
index 81b7791..e493e5b 100644 (file)
@@ -189,7 +189,8 @@ class user_competency_plan extends persistent {
                     ON ucp.competencyid = c.id
                    AND ucp.userid = :userid
                  WHERE ucp.planid = :planid
-              ORDER BY ucp.sortorder ASC';
+              ORDER BY ucp.sortorder ASC,
+                       ucp.id ASC';
         $params = array('userid' => $userid, 'planid' => $planid);
 
         $results = $DB->get_recordset_sql($sql, $params);
@@ -258,7 +259,8 @@ class user_competency_plan extends persistent {
             $sql = "competencyid $insql";
         }
 
-        return static::get_records_select("userid = :userid AND planid = :planid AND $sql", $params);
+        // Order by ID to prevent random ordering.
+        return static::get_records_select("userid = :userid AND planid = :planid AND $sql", $params, 'id ASC');
     }
 
     /**
index d0f86eb..4ede634 100644 (file)
@@ -2686,17 +2686,17 @@ class core_competency_api_testcase extends advanced_testcase {
         $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
         $user = $dg->create_user();
 
-        $dg->create_scale(array("id" => "1", "scale" => "value1, value2"));
-        $dg->create_scale(array("id" => "2", "scale" => "value3, value4, value5, value6"));
+        $s1 = $dg->create_scale(array("scale" => "value1, value2"));
+        $s2 = $dg->create_scale(array("scale" => "value3, value4, value5, value6"));
 
-        $scaleconfiguration1 = '[{"scaleid":"1"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
+        $scaleconfiguration1 = '[{"scaleid":"'.$s1->id.'"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
                 '{"name":"value2","id":2,"scaledefault":0,"proficient":1}]';
-        $scaleconfiguration2 = '[{"scaleid":"2"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},'
+        $scaleconfiguration2 = '[{"scaleid":"'.$s2->id.'"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},'
                 . '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]';
 
         // Create a framework with scale configuration1.
         $frm = array(
-            'scaleid' => 1,
+            'scaleid' => $s1->id,
             'scaleconfiguration' => $scaleconfiguration1
         );
         $framework = $lpg->create_framework($frm);
@@ -2704,7 +2704,7 @@ class core_competency_api_testcase extends advanced_testcase {
 
         // Create competency with its own scale configuration.
         $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get_id(),
-                                            'scaleid' => 2,
+                                            'scaleid' => $s2->id,
                                             'scaleconfiguration' => $scaleconfiguration2
                                         ));
 
@@ -3961,7 +3961,6 @@ class core_competency_api_testcase extends advanced_testcase {
         $dg = $this->getDataGenerator();
         $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency');
 
-        $currenttime = time();
         $syscontext = context_system::instance();
 
         // Create users.
@@ -3979,10 +3978,8 @@ class core_competency_api_testcase extends advanced_testcase {
         $pc1 = $lpg->create_plan_competency(array('planid' => $p1->get_id(), 'competencyid' => $c1->get_id()));
         $pc2 = $lpg->create_plan_competency(array('planid' => $p2->get_id(), 'competencyid' => $c1->get_id()));
 
-        // Create user competency. Add user_evidence and associate it to the user competency.
+        // Create user competency and add an evidence.
         $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get_id()));
-        $ue = $lpg->create_user_evidence(array('userid' => $user->id));
-        $uec = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue->get_id(), 'competencyid' => $c1->get_id()));
         $e1 = $lpg->create_evidence(array('usercompetencyid' => $uc->get_id()));
 
         // Check both plans as one evidence.
@@ -3990,24 +3987,18 @@ class core_competency_api_testcase extends advanced_testcase {
         $this->assertEquals(1, count(api::list_evidence($user->id, $c1->get_id(), $p2->get_id())));
 
         // Complete second plan.
-        $currenttime += 1;
         $p2->set_status(plan::STATUS_COMPLETE);
         $p2->update();
-        $plansql = "UPDATE {" . plan::TABLE . "} SET timemodified = :currenttime WHERE id = :planid";
-        $DB->execute($plansql, array('currenttime' => $currenttime, 'planid' => $p2->get_id()));
 
-        // Add an other user evidence for the same competency.
-        $currenttime += 1;
-        $ue2 = $lpg->create_user_evidence(array('userid' => $user->id));
-        $uec2 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue2->get_id(), 'competencyid' => $c1->get_id()));
+        // Add another evidence for the same competency, but in the future (time + 1).
         $e2 = $lpg->create_evidence(array('usercompetencyid' => $uc->get_id()));
         $evidencesql = "UPDATE {" . evidence::TABLE . "} SET timecreated = :currenttime WHERE id = :evidenceid";
-        $DB->execute($evidencesql, array('currenttime' => $currenttime, 'evidenceid' => $e2->get_id()));
+        $DB->execute($evidencesql, array('currenttime' => time() + 1, 'evidenceid' => $e2->get_id()));
 
-        // Check first plan which is not completed as all evidences.
+        // Check that the first plan, which is not completed, has all the evidence.
         $this->assertEquals(2, count(api::list_evidence($user->id, $c1->get_id(), $p1->get_id())));
 
-        // Check second plan completed before the new evidence as only the first evidence.
+        // Check that the second plan, completed before the new evidence, only has the first piece of evidence.
         $listevidences = api::list_evidence($user->id, $c1->get_id(), $p2->get_id());
         $this->assertEquals(1, count($listevidences));
         $this->assertEquals($e1->get_id(), $listevidences[$e1->get_id()]->get_id());
index f280e5f..6be6bf0 100644 (file)
@@ -69,6 +69,18 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
     /** @var int User role id */
     protected $userrole = null;
 
+    /** @var stdClass $scale1 Scale */
+    protected $scale1 = null;
+
+    /** @var stdClass $scale2 Scale */
+    protected $scale2 = null;
+
+    /** @var stdClass $scale3 Scale */
+    protected $scale3 = null;
+
+    /** @var stdClass $scale4 Scale */
+    protected $scale4 = null;
+
     /** @var string scaleconfiguration */
     protected $scaleconfiguration1 = null;
 
@@ -153,31 +165,36 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->category = $category;
         $this->othercategory = $othercategory;
 
-        $this->getDataGenerator()->create_scale(array("id" => "1", "scale" => "value1, value2"));
-        $this->getDataGenerator()->create_scale(array("id" => "2", "scale" => "value3, value4"));
-        $this->getDataGenerator()->create_scale(array("id" => "3", "scale" => "value5, value6"));
-        $this->getDataGenerator()->create_scale(array("id" => "4", "scale" => "value7, value8"));
+        $this->scale1 = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2"));
+        $this->scale2 = $this->getDataGenerator()->create_scale(array("scale" => "value3, value4"));
+        $this->scale3 = $this->getDataGenerator()->create_scale(array("scale" => "value5, value6"));
+        $this->scale4 = $this->getDataGenerator()->create_scale(array("scale" => "value7, value8"));
 
-        $this->scaleconfiguration1 = '[{"scaleid":"1"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
+        $this->scaleconfiguration1 = '[{"scaleid":"'.$this->scale1->id.'"},' .
+                '{"name":"value1","id":1,"scaledefault":1,"proficient":0},' .
                 '{"name":"value2","id":2,"scaledefault":0,"proficient":1}]';
-        $this->scaleconfiguration2 = '[{"scaleid":"2"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},' .
+        $this->scaleconfiguration2 = '[{"scaleid":"'.$this->scale2->id.'"},' .
+                '{"name":"value3","id":1,"scaledefault":1,"proficient":0},' .
                 '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]';
-        $this->scaleconfiguration3 = '[{"scaleid":"3"},{"name":"value5","id":1,"scaledefault":1,"proficient":0},' .
+        $this->scaleconfiguration3 = '[{"scaleid":"'.$this->scale3->id.'"},' .
+                '{"name":"value5","id":1,"scaledefault":1,"proficient":0},' .
                 '{"name":"value6","id":2,"scaledefault":0,"proficient":1}]';
-        $this->scaleconfiguration4 = '[{"scaleid":"4"},{"name":"value8","id":1,"scaledefault":1,"proficient":0},' .
+        $this->scaleconfiguration4 = '[{"scaleid":"'.$this->scale4->id.'"},'.
+                '{"name":"value8","id":1,"scaledefault":1,"proficient":0},' .
                 '{"name":"value8","id":2,"scaledefault":0,"proficient":1}]';
         accesslib_clear_all_caches_for_unit_testing();
     }
 
 
     protected function create_competency_framework($number = 1, $system = true) {
+        $scalename = 'scale' . $number;
         $scalepropname = 'scaleconfiguration' . $number;
         $framework = array(
             'shortname' => 'shortname' . $number,
             'idnumber' => 'idnumber' . $number,
             'description' => 'description' . $number,
             'descriptionformat' => FORMAT_HTML,
-            'scaleid' => $number,
+            'scaleid' => $this->$scalename->id,
             'scaleconfiguration' => $this->$scalepropname,
             'visible' => true,
             'contextid' => $system ? context_system::instance()->id : context_coursecat::instance($this->category->id)->id
@@ -241,6 +258,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
     }
 
     protected function update_competency_framework($id, $number = 1, $system = true) {
+        $scalename = 'scale' . $number;
         $scalepropname = 'scaleconfiguration' . $number;
         $framework = array(
             'id' => $id,
@@ -248,7 +266,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
             'idnumber' => 'idnumber' . $number,
             'description' => 'description' . $number,
             'descriptionformat' => FORMAT_HTML,
-            'scaleid' => $number,
+            'scaleid' => $this->$scalename->id,
             'scaleconfiguration' => $this->$scalepropname,
             'visible' => true,
             'contextid' => $system ? context_system::instance()->id : context_coursecat::instance($this->category->id)->id
@@ -316,7 +334,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
     }
@@ -335,7 +353,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
 
@@ -358,7 +376,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
             'idnumber' => 'id;"number',
             'description' => 'de<>\\..scription',
             'descriptionformat' => FORMAT_HTML,
-            'scaleid' => 1,
+            'scaleid' => $this->scale1->id,
             'scaleconfiguration' => $this->scaleconfiguration1,
             'visible' => true,
             'contextid' => context_system::instance()->id
@@ -384,7 +402,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
     }
@@ -410,7 +428,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber2', $result->idnumber);
         $this->assertEquals('description2', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(2, $result->scaleid);
+        $this->assertEquals($this->scale2->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
 
@@ -444,7 +462,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
     }
@@ -470,7 +488,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber2', $result->idnumber);
         $this->assertEquals('description2', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(2, $result->scaleid);
+        $this->assertEquals($this->scale2->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
 
@@ -579,12 +597,12 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
 
         $s1 = $this->getDataGenerator()->create_scale();
 
-        $f1 = $lpg->create_framework(array('scaleid' => 1));
-        $f2 = $lpg->create_framework(array('scaleid' => 1));
+        $f1 = $lpg->create_framework(array('scaleid' => $s1->id));
+        $f2 = $lpg->create_framework(array('scaleid' => $s1->id));
         $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
         $c2 = $lpg->create_competency(array('competencyframeworkid' => $f2->get_id()));
 
-        $this->assertEquals(1, $f1->get_scaleid());
+        $this->assertEquals($s1->id, $f1->get_scaleid());
 
         // Make the scale of f2 being used.
         $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c2->get_id()));
@@ -593,7 +611,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $result = $this->update_competency_framework($f1->get_id(), 3, true);
 
         $f1 = new \core_competency\competency_framework($f1->get_id());
-        $this->assertEquals(3, $f1->get_scaleid());
+        $this->assertEquals($this->scale3->id, $f1->get_scaleid());
 
         // Changing the framework where the scale is used.
         try {
@@ -645,7 +663,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
     }
@@ -732,7 +750,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals('idnumber1', $result->idnumber);
         $this->assertEquals('description1', $result->description);
         $this->assertEquals(FORMAT_HTML, $result->descriptionformat);
-        $this->assertEquals(1, $result->scaleid);
+        $this->assertEquals($this->scale1->id, $result->scaleid);
         $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration);
         $this->assertEquals(true, $result->visible);
     }
index 35dc8d7..b47b573 100644 (file)
@@ -2,6 +2,6 @@
     "require-dev": {
         "phpunit/phpunit": "4.8.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "3.31.1"
+        "moodlehq/behat-extension": "3.31.2"
     }
 }
index 8a46951..fccd56f 100644 (file)
@@ -4,8 +4,8 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "08ee36172d6de7fe083e753b44255ed7",
-    "content-hash": "2bc89ce1a925ac037c899ae6f02eaa26",
+    "hash": "ccba8f24cd70bd4ca9b78873fc4be17f",
+    "content-hash": "cf7a848add8e3de854561718a0d18986",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.31.1",
+            "version": "v3.31.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "d876ea5940e7ad115318140ae37f228c70450225"
+                "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d876ea5940e7ad115318140ae37f228c70450225",
-                "reference": "d876ea5940e7ad115318140ae37f228c70450225",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/f0b6a44de9111fd4fa82796aca712b9e9772d07e",
+                "reference": "f0b6a44de9111fd4fa82796aca712b9e9772d07e",
                 "shasum": ""
             },
             "require": {
                 "Behat",
                 "moodle"
             ],
-            "time": "2016-04-01 01:57:33"
+            "time": "2016-05-09 03:32:06"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
         },
         {
             "name": "react/promise",
-            "version": "v2.4.0",
+            "version": "v2.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/reactphp/promise.git",
-                "reference": "f942da7b505d1a294284ab343d05df42d02ad6d9"
+                "reference": "8025426794f1944de806618671d4fa476dc7626f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/reactphp/promise/zipball/f942da7b505d1a294284ab343d05df42d02ad6d9",
-                "reference": "f942da7b505d1a294284ab343d05df42d02ad6d9",
+                "url": "https://api.github.com/repos/reactphp/promise/zipball/8025426794f1944de806618671d4fa476dc7626f",
+                "reference": "8025426794f1944de806618671d4fa476dc7626f",
                 "shasum": ""
             },
             "require": {
                 }
             ],
             "description": "A lightweight implementation of CommonJS Promises/A for PHP",
-            "time": "2016-03-31 13:10:33"
+            "time": "2016-05-03 17:50:52"
         },
         {
             "name": "sebastian/comparator",
         },
         {
             "name": "sebastian/environment",
-            "version": "1.3.5",
+            "version": "1.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/environment.git",
-                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
+                "reference": "2292b116f43c272ff4328083096114f84ea46a56"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
-                "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/2292b116f43c272ff4328083096114f84ea46a56",
+                "reference": "2292b116f43c272ff4328083096114f84ea46a56",
                 "shasum": ""
             },
             "require": {
                 "environment",
                 "hhvm"
             ],
-            "time": "2016-02-26 18:40:46"
+            "time": "2016-05-04 07:59:13"
         },
         {
             "name": "sebastian/exporter",
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
         },
         {
             "name": "symfony/class-loader",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "7d362c22710980730d46a5d039e788946a2938cb"
+                "reference": "f1cf312c81c7b4f0f11431e6fd37b66890f5e27b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/7d362c22710980730d46a5d039e788946a2938cb",
-                "reference": "7d362c22710980730d46a5d039e788946a2938cb",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/f1cf312c81c7b4f0f11431e6fd37b66890f5e27b",
+                "reference": "f1cf312c81c7b4f0f11431e6fd37b66890f5e27b",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-10 19:33:53"
+            "time": "2016-03-30 10:37:34"
         },
         {
             "name": "symfony/config",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "5273f4724dc5288fe7a33cb08077ab9852621f2c"
+                "reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/5273f4724dc5288fe7a33cb08077ab9852621f2c",
-                "reference": "5273f4724dc5288fe7a33cb08077ab9852621f2c",
+                "url": "https://api.github.com/repos/symfony/config/zipball/edbbcf33cffa2a85104fc80de8dc052cc51596bb",
+                "reference": "edbbcf33cffa2a85104fc80de8dc052cc51596bb",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-04 07:54:35"
+            "time": "2016-04-20 18:52:26"
         },
         {
             "name": "symfony/console",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154"
+                "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/9a5aef5fc0d4eff86853d44202b02be8d5a20154",
-                "reference": "9a5aef5fc0d4eff86853d44202b02be8d5a20154",
+                "url": "https://api.github.com/repos/symfony/console/zipball/48221d3de4dc22d2cd57c97e8b9361821da86609",
+                "reference": "48221d3de4dc22d2cd57c97e8b9361821da86609",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-17 09:19:04"
+            "time": "2016-04-26 12:00:47"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "f7b4a498e679fa440b16facb934680a1527ed48c"
+                "reference": "35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f7b4a498e679fa440b16facb934680a1527ed48c",
-                "reference": "f7b4a498e679fa440b16facb934680a1527ed48c",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d",
+                "reference": "35ac8cd26e4477d79e5cbd4f11d41dc92fed4d8d",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-21 07:27:21"
+            "time": "2016-04-20 14:12:37"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "aae5c37d243c6ec11db62221aaff37e7f8005926"
+                "reference": "f282b08f6bbbc72e7af2e9e0c2f896221053f791"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/aae5c37d243c6ec11db62221aaff37e7f8005926",
-                "reference": "aae5c37d243c6ec11db62221aaff37e7f8005926",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/f282b08f6bbbc72e7af2e9e0c2f896221053f791",
+                "reference": "f282b08f6bbbc72e7af2e9e0c2f896221053f791",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-23 13:11:46"
+            "time": "2016-04-12 18:01:21"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87"
+                "reference": "81c4c51f7fd6d0d40961bd53dd60cade32db6ed6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/47d2d8cade9b1c3987573d2943bb9352536cdb87",
-                "reference": "47d2d8cade9b1c3987573d2943bb9352536cdb87",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/81c4c51f7fd6d0d40961bd53dd60cade32db6ed6",
+                "reference": "81c4c51f7fd6d0d40961bd53dd60cade32db6ed6",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-07 14:04:32"
+            "time": "2016-04-05 16:36:54"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "f08ffdf229252cd2745558cb2112df43903bcae4"
+                "reference": "dee379131dceed90a429e951546b33edfe7dccbb"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/f08ffdf229252cd2745558cb2112df43903bcae4",
-                "reference": "f08ffdf229252cd2745558cb2112df43903bcae4",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/dee379131dceed90a429e951546b33edfe7dccbb",
+                "reference": "dee379131dceed90a429e951546b33edfe7dccbb",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-27 10:20:16"
+            "time": "2016-04-12 18:01:21"
         },
         {
             "name": "symfony/polyfill-apcu",
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "fb467471952ef5cf8497c029980e556b47545333"
+                "reference": "1276bd9be89be039748cf753a2137f4ef149cd74"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/fb467471952ef5cf8497c029980e556b47545333",
-                "reference": "fb467471952ef5cf8497c029980e556b47545333",
+                "url": "https://api.github.com/repos/symfony/process/zipball/1276bd9be89be039748cf753a2137f4ef149cd74",
+                "reference": "1276bd9be89be039748cf753a2137f4ef149cd74",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-23 13:11:46"
+            "time": "2016-04-14 15:22:22"
         },
         {
             "name": "symfony/translation",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
         },
         {
             "name": "symfony/yaml",
-            "version": "v2.8.4",
+            "version": "v2.8.5",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb"
+                "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/584e52cb8f788a887553ba82db6caacb1d6260bb",
-                "reference": "584e52cb8f788a887553ba82db6caacb1d6260bb",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
+                "reference": "e4fbcc65f90909c999ac3b4dfa699ee6563a9940",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2016-03-04 07:54:35"
+            "time": "2016-03-29 19:00:15"
         }
     ],
     "aliases": [],
index 262bcf9..30d8c15 100644 (file)
@@ -242,10 +242,6 @@ $CFG->admin = 'admin';
 //      $CFG->session_handler_class = '\core\session\file';
 //      $CFG->session_file_save_path = $CFG->dataroot.'/sessions';
 //
-//   Redis session handler (requires redis server and redis extension):
-//      $CFG->session_handler_class = '\core\session\redis';
-//      $CFG->session_redis_save_path = 'tcp://127.0.0.1'
-//
 //   Memcached session handler (requires memcached server and extension):
 //      $CFG->session_handler_class = '\core\session\memcached';
 //      $CFG->session_memcached_save_path = '127.0.0.1:11211';
diff --git a/course/classes/search/mycourse.php b/course/classes/search/mycourse.php
new file mode 100644 (file)
index 0000000..db136cd
--- /dev/null
@@ -0,0 +1,127 @@
+<?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/>.
+
+/**
+ * Search area for Moodle courses I can access.
+ *
+ * @package    core_course
+ * @copyright  2016 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_course\search;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Search area for Moodle courses I can access.
+ *
+ * @package    core_course
+ * @copyright  2016 Skylar Kelty <S.Kelty@kent.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mycourse extends \core_search\area\base {
+
+    /**
+     * The context levels the search implementation is working on.
+     *
+     * @var array
+     */
+    protected static $levels = [CONTEXT_COURSE];
+
+    /**
+     * Returns recordset containing required data for indexing courses.
+     *
+     * @param int $modifiedfrom timestamp
+     * @return \moodle_recordset
+     */
+    public function get_recordset_by_timestamp($modifiedfrom = 0) {
+        global $DB;
+        return $DB->get_recordset_select('course', 'timemodified >= ?', array($modifiedfrom));
+    }
+
+    /**
+     * Returns the document associated with this course.
+     *
+     * @param stdClass $record
+     * @param array    $options
+     * @return \core_search\document
+     */
+    public function get_document($record, $options = array()) {
+        try {
+            $context = \context_course::instance($record->id);
+        } catch (\moodle_exception $ex) {
+            // Notify it as we run here as admin, we should see everything.
+            debugging('Error retrieving ' . $this->areaid . ' ' . $record->id . ' document, not all required data is available: ' .
+                $ex->getMessage(), DEBUG_DEVELOPER);
+            return false;
+        }
+        // Prepare associative array with data from DB.
+        $doc = \core_search\document_factory::instance($record->id, $this->componentname, $this->areaname);
+        $doc->set('title', $record->fullname);
+        $doc->set('content', content_to_text($record->summary, $record->summaryformat));
+        $doc->set('contextid', $context->id);
+        $doc->set('courseid', $record->id);
+        $doc->set('owneruserid', \core_search\manager::NO_OWNER_ID);
+        $doc->set('modified', $record->timemodified);
+        $doc->set('description1', $record->shortname);
+
+        // Check if this document should be considered new.
+        if (isset($options['lastindexedtime']) && $options['lastindexedtime'] < $record->timecreated) {
+            // If the document was created after the last index time, it must be new.
+            $doc->set_is_new(true);
+        }
+
+        return $doc;
+    }
+
+    /**
+     * Whether the user can access the document or not.
+     *
+     * @param int $id The course instance id.
+     * @return int
+     */
+    public function check_access($id) {
+        global $DB;
+        $course = $DB->get_record('course', array('id' => $id));
+        if (!$course) {
+            return \core_search\manager::ACCESS_DELETED;
+        }
+        if (can_access_course($course)) {
+            return \core_search\manager::ACCESS_GRANTED;
+        }
+        return \core_search\manager::ACCESS_DENIED;
+    }
+
+    /**
+     * Link to the course.
+     *
+     * @param \core_search\document $doc
+     * @return \moodle_url
+     */
+    public function get_doc_url(\core_search\document $doc) {
+        return $this->get_context_url($doc);
+    }
+
+    /**
+     * Link to the course.
+     *
+     * @param \core_search\document $doc
+     * @return \moodle_url
+     */
+    public function get_context_url(\core_search\document $doc) {
+        return new \moodle_url('/course/view.php', array('id' => $doc->get('courseid')));
+    }
+}
index da654a2..ea687c6 100644 (file)
@@ -431,6 +431,7 @@ class core_course_external extends external_api {
             $courseinfo['id'] = $course->id;
             $courseinfo['fullname'] = $course->fullname;
             $courseinfo['shortname'] = $course->shortname;
+            $courseinfo['displayname'] = external_format_string(get_course_display_name_for_list($course), $context->id);
             $courseinfo['categoryid'] = $course->category;
             list($courseinfo['summary'], $courseinfo['summaryformat']) =
                 external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
@@ -498,6 +499,7 @@ class core_course_external extends external_api {
                             'categorysortorder' => new external_value(PARAM_INT,
                                     'sort order into the category', VALUE_OPTIONAL),
                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
+                            'displayname' => new external_value(PARAM_TEXT, 'course display name'),
                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
                             'summary' => new external_value(PARAM_RAW, 'summary'),
                             'summaryformat' => new external_format_value('summary'),
@@ -2177,6 +2179,7 @@ class core_course_external extends external_api {
             'requiredcapabilities' => $requiredcapabilities
         );
         $params = self::validate_parameters(self::search_courses_parameters(), $parameters);
+        self::validate_context(context_system::instance());
 
         $allowedcriterianames = array('search', 'modulelist', 'blocklist', 'tagid');
         if (!in_array($params['criterianame'], $allowedcriterianames)) {
index 26400e3..f0d4869 100644 (file)
@@ -32,13 +32,6 @@ require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php'
 
 class core_course_courselib_testcase extends advanced_testcase {
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     /**
      * Set forum specific test values for calling create_module().
      *
@@ -1917,7 +1910,6 @@ class core_course_courselib_testcase extends advanced_testcase {
         $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
         $file->extract_to_pathname($fp, $filepath);
         $bc->destroy();
-        unset($bc);
 
         // Now we want to catch the restore course event.
         $sink = $this->redirectEvents();
@@ -1955,7 +1947,6 @@ class core_course_courselib_testcase extends advanced_testcase {
 
         // Destroy the resource controller since we are done using it.
         $rc->destroy();
-        unset($rc);
     }
 
     /**
index 81af78e..9ad5d73 100644 (file)
@@ -47,13 +47,6 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         require_once($CFG->dirroot . '/course/externallib.php');
     }
 
-    /**
-     * Tidy up open files that may be left open.
-     */
-    protected function tearDown() {
-        gc_collect_cycles();
-    }
-
     /**
      * Test create_categories
      */
@@ -561,6 +554,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
             $dbcourse = $generatedcourses[$course['id']];
             $this->assertEquals($course['idnumber'], $dbcourse->idnumber);
             $this->assertEquals($course['fullname'], $dbcourse->fullname);
+            $this->assertEquals($course['displayname'], get_course_display_name_for_list($dbcourse));
             // Summary was converted to the HTML format.
             $this->assertEquals($course['summary'], format_text($dbcourse->summary, FORMAT_MOODLE, array('para' => false)));
             $this->assertEquals($course['summaryformat'], FORMAT_HTML);
diff --git a/course/tests/restore_test.php b/course/tests/restore_test.php
new file mode 100644 (file)
index 0000000..f794e66
--- /dev/null
@@ -0,0 +1,324 @@
+<?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/>.
+
+/**
+ * Course restore tests.
+ *
+ * @package    core_course
+ * @copyright  2016 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+global $CFG;
+
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
+
+/**
+ * Course restore testcase.
+ *
+ * @package    core_course
+ * @copyright  2016 Frédéric Massart - FMCorz.net
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_restore_backup_testcase extends advanced_testcase {
+
+    /**
+     * Backup a course and return its backup ID.
+     *
+     * @param int $courseid The course ID.
+     * @param int $userid The user doing the backup.
+     * @return string
+     */
+    protected function backup_course($courseid, $userid = 2) {
+        globaL $CFG;
+        $packer = get_file_packer('application/vnd.moodle.backup');
+
+        $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
+            backup::MODE_GENERAL, $userid);
+        $bc->execute_plan();
+
+        $results = $bc->get_results();
+        $results['backup_destination']->extract_to_pathname($packer, "$CFG->tempdir/backup/core_course_testcase");
+
+        $bc->destroy();
+        unset($bc);
+        return 'core_course_testcase';
+    }
+
+    /**
+     * Create a role with capabilities and permissions.
+     *
+     * @param string|array $caps Capability names.
+     * @param int $perm Constant CAP_* to apply to the capabilities.
+     * @return int The new role ID.
+     */
+    protected function create_role_with_caps($caps, $perm) {
+        $caps = (array) $caps;
+        $dg = $this->getDataGenerator();
+        $roleid = $dg->create_role();
+        foreach ($caps as $cap) {
+            assign_capability($cap, $perm, $roleid, context_system::instance()->id, true);
+        }
+        accesslib_clear_all_caches_for_unit_testing();
+        return $roleid;
+    }
+
+    /**
+     * Restore a course.
+     *
+     * @param int $backupid The backup ID.
+     * @param int $courseid The course ID to restore in, or 0.
+     * @param int $userid The ID of the user performing the restore.
+     * @return stdClass The updated course object.
+     */
+    protected function restore_course($backupid, $courseid, $userid) {
+        global $DB;
+
+        $target = backup::TARGET_CURRENT_ADDING;
+        if (!$courseid) {
+            $target = backup::TARGET_NEW_COURSE;
+            $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
+            $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
+        }
+
+        $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
+        $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
+        $rc->execute_precheck();
+        $rc->execute_plan();
+
+        $course = $DB->get_record('course', array('id' => $rc->get_courseid()));
+
+        $rc->destroy();
+        unset($rc);
+        return $course;
+    }
+
+    /**
+     * Restore a course to an existing course.
+     *
+     * @param int $backupid The backup ID.
+     * @param int $courseid The course ID to restore in.
+     * @param int $userid The ID of the user performing the restore.
+     * @return stdClass The updated course object.
+     */
+    protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
+        return $this->restore_course($backupid, $courseid, $userid);
+    }
+
+    /**
+     * Restore a course to a new course.
+     *
+     * @param int $backupid The backup ID.
+     * @param int $userid The ID of the user performing the restore.
+     * @return stdClass The new course object.
+     */
+    protected function restore_to_new_course($backupid, $userid = 2) {
+        return $this->restore_course($backupid, 0, $userid);
+    }
+
+    public function test_restore_existing_idnumber_in_new_course() {
+        $this->resetAfterTest();
+
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course(['idnumber' => 'ABC']);
+        $backupid = $this->backup_course($c1->id);
+        $c2 = $this->restore_to_new_course($backupid);
+
+        // The ID number is set empty.
+        $this->assertEquals('', $c2->idnumber);
+    }
+
+    public function test_restore_non_existing_idnumber_in_new_course() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course(['idnumber' => 'ABC']);
+        $backupid = $this->backup_course($c1->id);
+
+        $c1->idnumber = 'BCD';
+        $DB->update_record('course', $c1);
+
+        // The ID number changed.
+        $c2 = $this->restore_to_new_course($backupid);
+        $this->assertEquals('ABC', $c2->idnumber);
+    }
+
+    public function test_restore_existing_idnumber_in_existing_course() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course(['idnumber' => 'ABC']);
+        $c2 = $dg->create_course(['idnumber' => 'DEF']);
+        $backupid = $this->backup_course($c1->id);
+
+        // The ID number does not change.
+        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+        $this->assertEquals('DEF', $c2->idnumber);
+
+        $c1 = $DB->get_record('course', array('id' => $c1->id));
+        $this->assertEquals('ABC', $c1->idnumber);
+    }
+
+    public function test_restore_non_existing_idnumber_in_existing_course() {
+        global $DB;
+        $this->resetAfterTest();
+
+        $dg = $this->getDataGenerator();
+        $c1 = $dg->create_course(['idnumber' => 'ABC']);
+        $c2 = $dg->create_course(['idnumber' => 'DEF']);
+        $backupid = $this->backup_course($c1->id);
+
+        $c1->idnumber = 'XXX';
+        $DB->update_record('course', $c1);
+
+        // The ID number has changed.
+        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+        $this->assertEquals('ABC', $c2->idnumber);
+    }
+
+    public function test_restore_idnumber_in_existing_course_without_permissions() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+        $u1 = $dg->create_user();
+
+        $managers = get_archetype_roles('manager');
+        $manager = array_shift($managers);
+        $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
+        $dg->role_assign($manager->id, $u1->id);
+        $dg->role_assign($roleid, $u1->id);
+
+        $c1 = $dg->create_course(['idnumber' => 'ABC']);
+        $c2 = $dg->create_course(['idnumber' => 'DEF']);
+        $backupid = $this->backup_course($c1->id);
+
+        $c1->idnumber = 'XXX';
+        $DB->update_record('course', $c1);
+
+        // The ID number does not change.
+        $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+        $this->assertEquals('DEF', $c2->idnumber);
+    }
+
+    public function test_restore_course_info_in_new_course() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+
+        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+        $backupid = $this->backup_course($c1->id);
+
+        // The information is restored but adapted because names are already taken.
+        $c2 = $this->restore_to_new_course($backupid);
+        $this->assertEquals('SN_1', $c2->shortname);
+        $this->assertEquals('FN copy 1', $c2->fullname);
+        $this->assertEquals('DESC', $c2->summary);
+        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+    }
+
+    public function test_restore_course_info_in_existing_course() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+
+        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+        $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN]);
+        $backupid = $this->backup_course($c1->id);
+
+        // The information is restored but adapted because names are already taken.
+        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
+        $this->assertEquals('SN_1', $c2->shortname);
+        $this->assertEquals('FN copy 1', $c2->fullname);
+        $this->assertEquals('DESC', $c2->summary);
+        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
+    }
+
+    public function test_restore_course_shortname_in_existing_course_without_permissions() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+        $u1 = $dg->create_user();
+
+        $managers = get_archetype_roles('manager');
+        $manager = array_shift($managers);
+        $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
+        $dg->role_assign($manager->id, $u1->id);
+        $dg->role_assign($roleid, $u1->id);
+
+        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+        // The shortname does not change.
+        $backupid = $this->backup_course($c1->id);
+        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+        $this->assertEquals($c2->shortname, $restored->shortname);
+        $this->assertEquals('FN copy 1', $restored->fullname);
+        $this->assertEquals('DESC', $restored->summary);
+        $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+    }
+
+    public function test_restore_course_fullname_in_existing_course_without_permissions() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+        $u1 = $dg->create_user();
+
+        $managers = get_archetype_roles('manager');
+        $manager = array_shift($managers);
+        $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
+        $dg->role_assign($manager->id, $u1->id);
+        $dg->role_assign($roleid, $u1->id);
+
+        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+        // The fullname does not change.
+        $backupid = $this->backup_course($c1->id);
+        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+        $this->assertEquals('SN_1', $restored->shortname);
+        $this->assertEquals($c2->fullname, $restored->fullname);
+        $this->assertEquals('DESC', $restored->summary);
+        $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
+    }
+
+    public function test_restore_course_summary_in_existing_course_without_permissions() {
+        global $DB;
+        $this->resetAfterTest();
+        $dg = $this->getDataGenerator();
+        $u1 = $dg->create_user();
+
+        $managers = get_archetype_roles('manager');
+        $manager = array_shift($managers);
+        $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
+        $dg->role_assign($manager->id, $u1->id);
+        $dg->role_assign($roleid, $u1->id);
+
+        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
+        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);
+
+        // The summary and format do not change.
+        $backupid = $this->backup_course($c1->id);
+        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
+        $this->assertEquals('SN_1', $restored->shortname);
+        $this->assertEquals('FN copy 1', $restored->fullname);
+        $this->assertEquals($c2->summary, $restored->summary);
+        $this->assertEquals($c2->summaryformat, $restored->summaryformat);
+    }
+}
diff --git a/course/tests/search_test.php b/course/tests/search_test.php
new file mode 100644 (file)
index 0000000..6750fbf
--- /dev/null
@@ -0,0 +1,164 @@
+<?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/>.
+
+/**
+ * Course global search unit tests.
+ *
+ * @package     core
+ * @category    phpunit
+ * @copyright   2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/search/tests/fixtures/testable_core_search.php');
+
+/**
+ * Provides the unit tests for course global search.
+ *
+ * @package     core
+ * @category    phpunit
+ * @copyright   2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_search_testcase extends advanced_testcase {
+
+    /**
+     * @var string Area id
+     */
+    protected $mycoursesareaid = null;
+
+    public function setUp() {
+        $this->resetAfterTest(true);
+        set_config('enableglobalsearch', true);
+
+        $this->mycoursesareaid = \core_search\manager::generate_areaid('core_course', 'mycourse');
+
+        // Set \core_search::instance to the mock_search_engine as we don't require the search engine to be working to test this.
+        $search = testable_core_search::instance();
+    }
+
+    /**
+     * Indexing my courses contents.
+     *
+     * @return void
+     */
+    public function test_mycourses_indexing() {
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+        $this->assertInstanceOf('\core_course\search\mycourse', $searcharea);
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $course1 = self::getDataGenerator()->create_course();
+        $course2 = self::getDataGenerator()->create_course();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
+
+        $record = new stdClass();
+        $record->course = $course1->id;
+
+        // All records.
+        $recordset = $searcharea->get_recordset_by_timestamp(0);
+        $this->assertTrue($recordset->valid());
+        $nrecords = 0;
+        foreach ($recordset as $record) {
+            $this->assertInstanceOf('stdClass', $record);
+            $doc = $searcharea->get_document($record);
+            $this->assertInstanceOf('\core_search\document', $doc);
+            $nrecords++;
+        }
+        // If there would be an error/failure in the foreach above the recordset would be closed on shutdown.
+        $recordset->close();
+        $this->assertEquals(3, $nrecords);
+
+        // The +2 is to prevent race conditions.
+        $recordset = $searcharea->get_recordset_by_timestamp(time() + 2);
+
+        // No new records.
+        $this->assertFalse($recordset->valid());
+        $recordset->close();
+    }
+
+    /**
+     * Document contents.
+     *
+     * @return void
+     */
+    public function test_mycourses_document() {
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+        $this->assertInstanceOf('\core_course\search\mycourse', $searcharea);
+
+        $user = self::getDataGenerator()->create_user();
+        $course = self::getDataGenerator()->create_course();
+        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'teacher');
+
+        $doc = $searcharea->get_document($course);
+        $this->assertInstanceOf('\core_search\document', $doc);
+        $this->assertEquals($course->id, $doc->get('itemid'));
+        $this->assertEquals($this->mycoursesareaid . '-' . $course->id, $doc->get('id'));
+        $this->assertEquals($course->id, $doc->get('courseid'));
+        $this->assertFalse($doc->is_set('userid'));
+        $this->assertEquals(\core_search\manager::NO_OWNER_ID, $doc->get('owneruserid'));
+        $this->assertEquals($course->fullname, $doc->get('title'));
+
+        // Not nice. Applying \core_search\document::set line breaks clean up.
+        $summary = preg_replace("/\s+/", ' ', trim(content_to_text($course->summary, $course->summaryformat), "\r\n"));
+        $this->assertEquals($summary, $doc->get('content'));
+        $this->assertEquals($course->shortname, $doc->get('description1'));
+    }
+
+    /**
+     * Document accesses.
+     *
+     * @return void
+     */
+    public function test_mycourses_access() {
+
+        // Returns the instance as long as the area is supported.
+        $searcharea = \core_search\manager::get_search_area($this->mycoursesareaid);
+
+        $user1 = self::getDataGenerator()->create_user();
+        $user2 = self::getDataGenerator()->create_user();
+
+        $course1 = self::getDataGenerator()->create_course();
+        $course2 = self::getDataGenerator()->create_course(array('visible' => 0));
+        $course3 = self::getDataGenerator()->create_course();
+
+        $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'teacher');
+        $this->getDataGenerator()->enrol_user($user2->id, $course1->id, 'student');
+        $this->getDataGenerator()->enrol_user($user1->id, $course2->id, 'teacher');
+        $this->getDataGenerator()->enrol_user($user2->id, $course2->id, 'student');
+
+        $this->setUser($user1);
+        $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id));
+        $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course2->id));
+        $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id));
+        $this->assertEquals(\core_search\manager::ACCESS_DELETED, $searcharea->check_access(-123));
+
+        $this->setUser($user2);
+        $this->assertEquals(\core_search\manager::ACCESS_GRANTED, $searcharea->check_access($course1->id));
+        $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course2->id));
+        $this->assertEquals(\core_search\manager::ACCESS_DENIED, $searcharea->check_access($course3->id));
+    }
+}
index 12cea2e..8fcc61c 100644 (file)
@@ -42,6 +42,7 @@ require_capability('enrol/' . $type . ':config', $context);
 
 $PAGE->set_url('/enrol/editinstance.php', array('courseid' => $course->id, 'id' => $instanceid, 'type' => $type));
 $PAGE->set_pagelayout('admin');
+$PAGE->set_docs_path('enrol/' . $type . '/edit');
 
 if (empty($return)) {
     $return = new moodle_url('/enrol/instances.php', array('id' => $course->id));
index 8f8347b..8db19d9 100644 (file)
@@ -651,9 +651,7 @@ class core_enrol_external extends external_api {
         global $DB;
 
         $params = self::validate_parameters(self::get_course_enrolment_methods_parameters(), array('courseid' => $courseid));
-
-        // Note that we can't use validate_context because the user is not enrolled in the course.
-        require_login(null, false, null, false, true);
+        self::validate_context(context_system::instance());
 
         $course = $DB->get_record('course', array('id' => $params['courseid']), '*', MUST_EXIST);
         $context = context_course::instance($course->id);
index a441ef8..656c295 100644 (file)
@@ -71,7 +71,7 @@ class enrol_guest_external extends external_api {
             throw new moodle_exception('invaliddata', 'error');
         }
 
-        require_login(null, false, null, false, true);
+        self::validate_context(context_system::instance());
         $enrolinstance = $DB->get_record('enrol', array('id' => $params['instanceid']), '*', MUST_EXIST);
 
         $course = $DB->get_record('course', array('id' => $enrolinstance->courseid), '*', MUST_EXIST);
index 9127483..dc23ea6 100644 (file)
@@ -30,6 +30,7 @@ $functions = array(
         'classname'   => 'enrol_guest_external',
         'methodname'  => 'get_instance_info',
         'description' => 'Return guest enrolment instance information.',
-        'type'        => 'read'
+        'type'        => 'read',
+        'services'    => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
 );
index 5076e7b..5abca61 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015111601;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2015111602;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015111000;        // Requires this Moodle version
 $plugin->component = 'enrol_guest';     // Full name of the plugin (used for diagnostics)
index f6a006a..d2d6336 100644 (file)
@@ -267,7 +267,10 @@ class helper {
             $instance->enrol = 'lti';
             $instance->status = $tool->status;
             $ltienrol = enrol_get_plugin('lti');
-            $ltienrol->enrol_user($instance, $userid, null, time(), $timeend);
+
+            // Hack - need to do this to workaround DB caching hack. See MDL-53977.
+            $timestart = intval(substr(time(), 0, 8) . '00') - 1;
+            $ltienrol->enrol_user($instance, $userid, null, $timestart, $timeend);
         }
 
         return self::ENROLMENT_SUCCESSFUL;
index efe55ff..3780910 100644 (file)
@@ -22,6 +22,7 @@
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['allowframembedding'] = 'In order to avoid problems embedding this site, please enable the \'Allow frame embedding\' setting in Admin > Security > HTTP security.';
 $string['enrolenddate'] = 'End date';
 $string['enrolenddate_help'] = 'If enabled, users can access until this date only.';
 $string['enrolenddateerror'] = 'Enrolment end date cannot be earlier than start date';
@@ -32,6 +33,7 @@ $string['enrolmentfinished'] = 'Enrolment finished.';
 $string['enrolmentnotstarted'] = 'Enrolment has not started.';
 $string['enrolstartdate'] = 'Start date';
 $string['enrolstartdate_help'] = 'If enabled, users can access from this date onward only.';
+$string['framembeddingnotenabled'] = 'The site you are connecting to does not have frame embedding enabled. To access the resource please click on the link below.';
 $string['globalsharedsecret'] = 'Global shared secret';
 $string['gradesync'] = 'Grade synchronisation';
 $string['gradesync_help'] = 'This determines if we want grade synchronisation to occur.';
@@ -48,6 +50,7 @@ $string['membersyncmodeunenrolmissing'] = 'Unenrol missing members';
 $string['notoolsprovided'] = 'No tools provided';
 $string['lti:config'] = 'Configure LTI enrol instances';
 $string['lti:unenrol'] = 'Unenrol users from the course';
+$string['opentool'] = 'Open tool';
 $string['pluginname'] = 'Shared external tool';
 $string['pluginname_desc'] = 'The shared external tool plugin allows externals users to access a course or an activity via a unique link - this requires the LTI authentication plugin to be enabled.';
 $string['remotesystem'] = 'Remote system';
@@ -62,7 +65,7 @@ $string['sharedexternaltools'] = 'Shared external tools';
 $string['syncsettings'] = 'Synchronisation settings';
 $string['tooldoesnotexist'] = 'The requested tool does not exist.';
 $string['tasksyncgrades'] = 'Handles syncing grades with the consumer';
-$string['tasksyncmembers'] = 'handles syncing members with the consumer';
+$string['tasksyncmembers'] = 'Handles syncing members with the consumer';
 $string['toolsprovided'] = 'Tools provided';
 $string['tooltobeprovided'] = 'Tool to be provided';
 $string['userdefaultvalues'] = 'User default values';
index 372e287..1df4193 100644 (file)
@@ -174,11 +174,12 @@ class enrol_lti_plugin extends enrol_plugin {
     public function unenrol_user(stdClass $instance, $userid) {
         global $DB;
 
-        // Get the tool associated with this instance.
-        $tool = $DB->get_record('enrol_lti_tools', array('enrolid' => $instance->id), 'id', MUST_EXIST);
-
-        // Need to remove the user from the users table.
-        $DB->delete_records('enrol_lti_users', array('userid' => $userid, 'toolid' => $tool->id));
+        // Get the tool associated with this instance. Note - it may not exist if we have deleted