Merge branch 'MDL-56954-master' of git://github.com/lameze/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Feb 2017 05:20:26 +0000 (06:20 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 7 Feb 2017 05:20:26 +0000 (06:20 +0100)
21 files changed:
admin/tool/usertours/tests/manager_test.php
enrol/lti/tests/helper_test.php
lib/amd/build/form-autocomplete.min.js
lib/amd/src/form-autocomplete.js
lib/classes/external/exporter.php
lib/db/services.php
lib/tests/exporter_test.php
message/output/popup/tests/externallib_test.php
mod/forum/classes/output/big_search_form.php
mod/forum/search.php
mod/forum/templates/big_search_form.mustache
mod/quiz/tests/behat/editing_section_headings.feature
question/type/gapselect/questionbase.php
question/type/match/question.php
question/type/multichoice/question.php
question/type/questionbase.php
question/type/upgrade.txt
rss/file.php
theme/boost/templates/mod_forum/big_search_form.mustache
user/tests/externallib_test.php
version.php

index 321ff06..bffecb3 100644 (file)
@@ -118,7 +118,7 @@ class tool_usertours_manager_testcase extends advanced_testcase {
         $rcm = $rc->getMethod($function);
         $rcm->setAccessible(true);
 
-        $this->setExpectedException('moodle_exception', 'A required parameter (sesskey) was missing');
+        $this->expectException('moodle_exception');
         $rcm->invokeArgs($manager, $arguments);
     }
 
index 6583665..09c5804 100644 (file)
@@ -516,7 +516,7 @@ class enrol_lti_helper_testcase extends advanced_testcase {
         $document->load(realpath(__DIR__ . '/fixtures/input.xml'));
         $xpath = new \DOMXpath($document);
 
-        $this->setExpectedException("coding_exception");
+        $this->expectException('coding_exception');
         $function->invokeArgs(null, [$xpath, $parameters]);
     }
 
index a29e064..17220a3 100644 (file)
Binary files a/lib/amd/build/form-autocomplete.min.js and b/lib/amd/build/form-autocomplete.min.js differ
index ca7f034..361d0e1 100644 (file)
@@ -33,7 +33,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
         ENTER: 13,
         SPACE: 32,
         ESCAPE: 27,
-        COMMA: 188,
+        COMMA: 44,
         UP: 38
     };
 
@@ -543,14 +543,6 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
                     // We handled this event, so prevent it.
                     e.preventDefault();
                     return false;
-                case KEYS.COMMA:
-                    if (options.tags) {
-                        // If we are allowing tags, comma should create a tag (or enter).
-                        createItem(options, state, originalSelect);
-                    }
-                    // We handled this event, so prevent it.
-                    e.preventDefault();
-                    return false;
                 case KEYS.UP:
                     // Choose the previous active item.
                     activatePreviousItem(state);
@@ -581,6 +573,19 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
             }
             return true;
         });
