Merge branch 'MDL-60809-master' of git://git.cameron1729.xyz/moodle
authorDamyon Wiese <damyon@moodle.com>
Mon, 20 Nov 2017 07:29:53 +0000 (15:29 +0800)
committerDamyon Wiese <damyon@moodle.com>
Mon, 20 Nov 2017 07:29:53 +0000 (15:29 +0800)
12 files changed:
backup/util/settings/setting_dependency.class.php
backup/util/ui/backup_ui_setting.class.php
backup/util/ui/base_moodleform.class.php
backup/util/ui/tests/behat/restore_moodle2_courses_settings.feature [new file with mode: 0644]
calendar/lib.php
completion/classes/external.php
completion/tests/externallib_test.php
lib/amd/build/form-autocomplete.min.js
lib/amd/src/form-autocomplete.js
mod/glossary/lib.php
mod/lti/locallib.php
user/profile/lib.php

index 425a328..079537f 100644 (file)
@@ -153,7 +153,7 @@ abstract class setting_dependency {
      */
     abstract public function get_moodleform_properties();
     /**
-     * Returns true if the dependent setting is locked.
+     * Returns true if the dependent setting is locked by this setting_dependency.
      * @return bool
      */
     abstract public function is_locked();
@@ -185,7 +185,7 @@ class setting_dependency_disabledif_equals extends setting_dependency {
         $this->value = ($value)?(string)$value:0;
     }
     /**
-     * Returns true if the dependent setting is locked.
+     * Returns true if the dependent setting is locked by this setting_dependency.
      * @return bool
      */
     public function is_locked() {
@@ -193,8 +193,8 @@ class setting_dependency_disabledif_equals extends setting_dependency {
         if ($this->setting->get_status() !== base_setting::NOT_LOCKED || $this->setting->get_value() == $this->value) {
             return true;
         }
-        // Else return based upon the dependent settings status
-        return ($this->dependentsetting->get_status() !== base_setting::NOT_LOCKED);
+        // Else the dependent setting is not locked by this setting_dependency.
+        return false;
     }
     /**
      * Processes a value change in the primary setting
@@ -343,7 +343,7 @@ class setting_dependency_disabledif_equals2 extends setting_dependency {
         $this->value = $value;
     }
     /**
-     * Returns true if the dependent setting is locked.
+     * Returns true if the dependent setting is locked by this setting_dependency.
      * @return bool
      */
     public function is_locked() {
@@ -351,8 +351,8 @@ class setting_dependency_disabledif_equals2 extends setting_dependency {
         if ($this->setting->get_status() !== base_setting::NOT_LOCKED || in_array($this->setting->get_value(), $this->value)) {
             return true;
         }
-        // Else return based upon the dependent settings status
-        return ($this->dependentsetting->get_status() !== base_setting::NOT_LOCKED);
+        // Else the dependent setting is not locked by this setting_dependency.
+        return false;
     }
     /**
      * Processes a value change in the primary setting
@@ -537,7 +537,7 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
     }
 
     /**
-     * Returns true if the dependent setting is locked.
+     * Returns true if the dependent setting is locked by this setting_dependency.
      * @return bool
      */
     public function is_locked() {
@@ -545,8 +545,8 @@ class setting_dependency_disabledif_not_empty extends setting_dependency_disable
         if ($this->setting->get_status() !== base_setting::NOT_LOCKED || !empty($value)) {
             return true;
         }
-        // Else return based upon the dependent settings status
-        return ($this->dependentsetting->get_status() !== base_setting::NOT_LOCKED);
+        // Else the dependent setting is not locked by this setting_dependency.
+        return false;
     }
 }
 
@@ -601,7 +601,7 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
         return ($prevalue != $this->dependentsetting->get_value());
     }
     /**
-     * Returns true if the dependent setting is locked.
+     * Returns true if the dependent setting is locked by this setting_dependency.
      * @return bool
      */
     public function is_locked() {
@@ -609,7 +609,7 @@ class setting_dependency_disabledif_empty extends setting_dependency_disabledif_
         if ($this->setting->get_status() !== base_setting::NOT_LOCKED || empty($value)) {
             return true;
         }
-        // Else return based upon the dependent settings status
-        return ($this->dependentsetting->get_status() !== base_setting::NOT_LOCKED);
+        // Else the dependent setting is not locked by this setting_dependency.
+        return false;
     }
 }