+        // Support multi lingual COMMA keycode (44).
+        inputElement.on('keypress', function(e) {
+            if (e.keyCode === KEYS.COMMA) {
+                if (options.tags) {
+                    // If we are allowing tags, comma should create a tag (or enter).
+                    createItem(options, state, originalSelect);
+                }
+                // We handled this event, so prevent it.
+                e.preventDefault();
+                return false;
+            }
+            return true;
+        });
         // Handler used to force set the value from behat.
         inputElement.on('behat:set-value', function() {
             var suggestionsElement = $(document.getElementById(state.suggestionsId));
index 9539e54..dcfeb4e 100644 (file)
@@ -96,7 +96,11 @@ abstract class exporter {
                 }
 
             } else {
-                if (array_key_exists($key, $related) && $related[$key] instanceof $classname) {
+                $scalartypes = ['string', 'int', 'bool', 'float'];
+                $scalarcheck = 'is_' . $classname;
+                if (array_key_exists($key, $related) &&
+                        ((in_array($classname, $scalartypes) && $scalarcheck($related[$key])) ||
+                        ($related[$key] instanceof $classname))) {
                     $this->related[$key] = $related[$key];
                 } else {
                     throw new coding_exception($missingdataerr . $key . ' => ' . $classname);
@@ -262,6 +266,9 @@ abstract class exporter {
             if (!isset($definition['null'])) {
                 $customprops[$property]['null'] = NULL_NOT_ALLOWED;
             }
+            if (!isset($definition['description'])) {
+                $customprops[$property]['description'] = $property;
+            }
         }
         $properties += $customprops;
         return $properties;
@@ -280,6 +287,9 @@ abstract class exporter {
             if (!isset($definition['null'])) {
                 $properties[$property]['null'] = NULL_NOT_ALLOWED;
             }
+            if (!isset($definition['description'])) {
+                $properties[$property]['description'] = $property;
+            }
         }
         return $properties;
     }
@@ -331,7 +341,8 @@ abstract class exporter {
      * Return the list of properties.
      *
      * The format of the array returned by this method has to match the structure
-     * defined in {@link \core\persistent::define_properties()}.
+     * defined in {@link \core\persistent::define_properties()}. Howewer you can
+     * add a new attribute "description" to describe the parameter for documenting the API.
      *
      * Note that the type PARAM_TEXT should ONLY be used for strings which need to
      * go through filters (multilang, etc...) and do not have a FORMAT_* associated
@@ -441,7 +452,8 @@ abstract class exporter {
                 $returns += self::get_context_structure();
 
             } else {
-                $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']);
+                $returns[$property] = new external_value($definition['type'], $definition['description'], $required, $default,
+                    $definition['null']);
 
                 // Magically treat the format properties.
                 if ($formatproperty = self::get_format_field($properties, $property)) {
@@ -504,10 +516,11 @@ abstract class exporter {
                     // PARAM_TEXT always becomes PARAM_RAW because filters may be applied.
                     $type = PARAM_RAW;
                 }
-                $thisvalue = new external_value($type, $property, $proprequired, $propdefault, $definition['null']);
+                $thisvalue = new external_value($type, $definition['description'], $proprequired, $propdefault, $definition['null']);
             }
             if (!empty($definition['multiple'])) {
-                $returns[$property] = new external_multiple_structure($thisvalue, '', $proprequired, $propdefault);
+                $returns[$property] = new external_multiple_structure($thisvalue, $definition['description'], $proprequired,
+                    $propdefault);
             } else {
                 $returns[$property] = $thisvalue;
 
@@ -556,7 +569,8 @@ abstract class exporter {
                 $returns += self::get_context_structure();
 
             } else {
-                $returns[$property] = new external_value($definition['type'], $property, $required, $default, $definition['null']);
+                $returns[$property] = new external_value($definition['type'], $definition['description'], $required, $default,
+                    $definition['null']);
 
                 // Magically treat the format properties.
                 if ($formatproperty = self::get_format_field($properties, $property)) {
index 9dbead8..afabd03 100644 (file)
@@ -702,6 +702,7 @@ $functions = array(
         'description' => 'Retrieve the template data for the conversation list',
         'type' => 'read',
         'ajax' => true,
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_message_data_for_messagearea_contacts' => array(
         'classname' => 'core_message_external',
@@ -710,6 +711,7 @@ $functions = array(
         'description' => 'Retrieve the template data for the contact list',
         'type' => 'read',
         'ajax' => true,
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_message_data_for_messagearea_messages' => array(
         'classname' => 'core_message_external',
@@ -718,6 +720,7 @@ $functions = array(
         'description' => 'Retrieve the template data for the messages',
         'type' => 'read',
         'ajax' => true,
+        'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
     ),
     'core_message_data_for_messagearea_get_most_recent_message' => array(
         'classname' => 'core_message_external',
index 3c12607..f456628 100644 (file)
@@ -41,8 +41,22 @@ class core_exporter_testcase extends advanced_testcase {
 
     public function setUp() {
         $s = new stdClass();
-        $this->validrelated = array('simplestdClass' => $s, 'arrayofstdClass' => array($s, $s), 'context' => null);
-        $this->invalidrelated = array('simplestdClass' => 'a string', 'arrayofstdClass' => 5, 'context' => null);
+        $this->validrelated = array(
+            'simplestdClass' => $s,
+            'arrayofstdClass' => array($s, $s),
+            'context' => null,
+            'aint' => 5,
+            'astring' => 'valid string',
+            'abool' => false
+        );
+        $this->invalidrelated = array(
+            'simplestdClass' => 'a string',
+            'arrayofstdClass' => 5,
+            'context' => null,
+            'aint' => false,
+            'astring' => 4,
+            'abool' => 'not a boolean'
+        );
 
         $this->validdata = array('stringA' => 'A string', 'stringAformat' => FORMAT_HTML, 'intB' => 4);
 
@@ -153,6 +167,19 @@ class core_exporter_testcase extends advanced_testcase {
         $this->assertEquals($expected, $result->stringA);
         $this->assertEquals(FORMAT_HTML, $result->stringAformat);
     }
+
+    public function test_properties_description() {
+        $properties = core_testable_exporter::read_properties_definition();
+        // Properties default description.
+        $this->assertEquals('stringA', $properties['stringA']['description']);
+        $this->assertEquals('stringAformat', $properties['stringAformat']['description']);
+        // Properties custom description.
+        $this->assertEquals('intB description', $properties['intB']['description']);
+        // Other properties custom description.
+        $this->assertEquals('otherstring description', $properties['otherstring']['description']);
+        // Other properties default description.
+        $this->assertEquals('otherstrings', $properties['otherstrings']['description']);
+    }
 }
 
 /**
@@ -166,7 +193,8 @@ class core_testable_exporter extends \core\external\exporter {
 
     protected static function define_related() {
         // We cache the context so it does not need to be retrieved from the course.
-        return array('simplestdClass' => 'stdClass', 'arrayofstdClass' => 'stdClass[]', 'context' => 'context?');
+        return array('simplestdClass' => 'stdClass', 'arrayofstdClass' => 'stdClass[]', 'context' => 'context?',
+            'astring' => 'string', 'abool' => 'bool', 'aint' => 'int');
     }
 
     protected function get_other_values(renderer_base $output) {
@@ -186,6 +214,7 @@ class core_testable_exporter extends \core\external\exporter {
             ),
             'intB' => array(
                 'type' => PARAM_INT,
+                'description' => 'intB description',
             )
         );
     }
@@ -194,6 +223,7 @@ class core_testable_exporter extends \core\external\exporter {
         return array(
             'otherstring' => array(
                 'type' => PARAM_TEXT,
+                'description' => 'otherstring description',
             ),
             'otherstrings' => array(
                 'type' => PARAM_TEXT,
index 137573b..a8ca638 100644 (file)
@@ -57,7 +57,7 @@ class message_popup_externallib_testcase extends advanced_testcase {
     public function test_get_popup_notifications_no_user_exception() {
         $this->resetAfterTest(true);
 
-        $this->setExpectedException('moodle_exception');
+        $this->expectException('moodle_exception');
         $result = message_popup_external::get_popup_notifications(-2132131, false, 0, 0);
     }
 
@@ -72,7 +72,7 @@ class message_popup_externallib_testcase extends advanced_testcase {
         $user = $this->getDataGenerator()->create_user();
 
         $this->setUser($user);
-        $this->setExpectedException('moodle_exception');
+        $this->expectException('moodle_exception');
         $result = message_popup_external::get_popup_notifications($sender->id, false, 0, 0);
     }
 
@@ -141,7 +141,7 @@ class message_popup_externallib_testcase extends advanced_testcase {
     public function test_get_unread_popup_notification_count_invalid_user_exception() {
         $this->resetAfterTest(true);
 
-        $this->setExpectedException('moodle_exception');
+        $this->expectException('moodle_exception');
         $result = message_popup_external::get_unread_popup_notification_count(-2132131, 0);
     }
 
@@ -156,7 +156,7 @@ class message_popup_externallib_testcase extends advanced_testcase {
         $user = $this->getDataGenerator()->create_user();
 
         $this->setUser($user);
-        $this->setExpectedException('moodle_exception');
+        $this->expectException('moodle_exception');
         $result = message_popup_external::get_unread_popup_notification_count($sender->id, 0);
     }
 
index 6a77898..8145103 100644 (file)
@@ -148,6 +148,15 @@ class big_search_form implements renderable, templatable {
         $this->words = $value;
     }
 
+    /**
+     * Forum ID setter search criteria.
+     *
+     * @param int $forumid The forum ID.
+     */
+    public function set_forumid($forumid) {
+        $this->forumid = $forumid;
+    }
+
     public function export_for_template(renderer_base $output) {
         $data = new stdClass();
 
@@ -185,6 +194,15 @@ class big_search_form implements renderable, templatable {
                             . html_writer::select_time('hours', 'tohour', $dateto)
                             . html_writer::select_time('minutes', 'tominute', $dateto);
 
+        if ($this->forumid && !empty($this->forumoptions)) {
+            foreach ($this->forumoptions as $index => $option) {
+                if ($option['value'] == $this->forumid) {
+                    $this->forumoptions[$index]['selected'] = true;
+                } else {
+                    $this->forumoptions[$index]['selected'] = false;
+                }
+            }
+        }
         $data->forumoptions = $this->forumoptions;
 
         return $data;
index 931d045..ec399f9 100644 (file)
@@ -318,7 +318,7 @@ echo $OUTPUT->footer();
   * @return void The function prints the form.
   */
 function forum_print_big_search_form($course) {
-    global $PAGE, $words, $subject, $phrase, $user, $userid, $fullwords, $notwords, $datefrom, $dateto, $OUTPUT;
+    global $PAGE, $words, $subject, $phrase, $user, $fullwords, $notwords, $datefrom, $dateto, $forumid;
 
     $renderable = new \mod_forum\output\big_search_form($course, $user);
     $renderable->set_words($words);
@@ -329,6 +329,7 @@ function forum_print_big_search_form($course) {
     $renderable->set_dateto($dateto);
     $renderable->set_subject($subject);
     $renderable->set_user($user);
+    $renderable->set_forumid($forumid);
 
     $output = $PAGE->get_renderer('mod_forum');
     echo $output->render($renderable);
index 6fc760c..f9371f2 100644 (file)
                 <td class="c1">
                     <select name="forumid" id="menuforumid">
                         {{#forumoptions}}
-                            <option value="{{value}}">{{name}}</option>
+                            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
                         {{/forumoptions}}
                     </select>
                 </td>
index fb12326..aa6ac70 100644 (file)
@@ -149,6 +149,7 @@ Feature: Edit quiz page - section headings
     And I follow "Remove heading 'Heading 2'"
     And I should see "Are you sure you want to remove the 'Heading 2' section heading?"
     And I click on "Yes" "button" in the "Confirm" "dialogue"
+    And I wait until the page is ready
     And I wait until "Heading 2" "text" does not exist
     Then I should see "Heading 1"
     And I should not see "Heading 2"
index 180a448..76a3882 100644 (file)
@@ -321,7 +321,7 @@ abstract class qtype_gapselect_question_base extends question_graded_automatical
     public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
         if ($component == 'question' && in_array($filearea,
                 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
-            return $this->check_combined_feedback_file_access($qa, $options, $filearea);
+            return $this->check_combined_feedback_file_access($qa, $options, $filearea, $args);
 
         } else if ($component == 'question' && $filearea == 'hint') {
             return $this->check_hint_file_access($qa, $options, $args);
index b373fba..4113010 100644 (file)
@@ -342,7 +342,7 @@ class qtype_match_question extends question_graded_automatically_with_countback
 
         } else if ($component == 'question' && in_array($filearea,
                 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
-            return $this->check_combined_feedback_file_access($qa, $options, $filearea);
+            return $this->check_combined_feedback_file_access($qa, $options, $filearea, $args);
 
         } else if ($component == 'question' && $filearea == 'hint') {
             return $this->check_hint_file_access($qa, $options, $args);
index 2c5e17c..09b9493 100644 (file)
@@ -113,7 +113,7 @@ abstract class qtype_multichoice_base extends question_graded_automatically {
     public function check_file_access($qa, $options, $component, $filearea, $args, $forcedownload) {
         if ($component == 'question' && in_array($filearea,
                 array('correctfeedback', 'partiallycorrectfeedback', 'incorrectfeedback'))) {
-            return $this->check_combined_feedback_file_access($qa, $options, $filearea);
+            return $this->check_combined_feedback_file_access($qa, $options, $filearea, $args);
 
         } else if ($component == 'question' && $filearea == 'answer') {
             $answerid = reset($args); // Itemid is answer id.
index 879381e..b6a4b89 100644 (file)
@@ -668,11 +668,18 @@ abstract class question_graded_automatically extends question_with_responses
      * @param question_attempt $qa the question attempt being displayed.
      * @param question_display_options $options the options that control display of the question.
      * @param string $filearea the name of the file area.
+     * @param array $args the remaining bits of the file path.
      * @return bool whether access to the file should be allowed.
      */
-    protected function check_combined_feedback_file_access($qa, $options, $filearea) {
+    protected function check_combined_feedback_file_access($qa, $options, $filearea, $args = null) {
         $state = $qa->get_state();
 
+        if ($args === null) {
+            debugging('You must pass $args as the fourth argument to check_combined_feedback_file_access.',
+                    DEBUG_DEVELOPER);
+            $args = array($this->id); // Fake it for now, so the rest of this method works.
+        }
+
         if (!$state->is_finished()) {
             $response = $qa->get_last_qt_data();
             if (!$this->is_gradable_response($response)) {
index 470629c..6a74061 100644 (file)
@@ -1,5 +1,11 @@
 This files describes API changes for question type plugins.
 
+=== 3.1.5, 3.2.2, 3.3 ===
+
+* If you are using check_combined_feedback_file_access in your check_file_access method,
+  then you must now pass $args as the 4th argument, so the correct permission checks
+  can be performed. If you don't, you will get a developer debug notice.
+
 === 3.1 ===
 
 * The following functions, previously used (exclusively) by upgrade steps are not available
index b0bd7a2..9a31a11 100644 (file)
@@ -115,7 +115,11 @@ if ($token === "$inttoken") {
 }
 
 // Check the context actually exists.
-list($context, $course, $cm) = get_context_info_array($contextid);
+try {
+    list($context, $course, $cm) = get_context_info_array($contextid);
+} catch (dml_missing_record_exception $e) {
+    rss_error();
+}
 
 $PAGE->set_context($context);
 
index c376145..241d842 100644 (file)
                 <td class="c1">
                     <select name="forumid" id="menuforumid" class="form-control">
                         {{#forumoptions}}
-                            <option value="{{value}}">{{name}}</option>
+                            <option value="{{value}}" {{#selected}}selected{{/selected}}>{{name}}</option>
                         {{/forumoptions}}
                     </select>
                 </td>
index d7345e7..16405ae 100644 (file)
@@ -868,7 +868,7 @@ class core_user_externallib_testcase extends externallib_advanced_testcase {
         $anotheruser = self::getDataGenerator()->create_user();
         $this->setUser($anotheruser);
 
-        $this->setExpectedException('required_capability_exception');
+        $this->expectException('required_capability_exception');
         $result = core_user_external::get_user_preferences('', $user->id);
     }
 
index a795565..b806b7e 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2017020200.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2017020200.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.