index d928743..7109162 100644 (file)
@@ -307,10 +307,12 @@ abstract class backup_setting_ui extends base_setting_ui {
      * 2. The setting is locked but only by settings that are of the same level (same page)
      *
      * Condition 2 is really why we have this function
-     *
+     * @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
+     *          when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
+     *          they could be changeable in the same view.
      * @return bool
      */
-    public function is_changeable() {
+    public function is_changeable($level = null) {
         if ($this->setting->get_status() === backup_setting::NOT_LOCKED) {
             // Its not locked so its chanegable.
             return true;
@@ -319,6 +321,9 @@ abstract class backup_setting_ui extends base_setting_ui {
             return false;
         } else if ($this->setting->has_dependencies_on_settings()) {
             foreach ($this->setting->get_settings_depended_on() as $dependency) {
+                if ($level && $dependency->get_setting()->get_level() >= $level) {
+                    continue;
+                }
                 if ($dependency->is_locked() && $dependency->get_setting()->get_level() !== $this->setting->get_level()) {
                     // Its not changeable because one or more dependancies arn't changeable.
                     return false;
@@ -458,13 +463,16 @@ class backup_setting_ui_checkbox extends backup_setting_ui {
 
     /**
      * Returns true if the setting is changeable
+     * @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
+     *          when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
+     *          they could be changeable in the same view.
      * @return bool
      */
-    public function is_changeable() {
+    public function is_changeable($level = null) {
         if ($this->changeable === false) {
             return false;
         } else {
-            return parent::is_changeable();
+            return parent::is_changeable($level);
         }
     }
 
@@ -639,13 +647,16 @@ class backup_setting_ui_select extends backup_setting_ui {
     /**
      * Returns true if the setting is changeable, false otherwise
      *
+     * @param int $level Optional, if provided only depedency_settings below or equal to this level are considered,
+     *          when checking if the ui_setting is changeable. Although dependencies might cause a lock on this setting,
+     *          they could be changeable in the same view.
      * @return bool
      */
-    public function is_changeable() {
+    public function is_changeable($level = null) {
         if (count($this->values) == 1) {
             return false;
         } else {
-            return parent::is_changeable();
+            return parent::is_changeable($level);
         }
     }
 
index 4f8928e..34820e4 100644 (file)
@@ -183,11 +183,22 @@ abstract class base_moodleform extends moodleform {
     public function add_settings(array $settingstasks) {
         global $OUTPUT;
 
+        // Determine highest setting level, which is displayed in this stage. This is relevant for considering only
+        // locks of dependency settings for parent settings, which are not displayed in this stage.
+        $highestlevel = backup_setting::ACTIVITY_LEVEL;
+        foreach ($settingstasks as $st) {
+            list($setting, $task) = $st;
+            if ($setting->get_level() < $highestlevel) {
+                $highestlevel = $setting->get_level();
+            }
+        }
+
         $defaults = array();
         foreach ($settingstasks as $st) {
             list($setting, $task) = $st;
             // If the setting cant be changed or isn't visible then add it as a fixed setting.
-            if (!$setting->get_ui()->is_changeable() || $setting->get_visibility() != backup_setting::VISIBLE) {
+            if (!$setting->get_ui()->is_changeable($highestlevel) ||
+                $setting->get_visibility() != backup_setting::VISIBLE) {
                 $this->add_fixed_setting($setting, $task);
                 continue;
             }
diff --git a/backup/util/ui/tests/behat/restore_moodle2_courses_settings.feature b/backup/util/ui/tests/behat/restore_moodle2_courses_settings.feature
new file mode 100644 (file)
index 0000000..b851957
--- /dev/null
@@ -0,0 +1,125 @@
+@core @core_backup
+Feature: Restore Moodle 2 course backups with different user data settings
+  In order to decide upon including user data during backup and restore of courses
+  As a teacher and an admin
+  I need to be able to set and override backup and restore settings
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    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 |
+    And the following "activities" exist:
+      | activity | name               | intro | course | idnumber |
+      | data     | Test database name | n     | C1     | data1    |
+    And I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I add a "Text input" field to "Test database name" database and I fill the form with:
+      | Field name | Test field name |
+      | Field description | Test field description |
+    And I follow "Templates"
+    And I wait until the page is ready
+    And I log out
+    And I log in as "student1"
+    And I am on "Course 1" course homepage
+    And I add an entry to "Test database name" database with:
+      | Test field name | Student entry |
+    And I press "Save and view"
+    And I log out
+    And I log in as "admin"
+    And I backup "Course 1" course using this options:
+      | Initial |  Include enrolled users | 1 |
+      | Confirmation | Filename | test_backup.mbz |
+
+  @javascript
+  Scenario: Restore a backup with user data
+    # "User data" marks the user data field for the section
+    # "-" marks the user data field for the data activity
+    When I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 1 |
+      | Schema | User data | 1 |
+      | Schema | - | 1 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup without user data for data activity
+    # "User data" marks the user data field for the section
+    # "-" marks the user data field for the data activity
+    When I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 1 |
+      | Schema | User data | 1 |
+      | Schema | - | 0 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should not see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup without user data for section and data activity
+    # "User data" marks the user data field for the section
+    # "-" marks the user data field for the data activity
+    When I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 1 |
+      | Schema | User data | 0 |
+      | Schema | - | 0 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should not see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup without user data for section
+    # "User data" marks the user data field for the section
+    # "-" marks the user data field for the data activity
+    When I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 1 |
+      | Schema | - | 1 |
+      | Schema | User data | 0 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should not see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup with user data with local config for including users set to 0
+    And I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 0 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should not see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup with user data with site config for including users set to 0
+    Given I navigate to "General restore defaults" node in "Site administration > Courses > Backups"
+    And I set the field "s_restore_restore_general_users" to ""
+    And I press "Save changes"
+    And I am on "Course 1" course homepage
+    And I navigate to "Restore" node in "Course administration"
+    # "User data" marks the user data field for the section
+    # "-" marks the user data field for the data activity
+    And I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 1 |
+      | Schema | User data | 1 |
+      | Schema | - | 1 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should see "Student entry"
+
+  @javascript
+  Scenario: Restore a backup with user data with local and site config config for including users set to 0
+    Given I navigate to "General restore defaults" node in "Site administration > Courses > Backups"
+    And I set the field "s_restore_restore_general_users" to ""
+    And I press "Save changes"
+    And I am on "Course 1" course homepage
+    And I navigate to "Restore" node in "Course administration"
+    When I restore "test_backup.mbz" backup into a new course using this options:
+      | Settings |  Include enrolled users | 0 |
+    Then I should see "Test database name"
+    When I follow "Test database name"
+    Then I should not see "Student entry"
\ No newline at end of file
index 4790d70..26af6c1 100644 (file)
@@ -1056,7 +1056,7 @@ class calendar_information {
             }
 
             $courses = [$course->id => $course];
-            $category = (\coursecat::get($course->category))->get_db_record();
+            $category = (\coursecat::get($course->category, MUST_EXIST, true))->get_db_record();
         } else if (!empty($categoryid)) {
             $course = get_site();
             $courses = calendar_get_default_courses();
@@ -1147,7 +1147,7 @@ class calendar_information {
             // A specific course was requested.
             // Fetch the category that this course is in, along with all parents.
             // Do not include child categories of this category, as the user many not have enrolments in those siblings or children.
-            $category = \coursecat::get($course->category);
+            $category = \coursecat::get($course->category, MUST_EXIST, true);
             $this->categoryid = $category->id;
 
             $this->categories = $category->get_parents();
index 142496b..b610108 100644 (file)
@@ -265,9 +265,11 @@ class core_completion_external extends external_api {
                 $thisprogress  = $userprogress->progress[$activity->id];
                 $state         = $thisprogress->completionstate;
                 $timecompleted = $thisprogress->timemodified;
+                $overrideby    = $thisprogress->overrideby;
             } else {
                 $state = COMPLETION_INCOMPLETE;
                 $timecompleted = 0;
+                $overrideby = null;
             }
 
             $results[] = array(
@@ -276,7 +278,8 @@ class core_completion_external extends external_api {
                        'instance'      => $activity->instance,
                        'state'         => $state,
                        'timecompleted' => $timecompleted,
-                       'tracking'      => $activity->completion
+                       'tracking'      => $activity->completion,
+                       'overrideby'    => $overrideby
             );
         }
 
@@ -308,6 +311,8 @@ class core_completion_external extends external_api {
                             'timecompleted' => new external_value(PARAM_INT, 'timestamp for completed activity'),
                             'tracking'      => new external_value(PARAM_INT, 'type of tracking:
                                                                     0 means none, 1 manual, 2 automatic'),
+                            'overrideby' => new external_value(PARAM_INT, 'The user id who has overriden the status, or null',
+                                VALUE_OPTIONAL),
                         ), 'Activity'
                     ), 'List of activities status'
                 ),
index 0a5ec14..355ecc2 100644 (file)
@@ -164,7 +164,25 @@ class core_completion_externallib_testcase extends externallib_advanced_testcase
         // We added 4 activities, but only 3 with completion enabled and one of those is hidden.
         $this->assertCount(3, $result['statuses']);
 
-        // Change teacher role capabilities (disable access al goups).
+        // Override status by teacher.
+        $completion->update_state($cmforum, COMPLETION_INCOMPLETE, $student->id, true);
+
+        $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
+        // We need to execute the return values cleaning process to simulate the web service server.
+        $result = external_api::clean_returnvalue(
+            core_completion_external::get_activities_completion_status_returns(), $result);
+
+        // Check forum has been overriden by the teacher.
+        foreach ($result['statuses'] as $status) {
+            if ($status['cmid'] == $forum->cmid) {
+                $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
+                $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
+                $this->assertEquals($teacher->id, $status['overrideby']);
+                break;
+            }
+        }
+
+        // Change teacher role capabilities (disable access all groups).
         $context = context_course::instance($course->id);
         assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
         accesslib_clear_all_caches_for_unit_testing();
index 3b75249..e5c8906 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index 76294eb..29ae071 100644 (file)
@@ -448,6 +448,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                 $(ele).prop('selected', true);
             }
         });
+
         // Rerender the selection list.
         updateSelectionList(options, state, originalSelect);
         // Notifiy that the selection changed.
@@ -478,6 +479,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
      * @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results.
      */
     var updateAjax = function(e, options, state, originalSelect, ajaxHandler) {
+        var pendingKey = 'form-autocomplete-updateajax';
+        M.util.js_pending(pendingKey);
         // Get the query to pass to the ajax function.
         var query = $(e.currentTarget).val();
         // Call the transport function to do the ajax (name taken from Select2).
@@ -514,7 +517,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             });
             // Update the list of suggestions now from the new values in the select list.
             updateSuggestions(options, state, '', originalSelect);
-        }, notification.exception);
+            M.util.js_complete(pendingKey);
+        }, function(error) {
+            M.util.js_complete(pendingKey);
+            notification.exception(error);
+        });
     };
 
     /**
@@ -531,11 +538,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
         var inputElement = $(document.getElementById(state.inputId));
         // Add keyboard nav with keydown.
         inputElement.on('keydown', function(e) {
+            var pendingKey = 'form-autocomplete-addnav-' + state.inputId + '-' + e.keyCode;
+            M.util.js_pending(pendingKey);
+
             switch (e.keyCode) {
                 case KEYS.DOWN:
                     // If the suggestion list is open, move to the next item.
                     if (!options.showSuggestions) {
                         // Do not consume this event.
+                        M.util.js_complete(pendingKey);
                         return true;
                     } else if (inputElement.attr('aria-expanded') === "true") {
                         activateNextItem(state);
@@ -552,12 +563,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     }
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
                 case KEYS.UP:
                     // Choose the previous active item.
                     activatePreviousItem(state);
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
                 case KEYS.ENTER:
                     var suggestionsElement = $(document.getElementById(state.suggestionsId));
@@ -571,6 +584,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     }
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
                 case KEYS.ESCAPE:
                     if (inputElement.attr('aria-expanded') === "true") {
@@ -579,12 +593,16 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     }
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
             }
+            M.util.js_complete(pendingKey);
             return true;
         });
         // Support multi lingual COMMA keycode (44).
         inputElement.on('keypress', function(e) {
+            var pendingKey = 'form-autocomplete-keypress-' + e.keyCode;
+            M.util.js_pending(pendingKey);
             if (e.keyCode === KEYS.COMMA) {
                 if (options.tags) {
                     // If we are allowing tags, comma should create a tag (or enter).
@@ -592,13 +610,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                 }
                 // We handled this event, so prevent it.
                 e.preventDefault();
+                M.util.js_complete(pendingKey);
                 return false;
             }
+            M.util.js_complete(pendingKey);
             return true;
         });
         // Handler used to force set the value from behat.
         inputElement.on('behat:set-value', function() {
             var suggestionsElement = $(document.getElementById(state.suggestionsId));
+            var pendingKey = 'form-autocomplete-behat';
+            M.util.js_pending(pendingKey);
             if ((inputElement.attr('aria-expanded') === "true") &&
                     (suggestionsElement.children('[aria-selected=true]').length > 0)) {
                 // If the suggestion list has an active item, select it.
@@ -607,8 +629,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                 // If tags are enabled, create a tag.
                 createItem(options, state, originalSelect);
             }
+            M.util.js_complete(pendingKey);
         });
         inputElement.on('blur', function() {
+            var pendingKey = 'form-autocomplete-blur';
+            M.util.js_pending(pendingKey);
             window.setTimeout(function() {
                 // Get the current element with focus.
                 var focusElement = $(document.activeElement);
@@ -619,11 +644,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     }
                     closeSuggestions(state);
                 }
+                M.util.js_complete(pendingKey);
             }, 500);
         });
         if (options.showSuggestions) {
             var arrowElement = $(document.getElementById(state.downArrowId));
             arrowElement.on('click', function(e) {
+                var pendingKey = 'form-autocomplete-show-suggestions';
+                M.util.js_pending(pendingKey);
                 // Prevent the close timer, or we will open, then close the suggestions.
                 inputElement.focus();
                 // Handle ajax population of suggestions.
@@ -635,11 +663,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     // Else - open the suggestions list.
                     updateSuggestions(options, state, inputElement.val(), originalSelect);
                 }
+                M.util.js_complete(pendingKey);
             });
         }
 
         var suggestionsElement = $(document.getElementById(state.suggestionsId));
         suggestionsElement.parent().on('click', '[role=option]', function(e) {
+            var pendingKey = 'form-autocomplete-parent';
+            M.util.js_pending(pendingKey);
             // Handle clicks on suggestions.
             var element = $(e.currentTarget).closest('[role=option]');
             var suggestionsElement = $(document.getElementById(state.suggestionsId));
@@ -649,29 +680,37 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             activateItem(current, state);
             // And select it.
             selectCurrentItem(options, state, originalSelect);
+            M.util.js_complete(pendingKey);
         });
         var selectionElement = $(document.getElementById(state.selectionId));
         // Handle clicks on the selected items (will unselect an item).
         selectionElement.on('click', '[role=listitem]', function(e) {
+            var pendingKey = 'form-autocomplete-clicks';
+            M.util.js_pending(pendingKey);
             // Get the item that was clicked.
             var item = $(e.currentTarget);
             // Remove it from the selection.
             deselectItem(options, state, item, originalSelect);
+            M.util.js_complete(pendingKey);
         });
         // Keyboard navigation for the selection list.
         selectionElement.on('keydown', function(e) {
+            var pendingKey = 'form-autocomplete-keydown-' + e.keyCode;
+            M.util.js_pending(pendingKey);
             switch (e.keyCode) {
                 case KEYS.DOWN:
                     // Choose the next selection item.
                     activateNextSelection(state);
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
                 case KEYS.UP:
                     // Choose the previous selection item.
                     activatePreviousSelection(state);
                     // We handled this event, so prevent it.
                     e.preventDefault();
+                    M.util.js_complete(pendingKey);
                     return false;
                 case KEYS.SPACE:
                 case KEYS.ENTER:
@@ -683,8 +722,10 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                         // We handled this event, so prevent it.
                         e.preventDefault();
                     }
+                    M.util.js_complete(pendingKey);
                     return false;
             }
+            M.util.js_complete(pendingKey);
             return true;
         });
         // Whenever the input field changes, update the suggestion list.
@@ -693,8 +734,10 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             if (options.ajax) {
                 require([options.ajax], function(ajaxHandler) {
                     var throttleTimeout = null;
+                    var pendingKey = 'autocomplete-throttledhandler';
                     var handler = function(e) {
                         updateAjax(e, options, state, originalSelect, ajaxHandler);
+                        M.util.js_complete(pendingKey);
                     };
 
                     // For input events, we do not want to trigger many, many updates.
@@ -702,6 +745,9 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                         if (throttleTimeout !== null) {
                             window.clearTimeout(throttleTimeout);
                             throttleTimeout = null;
+                        } else {
+                            // No existing timeout handler, so this is the start of a throttling check.
+                            M.util.js_pending(pendingKey);
                         }
                         throttleTimeout = window.setTimeout(handler.bind(this, e), 300);
                     };
@@ -756,6 +802,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                 showSuggestions: true,
                 noSelectionString: noSelectionString
             };
+            var pendingKey = 'autocomplete-setup-' + selector;
+            M.util.js_pending(pendingKey);
             if (typeof tags !== "undefined") {
                 options.tags = tags;
             }
@@ -778,6 +826,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             var originalSelect = $(selector);
             if (!originalSelect) {
                 log.debug('Selector not found: ' + selector);
+                M.util.js_complete(pendingKey);
                 return false;
             }
 
@@ -839,7 +888,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
 
                 // Show the current values in the selection list.
                 updateSelectionList(options, state, originalSelect);
+                M.util.js_complete(pendingKey);
                 return true;
+            }).fail(function(error) {
+                M.util.js_complete(pendingKey);
+                notification.exception(error);
             });
         }
     };
index e7fc3e4..bad8317 100644 (file)
@@ -3846,7 +3846,7 @@ function glossary_get_entries_by_search($glossary, $context, $query, $fullsearch
     $count = $DB->count_records_sql("SELECT COUNT(DISTINCT(ge.id)) $sqlfrom $sqlwhere", $params);
 
     $query = "$sqlwrapheader $sqlselect $sqlfrom $sqlwhere $sqlwrapfooter $sqlorderby";
-    $entries = $DB->get_recordset_sql($query, $params, $from, $limit);
+    $entries = $DB->get_records_sql($query, $params, $from, $limit);
 
     return array($entries, $count);
 }
index 89ee999..641284f 100644 (file)
@@ -103,7 +103,12 @@ function lti_get_launch_data($instance) {
         if ($tool) {
             $typeid = $tool->id;
         } else {
-            $typeid = null;
+            $tool = lti_get_tool_by_url_match($instance->securetoolurl,  $instance->course);
+            if ($tool) {
+                $typeid = $tool->id;
+            } else {
+                $typeid = null;
+            }
         }
     } else {
         $typeid = $instance->typeid;
index 517bbb6..d545803 100644 (file)
@@ -88,7 +88,7 @@ class profile_field_base {
         $this->set_userid($userid);
         if ($fielddata) {
             $this->set_field($fielddata);
-            if ($userid && !empty($fielddata->hasuserdata)) {
+            if ($userid > 0 && !empty($fielddata->hasuserdata)) {
                 $this->set_user_data($fielddata->data, $fielddata->dataformat);
             }
         } else {
@@ -395,7 +395,7 @@ class profile_field_base {
             $this->set_field($field);
         }
 
-        if (!empty($this->field) && $this->userid) {
+        if (!empty($this->field) && $this->userid > 0) {
             $params = array('userid' => $this->userid, 'fieldid' => $this->fieldid);
             if ($data = $DB->get_record('user_info_data', $params, 'data, dataformat')) {
                 $this->set_user_data($data->data, $data->dataformat);
@@ -413,7 +413,7 @@ class profile_field_base {
     public function is_visible() {
         global $USER;
 
-        $context = $this->userid ? context_user::instance($this->userid) : context_system::instance();
+        $context = ($this->userid > 0) ? context_user::instance($this->userid) : context_system::instance();
 
         switch ($this->field->visible) {
             case PROFILE_VISIBLE_ALL:
@@ -507,12 +507,12 @@ function profile_get_user_fields_with_data($userid) {
 
     // Join any user info data present with each user info field for the user object.
     $sql = 'SELECT uif.*, uic.name AS categoryname ';
-    if ($userid) {
+    if ($userid > 0) {
         $sql .= ', uind.id AS hasuserdata, uind.data, uind.dataformat ';
     }
     $sql .= 'FROM {user_info_field} uif ';
     $sql .= 'LEFT JOIN {user_info_category} uic ON uif.categoryid = uic.id ';
-    if ($userid) {
+    if ($userid > 0) {
         $sql .= 'LEFT JOIN {user_info_data} uind ON uif.id = uind.fieldid AND uind.userid = :userid ';
     }
     $sql .= 'ORDER BY uic.sortorder ASC, uif.sortorder ASC ';