Merge branch 'MDL-50545-master' of git://github.com/jleyva/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 24 Jan 2017 09:15:50 +0000 (09:15 +0000)
committerDan Poltawski <dan@moodle.com>
Tue, 24 Jan 2017 09:15:50 +0000 (09:15 +0000)
248 files changed:
.eslintignore
.gherkin-lintrc [new file with mode: 0644]
.stylelintignore
Gruntfile.js
admin/tool/behat/tests/behat/get_and_set_fields.feature
admin/tool/behat/tests/behat/list_steps.feature
admin/tool/langimport/classes/output/langimport_page.php [new file with mode: 0644]
admin/tool/langimport/classes/output/renderer.php [new file with mode: 0644]
admin/tool/langimport/index.php
admin/tool/langimport/styles.css
admin/tool/langimport/templates/langimport.mustache [new file with mode: 0644]
admin/tool/langimport/tests/behat/manage_langpacks.feature
admin/tool/lp/tests/behat/framework_crud.feature
admin/tool/lp/tests/behat/plan_workflow.feature
admin/tool/lp/tests/behat/user_evidence_crud.feature
admin/tool/mobile/classes/api.php
admin/tool/mobile/lang/en/tool_mobile.php
admin/tool/mobile/settings.php
admin/tool/mobile/tests/externallib_test.php
admin/tool/mobile/version.php
auth/cas/config.html
auth/classes/external.php
auth/email/classes/external.php
auth/ldap/auth.php
auth/ldap/config.html
auth/ldap/lang/en/auth_ldap.php
badges/classes/external.php
badges/mybackpack.php
badges/mybadges.php
badges/tests/behat/award_badge.feature
blocks/activity_results/tests/behat/addblockinactivity.feature
blocks/activity_results/tests/behat/addunconfiguredblock.feature
blocks/activity_results/tests/behat/addunsupportedactivity.feature
blocks/activity_results/tests/behat/highscoreswithoutgroups.feature
blocks/activity_results/tests/behat/highscoreswithscales.feature
blocks/activity_results/tests/behat/highscoreswithscalesandgroups.feature
blocks/activity_results/tests/behat/highscoreswithseperategroups.feature
blocks/activity_results/tests/behat/highscoreswithvisiblegroups.feature
blocks/activity_results/tests/behat/lowscoreswithoutgroups.feature
blocks/activity_results/tests/behat/lowscoreswithscales.feature
blocks/activity_results/tests/behat/lowscoreswithscalesandgroups.feature
blocks/activity_results/tests/behat/lowscoreswithseperategroups.feature
blocks/activity_results/tests/behat/lowscoreswithvisiblegroups.feature
blocks/blog_menu/tests/behat/block_blog_menu_course.feature
blocks/calendar_upcoming/tests/behat/block_calendar_upcoming_dashboard.feature
blocks/completionstatus/tests/behat/block_completionstatus_manual_other.feature
blocks/course_overview/tests/behat/quiz_overview.feature
blocks/glossary_random/tests/behat/glossary_random_frontpage.feature
blocks/html/tests/behat/multiple_instances.feature
blocks/online_users/tests/behat/block_online_users_dashboard.feature
blocks/online_users/tests/behat/block_online_users_frontpage.feature
blocks/private_files/tests/behat/block_private_files_activity.feature
blocks/private_files/tests/behat/block_private_files_course.feature
blocks/private_files/tests/behat/block_private_files_dashboard.feature
blocks/private_files/tests/behat/block_private_files_frontpage.feature
blocks/search_forums/tests/behat/block_search_forums_course.feature
blocks/search_forums/tests/behat/block_search_forums_frontpage.feature
blocks/section_links/tests/behat/block_section_links_course.feature
blog/tests/behat/delete.feature
cache/classes/loaders.php
cache/stores/redis/addinstanceform.php
cache/stores/redis/lang/en/cachestore_redis.php
cache/stores/redis/lib.php
cache/stores/redis/settings.php
cache/stores/static/lib.php
cache/stores/static/tests/static_test.php
cache/tests/cache_test.php
calendar/tests/behat/calendar.feature
cohort/tests/behat/access_visible_cohorts.feature
completion/classes/external.php
completion/tests/behat/behat_completion.php
completion/tests/behat/enable_manual_complete_mark.feature
composer.json
composer.lock
course/dndupload.js
course/externallib.php
course/format/social/tests/behat/social_adjust_discussion_count.feature
course/tests/behat/category_change_visibility.feature
course/tests/behat/course_search.feature
course/tests/behat/force_group_mode.feature
course/tests/behat/view_subfolders_inline.feature
course/tests/courselib_test.php
course/tests/externallib_test.php
course/upgrade.txt
enrol/lti/db/install.xml
enrol/lti/db/upgrade.php
enrol/lti/version.php
files/tests/behat/add_custom_file_type.feature
grade/report/grader/lib.php
grade/report/grader/tests/behat/ajax_grader.feature
grade/report/overview/classes/external.php
grade/report/user/externallib.php
grade/report/user/tests/behat/view_usereport.feature
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_item_validation.feature
grade/tests/behat/grade_letter_boundary_20160518.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/report_graderlib_test.php
install/lang/ar/error.php
install/lang/es_mx_kids/langconfig.php
install/lang/fa/install.php
install/lang/lv/moodle.php
lib/editor/atto/plugins/accessibilityhelper/tests/behat/accessibilityhelper.feature
lib/editor/atto/plugins/align/tests/behat/align.feature
lib/editor/atto/plugins/clear/tests/behat/clear.feature
lib/editor/atto/plugins/collapse/tests/behat/collapse.feature
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js
lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js
lib/editor/atto/plugins/equation/yui/src/button/js/button.js
lib/editor/atto/plugins/html/tests/behat/html.feature
lib/editor/atto/plugins/link/tests/behat/link.feature
lib/editor/atto/plugins/media/tests/behat/media.feature
lib/editor/atto/plugins/strike/tests/behat/strike.feature
lib/editor/atto/plugins/subscript/tests/behat/subscript.feature
lib/editor/atto/plugins/superscript/tests/behat/superscript.feature
lib/editor/atto/plugins/title/tests/behat/title.feature
lib/editor/atto/plugins/underline/tests/behat/underline.feature
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js
lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js
lib/editor/atto/yui/src/editor/js/clean.js
lib/enrollib.php
lib/filestorage/file_storage.php
lib/filestorage/tests/file_storage_test.php
lib/formslib.php
lib/javascript-static.js
lib/ldaplib.php
lib/moodlelib.php
lib/navigationlib.php
lib/templates/login.mustache
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_hooks.php
lib/upgrade.txt
media/player/videojs/amd/build/Youtube-lazy.min.js [new file with mode: 0644]
media/player/videojs/amd/build/Youtube.min.js [deleted file]
media/player/videojs/amd/build/loader.min.js
media/player/videojs/amd/build/video-lazy.min.js [moved from media/player/videojs/amd/build/video.min.js with 100% similarity]
media/player/videojs/amd/src/Youtube-lazy.js [moved from media/player/videojs/amd/src/Youtube.js with 99% similarity]
media/player/videojs/amd/src/loader.js
media/player/videojs/amd/src/video-lazy.js [moved from media/player/videojs/amd/src/video.js with 100% similarity]
media/player/videojs/classes/plugin.php
media/player/videojs/readme_moodle.txt
media/player/videojs/thirdpartylibs.xml
message/tests/api_test.php
message/tests/behat/manage_contacts.feature
mod/assign/amd/build/participant_selector.min.js
mod/assign/amd/src/participant_selector.js
mod/assign/externallib.php
mod/assign/feedback/editpdf/tests/behat/view_previous_annotations.feature
mod/assign/feedback/offline/locallib.php
mod/assign/lang/en/assign.php
mod/assign/renderer.php
mod/assign/tests/behat/display_error_message_onbadformat.feature
mod/assign/tests/behat/submit_without_group.feature
mod/book/classes/external.php
mod/book/tests/behat/show_hide_chapters.feature
mod/chat/classes/external.php
mod/choice/classes/external.php
mod/choice/tests/behat/block_editing.feature
mod/data/classes/external.php
mod/data/tests/behat/required_entries.feature
mod/feedback/item/captcha/lib.php
mod/feedback/item/feedback_item_class.php
mod/feedback/item/label/lib.php
mod/feedback/item/multichoice/lib.php
mod/feedback/item/multichoicerated/lib.php
mod/feedback/yui/dragdrop/dragdrop.js
mod/forum/backup/moodle2/restore_forum_stepslib.php
mod/forum/classes/output/big_search_form.php
mod/forum/externallib.php
mod/forum/forum.js [deleted file]
mod/forum/lib.php
mod/forum/templates/big_search_form.mustache
mod/forum/tests/behat/discussion_subscriptions.feature
mod/forum/tests/behat/post_to_multiple_groups.feature
mod/forum/tests/externallib_test.php
mod/forum/tests/lib_test.php
mod/forum/upgrade.txt
mod/glossary/classes/external.php
mod/glossary/tests/behat/prevent_duplicate_entries.feature
mod/glossary/tests/external_test.php
mod/imscp/classes/external.php
mod/label/classes/external.php [new file with mode: 0644]
mod/label/db/services.php [new file with mode: 0644]
mod/label/tests/externallib_test.php [new file with mode: 0644]
mod/label/version.php
mod/lesson/report.php
mod/lesson/tests/behat/import_images.feature
mod/lesson/tests/behat/lesson_edit_cluster.feature
mod/lesson/tests/behat/lesson_group_override.feature
mod/lesson/tests/behat/lesson_practice.feature
mod/lesson/tests/behat/lesson_with_clusters.feature
mod/lesson/tests/behat/lesson_with_subcluster.feature
mod/lti/tests/behat/contentitemregistration.feature
mod/quiz/classes/external.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/overview/tests/report_test.php
mod/quiz/tests/behat/attempt_basic.feature
mod/quiz/tests/behat/attempt_require_previous.feature
mod/quiz/tests/behat/editing_section_headings.feature
mod/quiz/tests/behat/editing_set_marks_no_attempts.feature
mod/quiz/tests/behat/editing_set_marks_with_attempts.feature
mod/quiz/tests/behat/quiz_reset.feature
mod/resource/classes/external.php
mod/resource/db/services.php
mod/resource/lib.php
mod/resource/tests/externallib_test.php
mod/resource/tests/lib_test.php
mod/resource/version.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/report/basic/classes/report.php
mod/scorm/report/interactions/classes/report.php
mod/scorm/report/objectives/classes/report.php
mod/scorm/tests/behat/completion_condition_require_status.feature
mod/scorm/tests/behat/multisco_review_mode.feature [new file with mode: 0644]
mod/scorm/view.php
mod/survey/classes/external.php
mod/url/classes/external.php
mod/url/db/services.php
mod/url/tests/externallib_test.php
mod/url/version.php
mod/wiki/tests/behat/collaborative_individual.feature
mod/wiki/tests/behat/wiki_search.feature
mod/workshop/tests/behat/workshop_assessment.feature
npm-shrinkwrap.json
package.json
question/tests/behat/copy_questions.feature
question/type/ddmarker/tests/behat/preview.feature
question/type/description/tests/behat/add.feature
question/type/essay/tests/behat/export.feature
question/type/gapselect/tests/behat/import_test.feature
question/type/shortanswer/tests/behat/preview.feature
question/type/truefalse/tests/behat/add.feature
report/loglive/tests/behat/loglive_report.feature
repository/manage_instances.php
theme/boost/classes/output/core_renderer.php
theme/boost/scss/moodle/core.scss
theme/boost/scss/moodle/modules.scss
theme/boost/templates/core/login.mustache
theme/boost/templates/core_form/element-template.mustache
theme/boost/templates/mod_forum/big_search_form.mustache
user/files.php
user/index.php
user/tests/behat/view_preferences_page.feature
version.php

index fe5da7b..452b0df 100644 (file)
@@ -56,7 +56,8 @@ lib/amd/src/chartjs-lazy.js
 lib/maxmind/GeoIp2/
 lib/maxmind/MaxMind/
 lib/ltiprovider/
-media/player/videojs/amd/src/
+media/player/videojs/amd/src/video-lazy.js
+media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
 mod/assign/feedback/editpdf/fpdi/
 repository/s3/S3.php
diff --git a/.gherkin-lintrc b/.gherkin-lintrc
new file mode 100644 (file)
index 0000000..9439956
--- /dev/null
@@ -0,0 +1,20 @@
+{
+  "indentation": ["on",{
+    "Feature": 0,
+    "Background": 2,
+    "Scenario": 2,
+    "Step": 4,
+    "given": 4,
+    "and": 4
+  }],
+  "no-dupe-feature-names": "on",
+  "no-dupe-scenario-names": "off",
+  "no-empty-file": "on",
+  "no-files-without-scenarios": "on",
+  "no-multiple-empty-lines": "on",
+  "no-partially-commented-tag-lines": "on",
+  "no-trailing-spaces": "on",
+  "no-unamed-features": "on",
+  "no-unamed-scenarios": "on",
+  "no-scenario-outlines-without-examples": "on"
+}
index cafa933..48f1959 100644 (file)
@@ -57,7 +57,8 @@ lib/amd/src/chartjs-lazy.js
 lib/maxmind/GeoIp2/
 lib/maxmind/MaxMind/
 lib/ltiprovider/
-media/player/videojs/amd/src/
+media/player/videojs/amd/src/video-lazy.js
+media/player/videojs/amd/src/Youtube-lazy.js
 media/player/videojs/videojs/
 mod/assign/feedback/editpdf/fpdi/
 repository/s3/S3.php
index 5852cc4..4cb9e9e 100644 (file)
@@ -106,7 +106,6 @@ module.exports = function(grunt) {
         return libs;
     };
 
-
     // Project configuration.
     grunt.initConfig({
         eslint: {
@@ -171,6 +170,10 @@ module.exports = function(grunt) {
                 files: ['**/yui/src/**/*.js'],
                 tasks: ['yui']
             },
+            gherkinlint: {
+                files: ['**/tests/behat/*.feature'],
+                tasks: ['gherkinlint']
+            }
         },
         shifter: {
             options: {
@@ -178,6 +181,11 @@ module.exports = function(grunt) {
                 paths: [cwd]
             }
         },
+        gherkinlint: {
+            options: {
+                files: ['**/tests/behat/*.feature'],
+            }
+        },
         stylelint: {
             less: {
                 options: {
@@ -318,6 +326,22 @@ module.exports = function(grunt) {
         }, done);
     };
 
+    tasks.gherkinlint = function() {
+        var done = this.async(),
+            options = grunt.config('gherkinlint.options');
+
+        var args = grunt.file.expand(options.files);
+        args.unshift(path.normalize(__dirname + '/node_modules/.bin/gherkin-lint'));
+        grunt.util.spawn({
+            cmd: 'node',
+            args: args,
+            opts: {stdio: 'inherit', env: process.env}
+        }, function(error, result, code) {
+            // Propagate the exit code.
+            done(code);
+        });
+    };
+
     tasks.startup = function() {
         // Are we in a YUI directory?
         if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
@@ -329,6 +353,7 @@ module.exports = function(grunt) {
             // Run them all!.
             grunt.task.run('css');
             grunt.task.run('js');
+            grunt.task.run('gherkinlint');
         }
     };
 
@@ -343,6 +368,7 @@ module.exports = function(grunt) {
           grunt.config('uglify.amd.files', [{expand: true, src: files, rename: uglifyRename}]);
           grunt.config('shifter.options.paths', files);
           grunt.config('stylelint.less.src', files);
+          grunt.config('gherkinlint.options.files', files);
           changedFiles = Object.create(null);
     }, 200);
 
@@ -360,6 +386,7 @@ module.exports = function(grunt) {
 
     // Register JS tasks.
     grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
+    grunt.registerTask('gherkinlint', 'Run gherkinlint against the current directory', tasks.gherkinlint);
     grunt.registerTask('ignorefiles', 'Generate ignore files for linters', tasks.ignorefiles);
     grunt.registerTask('yui', ['eslint:yui', 'shifter']);
     grunt.registerTask('amd', ['eslint:amd', 'uglify']);
index f9ac3a1..4b467bc 100644 (file)
@@ -125,7 +125,6 @@ Feature: Verify that all form fields values can be get and set
     And I navigate to "Edit settings" node in "Course administration"
     And the field "Course ID number" matches value "Course id number"
 
-
   Scenario: with JS disabled all form fields getters and setters works as expected
 
   @javascript
index 3e4d31e..708a0e7 100644 (file)
@@ -26,4 +26,3 @@ Feature: List the system steps definitions
     Given I set the field "Contains" to "homepage"
     When I press "Filter"
     Then I should see "Opens Moodle homepage."
-
diff --git a/admin/tool/langimport/classes/output/langimport_page.php b/admin/tool/langimport/classes/output/langimport_page.php
new file mode 100644 (file)
index 0000000..97ff743
--- /dev/null
@@ -0,0 +1,110 @@
+<?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/>.
+
+/**
+ * Language import page.
+ *
+ * @package    tool_langimport
+ * @copyright  2016 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_langimport\output;
+defined('MOODLE_INTERNAL') || die();
+
+use moodle_url;
+use renderable;
+use renderer_base;
+use stdClass;
+use templatable;
+
+/**
+ * Language import page class.
+ *
+ * @package    tool_langimport
+ * @copyright  2016 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class langimport_page implements renderable, templatable {
+
+    /** @var array Array of currently installed languages. */
+    protected $installedlanguages;
+
+    /** @var array Array of languages that can be installed. */
+    protected $availablelanguages;
+
+    /** @var moodle_url The URL to be used for uninstalling the selected existing language packs. */
+    protected $uninstallurl;
+
+    /** @var moodle_url The URL to be used for updating the installed language packs. */
+    protected $updateurl;
+
+    /** @var moodle_url The URL to be used for installing the selected language packs to be installed. */
+    protected $installurl;
+
+
+    /**
+     * langimport_page constructor.
+     *
+     * @param array $installedlanguages Array of currently installed languages.
+     * @param array $availablelanguages Array of languages that can be installed.
+     * @param moodle_url $uninstallurl The URL to be used for uninstalling the selected existing language packs.
+     * @param moodle_url $updateurl The URL to be used for updating the installed language packs.
+     * @param moodle_url $installurl The URL to be used for installing the selected language packs to be installed.
+     */
+    public function __construct($installedlanguages, $availablelanguages, $uninstallurl, $updateurl, $installurl) {
+        $this->installedlanguages = $installedlanguages;
+        $this->availablelanguages = $availablelanguages;
+        $this->uninstallurl = $uninstallurl;
+        $this->updateurl = $updateurl;
+        $this->installurl = $installurl;
+    }
+
+    /**
+     * Export the data.
+     *
+     * @param renderer_base $output
+     * @return stdClass
+     */
+    public function export_for_template(renderer_base $output) {
+        $data = new stdClass();
+        $data->uninstallurl = $this->uninstallurl;
+        $data->sesskey = sesskey();
+
+        $data->installedoptions = [];
+        foreach ($this->installedlanguages as $code => $language) {
+            $option = new stdClass();
+            $option->value = $code;
+            $option->text = $language;
+            $data->installedoptions[] = $option;
+        }
+
+        $data->updateurl = $this->updateurl;
+
+        if (!empty($this->availablelanguages)) {
+            $data->toinstalloptions = [];
+            foreach ($this->availablelanguages as $code => $language) {
+                $option = new stdClass();
+                $option->value = $code;
+                $option->text = $language;
+                $data->toinstalloptions[] = $option;
+            }
+            $data->installurl = $this->installurl;
+            $data->caninstall = true;
+        }
+
+        return $data;
+    }
+}
diff --git a/admin/tool/langimport/classes/output/renderer.php b/admin/tool/langimport/classes/output/renderer.php
new file mode 100644 (file)
index 0000000..f74b11e
--- /dev/null
@@ -0,0 +1,50 @@
+<?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/>.
+
+/**
+ * Renderers.
+ *
+ * @package    tool_langimport
+ * @copyright  2016 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_langimport\output;
+
+defined('MOODLE_INTERNAL') || die();
+
+use plugin_renderer_base;
+
+/**
+ * Renderer class.
+ *
+ * @package    tool_langimport
+ * @copyright  2016 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class renderer extends plugin_renderer_base {
+
+    /**
+     * Defer to template.
+     *
+     * @param langimport_page $page
+     * @return string
+     */
+    public function render_langimport_page(langimport_page $page) {
+        $data = $page->export_for_template($this);
+        return parent::render_from_template('tool_langimport/langimport', $data);
+    }
+}
index c5f49da..0e98a5d 100644 (file)
@@ -124,23 +124,22 @@ if ($availablelangs = $controller->availablelangs) {
 } else {
     $remote = false;
     $availablelangs = array();
-    echo $OUTPUT->box_start();
     $a = [
         'src' => $controller->lang_pack_url(),
         'dest' => $CFG->dataroot.'/lang/',
     ];
-    print_string('downloadnotavailable', 'tool_langimport', $a);
-    echo $OUTPUT->box_end();
+    $errormessage = get_string('downloadnotavailable', 'tool_langimport', $a);
+    \core\notification::error($errormessage);
 }
 
 if ($controller->info) {
     $info = implode('<br />', $controller->info);
-    echo $OUTPUT->notification($info, 'notifysuccess');
+    \core\notification::success($info);
 }
 
 if ($controller->errors) {
     $info = implode('<br />', $controller->errors);
-    echo $OUTPUT->notification($info, 'notifyproblem');
+    \core\notification::error($info);
 }
 
 if ($missingparents) {
@@ -155,67 +154,29 @@ if ($missingparents) {
             }
         }
         $info = get_string('missinglangparent', 'tool_langimport', $a);
-        echo $OUTPUT->notification($info, 'notifyproblem');
+        \core\notification::error($info);
     }
 }
 
-echo $OUTPUT->box_start();
-
-echo html_writer::start_tag('table');
-echo html_writer::start_tag('tr');
-
-// list of installed languages
-$url = new moodle_url('/admin/tool/langimport/index.php', array('mode' => DELETION_OF_SELECTED_LANG));
-echo html_writer::start_tag('td', array('valign' => 'top'));
-echo html_writer::start_tag('form', array('id' => 'uninstallform', 'action' => $url->out(), 'method' => 'post'));
-echo html_writer::start_tag('fieldset');
-echo html_writer::label(get_string('installedlangs', 'tool_langimport'), 'menuuninstalllang');
-echo html_writer::empty_tag('br');
-echo html_writer::select($installedlangs, 'uninstalllang[]', '', false, array('size' => 15, 'multiple' => 'multiple'));
-echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
-echo html_writer::empty_tag('br');
-echo html_writer::empty_tag('input', array('id' => 'languninstallbutton',
-                                         'type' => 'submit',
-                                         'value' => get_string('uninstall', 'tool_langimport'))
-                            );
-echo html_writer::end_tag('fieldset');
-echo html_writer::end_tag('form');
+$uninstallurl = new moodle_url('/admin/tool/langimport/index.php', array('mode' => DELETION_OF_SELECTED_LANG));
+$updateurl = null;
 if ($remote) {
-    $url = new moodle_url('/admin/tool/langimport/index.php', array('mode' => UPDATE_ALL_LANG));
-    echo html_writer::start_tag('form', array('id' => 'updateform', 'action' => $url->out(), 'method' => 'post'));
-    echo html_writer::tag('fieldset', html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('updatelangs','tool_langimport'))));
-    echo html_writer::end_tag('form');
+    $updateurl = new moodle_url('/admin/tool/langimport/index.php', array('mode' => UPDATE_ALL_LANG));
 }
-echo html_writer::end_tag('td');
+$installurl = new moodle_url('/admin/tool/langimport/index.php', array('mode' => INSTALLATION_OF_SELECTED_LANG));
 
-// list of available languages
+// List of available languages.
 $options = array();
 foreach ($availablelangs as $alang) {
     if (!empty($alang[0]) and trim($alang[0]) !== 'en' and !$controller->is_installed_lang($alang[0], $alang[1])) {
         $options[$alang[0]] = $alang[2].' &lrm;('.$alang[0].')&lrm;';
     }
 }
-if (!empty($options)) {
-    echo html_writer::start_tag('td', array('valign' => 'top'));
-    $url = new moodle_url('/admin/tool/langimport/index.php', array('mode' => INSTALLATION_OF_SELECTED_LANG));
-    echo html_writer::start_tag('form', array('id' => 'installform', 'action' => $url->out(), 'method' => 'post'));
-    echo html_writer::start_tag('fieldset');
-    echo html_writer::label(get_string('availablelangs','install'), 'menupack');
-    echo html_writer::empty_tag('br');
-    echo html_writer::select($options, 'pack[]', '', false, array('size' => 15, 'multiple' => 'multiple'));
-    echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
-    echo html_writer::empty_tag('br');
-    echo html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('install','tool_langimport')));
-    echo html_writer::end_tag('fieldset');
-    echo html_writer::end_tag('form');
-    echo html_writer::end_tag('td');
-}
 
-echo html_writer::end_tag('tr');
-echo html_writer::end_tag('table');
-echo $OUTPUT->box_end();
+$renderable = new \tool_langimport\output\langimport_page($installedlangs, $options, $uninstallurl, $updateurl, $installurl);
+$output = $PAGE->get_renderer('tool_langimport');
+echo $output->render($renderable);
 
-$uninstallurl = new moodle_url('/admin/tool/langimport/index.php');
 $PAGE->requires->strings_for_js(array('uninstallconfirm', 'uninstall', 'selectlangs', 'noenglishuninstall'),
                                 'tool_langimport');
 $PAGE->requires->yui_module('moodle-core-languninstallconfirm',
@@ -223,4 +184,3 @@ $PAGE->requires->yui_module('moodle-core-languninstallconfirm',
                              array(array('uninstallUrl' => $uninstallurl->out()))
                             );
 echo $OUTPUT->footer();
-die();
index 42d8a69..f23c99b 100644 (file)
@@ -1,9 +1,5 @@
-#page-admin-tool-langimport-index .generalbox table {
+#page-admin-tool-langimport-index .langimport {
     margin: auto;
+    float: none;
     width: 100%;
 }
-
-#page-admin-tool-langimport-index .generalbox,
-#page-admin-tool-langimport-index .generalbox table {
-    text-align: center;
-}
diff --git a/admin/tool/langimport/templates/langimport.mustache b/admin/tool/langimport/templates/langimport.mustache
new file mode 100644 (file)
index 0000000..25dc700
--- /dev/null
@@ -0,0 +1,117 @@
+{{!
+    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/>.
+}}
+{{!
+    @template tool_langimport/langimport
+
+    Template for the language import page.
+
+    Classes required for JS:
+    * none
+
+    Data attributes required for JS:
+    * none
+
+    Context variables required for this template:
+    * sesskey string The session key.
+    * uninstallurl string The URL for the uninstall action.
+    * updateurl string The URL for the update-language-packs action.
+    * installurl string The URL for the install action.
+    * installedoptions array The list of languages installed.
+    * toinstalloptions array The list of languages to be installed.
+    * caninstall boolean Flag to indicate if there are language packs that can be installed.
+
+    Example context (json):
+    {
+        "sesskey": "sesskey",
+        "uninstallurl": "#",
+        "updateurl": "#",
+        "installurl": "#",
+        "installedoptions": [
+            {
+                "value": "en",
+                "text": "English",
+                "selected": true
+            }
+        ],
+        "toinstalloptions": [
+            {
+                "value": "ja",
+                "text": "Japanese"
+            },
+            {
+                "value": "fr",
+                "text": "French"
+            },
+            {
+                "value": "es",
+                "text": "Spanish"
+            }
+        ],
+        "caninstall": true
+    }
+}}
+<div class="container-fluid langimport">
+    <div class="row row-fluid rtl-compatible">
+        <div class="col-md-{{#caninstall}}6{{/caninstall}}{{^caninstall}}12{{/caninstall}} span{{#caninstall}}6{{/caninstall}}{{^caninstall}}12{{/caninstall}} m-b-1">
+            <form id="uninstallform" action="{{uninstallurl}}" method="post">
+                <fieldset>
+                    <div class="form-group">
+                        <label for="menuuninstalllang">{{#str}}installedlangs, tool_langimport{{/str}}</label>
+                        <select size="15" multiple="multiple" id="menuuninstalllang" class="form-control input-block-level" name="uninstalllang[]">
+                            {{#installedoptions}}
+                                <option value="{{value}}" {{#selected}}selected="selected"{{/selected}}>{{{text}}}‎</option>
+                            {{/installedoptions}}
+                        </select>
+                    </div>
+                    <div class="form-group">
+                        <input type="hidden" name="sesskey" value="{{sesskey}}">
+                        <input id="languninstallbutton" type="submit" value="{{#str}}uninstall, tool_langimport{{/str}}" class="btn btn-default">
+                    </div>
+                </fieldset>
+            </form>
+            {{#updateurl}}
+                <div>
+                    <form id="updateform" action="{{updateurl}}" method="post">
+                        <fieldset>
+                            <input type="submit" value="{{#str}}updatelangs, tool_langimport{{/str}}" class="btn btn-default">
+                        </fieldset>
+                    </form>
+                </div>
+            {{/updateurl}}
+        </div>
+        {{#caninstall}}
+            <div class="col-md-6 span6 m-b-1">
+                <form id="installform" action="{{installurl}}" method="post">
+                    <fieldset>
+                        <div class="form-group">
+                            <label for="menupack">{{#str}}availablelangs, install{{/str}}</label>
+                            <select size="15" multiple="multiple" class="form-control input-block-level" id="menupack" name="pack[]">
+                                {{#toinstalloptions}}
+                                    <option value="{{value}}" {{#selected}}selected="selected"{{/selected}}>{{{text}}}‎</option>
+                                {{/toinstalloptions}}
+                            </select>
+                        </div>
+                        <div class="form-group">
+                            <input type="hidden" name="sesskey" value="{{sesskey}}">
+                            <input type="submit" value="{{#str}}install, tool_langimport{{/str}}" class="btn btn-default">
+                        </div>
+                    </fieldset>
+                </form>
+            </div>
+        {{/caninstall}}
+    </div>
+</div>
index 6d6f0f0..e1b9170 100644 (file)
@@ -4,7 +4,6 @@ Feature: Manage language packs
   As an administrator
   I need to be able to add, update and remove language packs
 
-
   Background:
     Given remote langimport tests are enabled
 
index bccf5f7..998a950 100644 (file)
@@ -68,7 +68,7 @@ Feature: Manage competency frameworks
     Then I should not see "Science Year-4"
     And I should not see "sc-y-4"
 
-Scenario: Edit a framework with competencies in user competency
+  Scenario: Edit a framework with competencies in user competency
     Given the following lp "frameworks" exist:
       | shortname | idnumber |
       | Science Year-5 | sc-y-5 |
@@ -98,7 +98,7 @@ Scenario: Edit a framework with competencies in user competency
     And I should see "Science Year-5 Edited"
     And I should see "sc-y-5"
 
-Scenario: Edit a framework with competencies in user competency plan
+  Scenario: Edit a framework with competencies in user competency plan
     Given the following lp "frameworks" exist:
       | shortname | idnumber |
       | Science Year-6 | sc-y-6 |
index bfaf1e0..c98314e 100644 (file)
@@ -179,7 +179,7 @@ Feature: Manage plan workflow
     And I should not see "Active"
     And I log out
 
-Scenario: Manager reopen a complete learning plan
+  Scenario: Manager reopen a complete learning plan
     Given  the following lp "plans" exist:
       | name | user | description | status | reviewer |
       | Test-Plan3 | user1 | Description of plan 3 for user 1 | complete | manager1 |
index 6a46e19..35dbd2b 100644 (file)
@@ -78,4 +78,3 @@ Feature: Manage evidence of prior learning
     Then I should see "List of evidence"
     And I should see "Evidence-6"
     And I should not see "Evidence-5"
-
index f10fb60..c04bba3 100644 (file)
@@ -47,6 +47,8 @@ class api {
     const LOGIN_VIA_EMBEDDED_BROWSER = 3;
     /** @var int seconds an auto-login key will expire. */
     const LOGIN_KEY_TTL = 60;
+    /** @var str link to the custom strings documentation for the app */
+    const CUSTOM_STRINGS_DOC_URL = 'https://docs.moodle.org/en/Moodle_Mobile_language_strings_customisation';
 
     /**
      * Returns a list of Moodle plugins supporting the mobile app.
@@ -202,6 +204,11 @@ class api {
             $settings->mygradesurl = user_mygrades_url()->out(false);
         }
 
+        if (empty($section) or $section == 'mobileapp') {
+            $settings->tool_mobile_forcelogout = get_config('tool_mobile', 'forcelogout');
+            $settings->tool_mobile_customlangstrings = get_config('tool_mobile', 'customlangstrings');
+        }
+
         return $settings;
     }
 
index 53081b0..2979bad 100644 (file)
@@ -26,10 +26,19 @@ $string['autologinkeygenerationlockout'] = 'Auto-login key generation is blocked
 $string['autologinnotallowedtoadmins'] = 'Auto-login is not allowed for site admins.';
 $string['clickheretolaunchtheapp'] = 'Click here if the app does not open automatically.';
 $string['configmobilecssurl'] = 'A CSS file to customise your mobile app interface.';
+$string['customlangstrings'] = 'Custom language strings';
+$string['customlangstrings_desc'] = 'Words and phrases displayed in the app can be customised here. Enter each custom language string on a new line with format: string identifier, custom language string and language code, separated by pipe characters. For example:
+<pre>
+mm.user.student|Learner|en
+mm.user.student|Aprendiz|es
+</pre>
+For a complete list of string identifiers and more information, see the <a href="{$a}">documentation page</a>.';
 $string['enablesmartappbanners'] = 'Enable Smart App Banners';
 $string['enablesmartappbanners_desc'] = 'This will display a banner promoting the Moodle Mobile app when visiting the site in Mobile Safari.';
 $string['forcedurlscheme'] = 'If you want to allow only your custom branded app to be opened via a browser window, then specify its URL scheme here; otherwise leave the field empty.';
 $string['forcedurlscheme_key'] = 'URL scheme';
+$string['forcelogout'] = 'Force log out';
+$string['forcelogout_desc'] = 'If enabled, the app option \'Change site\' is replaced by \'Log out\'. This results in the user being completely logged out. They must then re-enter their password the next time they wish to access the site.';
 $string['httpsrequired'] = 'HTTPS required';
 $string['invalidprivatetoken'] = 'Invalid private token. Token should not be empty or passed via GET parameter.';
 $string['iosappid'] = 'App\'s unique identifier';
@@ -41,6 +50,7 @@ $string['mobileapp'] = 'Mobile app';
 $string['mobileappearance'] = 'Mobile appearance';
 $string['mobileauthentication'] = 'Mobile authentication';
 $string['mobilecssurl'] = 'CSS';
+$string['mobilefeatures'] = 'Mobile features';
 $string['mobilesettings'] = 'Mobile settings';
 $string['pluginname'] = 'Moodle Mobile tools';
 $string['smartappbanners'] = 'Smart App Banners (iOS only)';
index 9cc8d90..d129e5a 100644 (file)
@@ -81,5 +81,25 @@ if ($hassiteconfig) {
                     new lang_string('iosappid_desc', 'tool_mobile'), '633359593', PARAM_ALPHANUM));
 
         $ADMIN->add('mobileapp', $temp);
+
+        // Features related settings.
+        $temp = new admin_settingpage('mobilefeatures', new lang_string('mobilefeatures', 'tool_mobile'));
+
+        $temp->add(new admin_setting_heading('tool_mobile/logout',
+                    new lang_string('logout'), ''));
+
+        $temp->add(new admin_setting_configcheckbox('tool_mobile/forcelogout',
+                    new lang_string('forcelogout', 'tool_mobile'),
+                    new lang_string('forcelogout_desc', 'tool_mobile'), 0));
+
+        $temp->add(new admin_setting_heading('tool_mobile/language',
+                    new lang_string('language'), ''));
+
+        $temp->add(new admin_setting_configtextarea('tool_mobile/customlangstrings',
+                    new lang_string('customlangstrings', 'tool_mobile'),
+                    new lang_string('customlangstrings_desc', 'tool_mobile', tool_mobile\api::CUSTOM_STRINGS_DOC_URL),
+                    '', PARAM_RAW, '50', '10'));
+
+        $ADMIN->add('mobileapp', $temp);
     }
 }
index f145cd0..9b3c49d 100644 (file)
@@ -144,6 +144,8 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
             array('name' => 'commentsperpage', 'value' => $CFG->commentsperpage),
             array('name' => 'disableuserimages', 'value' => $CFG->disableuserimages),
             array('name' => 'mygradesurl', 'value' => user_mygrades_url()->out(false)),
+            array('name' => 'tool_mobile_forcelogout', 'value' => 0),
+            array('name' => 'tool_mobile_customlangstrings', 'value' => ''),
         );
         $this->assertCount(0, $result['warnings']);
         $this->assertEquals($expected, $result['settings']);
@@ -151,8 +153,8 @@ class tool_mobile_external_testcase extends externallib_advanced_testcase {
         // Change a value and retrieve filtering by section.
         set_config('commentsperpage', 1);
         $expected[10]['value'] = 1;
-        unset($expected[11]);
-        unset($expected[12]);
+        // Remove not expected elements.
+        array_splice($expected, 11);
 
         $result = external::get_config('frontpagesettings');
         $result = external_api::clean_returnvalue(external::get_config_returns(), $result);
index 87915c0..a0ea73c 100644 (file)
@@ -23,7 +23,7 @@
  */
 
 defined('MOODLE_INTERNAL') || die();
-$plugin->version   = 2016120500; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version   = 2016120502; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires  = 2016112900; // Requires this Moodle version.
 $plugin->component = 'tool_mobile'; // Full name of the plugin (used for diagnostics).
 $plugin->dependencies = array(
index c0ea85b..6279950 100644 (file)
@@ -99,9 +99,31 @@ if (!isset($config->removeuser)) {
 $yesno = array( get_string('no'), get_string('yes') );
 
 $disabled = '';
-if (!ldap_paged_results_supported($config->ldap_version)) {
+$pagedresultssupported = false;
+if ($config->host_url !== '') {
+    /**
+     * We try to connect each and every time we open the config, because we want to set the Page
+     * Size setting as enabled or disabled depending on the configured LDAP server supporting
+     * pagination or not, and to notify the user about it. If the user changed the LDAP server (or
+     * the LDAP protocol version) last time, it might happen that paged results are no longer
+     * available and we want to show that to the user the next time she goes to the settings page.
+     */
+    try {
+        $ldapconn = $this->ldap_connect();
+        $pagedresultssupported = ldap_paged_results_supported($config->ldap_version, $ldapconn);
+    } catch (Exception $e) {
+        // If we couldn't connect and get the supported options, we can only assume we don't support paged results.
+        $pagedresultssupported = false;
+    }
+}
+/* Make sure we only disable the paged result size setting and show the notification about it if
+ * there is a configured server that we tried to contact.  Othersiwe, if someone's LDAP server does
+ * support paged results, they won't be able to turn it on the first time they set it up (because
+ * the field will be disabled).
+ */
+if (($config->host_url !== '') && (!$pagedresultssupported)) {
     $disabled = ' disabled="disabled"';
-    echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'));
+    echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'), \core\output\notification::NOTIFY_INFO);
 }
 
 ?>
index 5e0693c..3bf9979 100644 (file)
@@ -43,7 +43,7 @@ class core_auth_external extends external_api {
     /**
      * Describes the parameters for confirm_user.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.2
      */
     public static function confirm_user_parameters() {
index 1f4f7cf..007a7e5 100644 (file)
@@ -59,7 +59,7 @@ class auth_email_external extends external_api {
     /**
      * Describes the parameters for get_signup_settings.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.2
      */
     public static function get_signup_settings_parameters() {
@@ -179,7 +179,7 @@ class auth_email_external extends external_api {
     /**
      * Describes the parameters for signup_user.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.2
      */
     public static function signup_user_parameters() {
index f80d6b3..b23c9ec 100644 (file)
@@ -695,7 +695,7 @@ class auth_plugin_ldap extends auth_plugin_base {
             array_push($contexts, $this->config->create_context);
         }
 
-        $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version);
+        $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
         $ldap_cookie = '';
         foreach ($contexts as $context) {
             $context = trim($context);
@@ -1540,7 +1540,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         }
 
         $ldap_cookie = '';
-        $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version);
+        $ldap_pagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
         foreach ($contexts as $context) {
             $context = trim($context);
             if (empty($context)) {
index 422f474..883eb9d 100644 (file)
@@ -117,9 +117,31 @@ $fastpathoptions = array(AUTH_NTLM_FASTPATH_YESFORM => get_string('auth_ntlmsso_
                          AUTH_NTLM_FASTPATH_ATTEMPT => get_string('auth_ntlmsso_ie_fastpath_attempt', 'auth_ldap'));
 
 $disabled = '';
-if (!ldap_paged_results_supported($config->ldap_version)) {
+$pagedresultssupported = false;
+if ($config->host_url !== '') {
+    /**
+     * We try to connect each and every time we open the config, because we want to set the Page
+     * Size setting as enabled or disabled depending on the configured LDAP server supporting
+     * pagination or not, and to notify the user about it. If the user changed the LDAP server (or
+     * the LDAP protocol version) last time, it might happen that paged results are no longer
+     * available and we want to show that to the user the next time she goes to the settings page.
+     */
+    try {
+        $ldapconn = $this->ldap_connect();
+        $pagedresultssupported = ldap_paged_results_supported($config->ldap_version, $ldapconn);
+    } catch (Exception $e) {
+        // If we couldn't connect and get the supported options, we can only assume we don't support paged results.
+        $pagedresultssupported = false;
+    }
+}
+/* Make sure we only disable the paged result size setting and show the notification about it if
+ * there is a configured server that we tried to contact.  Othersiwe, if someone's LDAP server does
+ * support paged results, they won't be able to turn it on the first time they set it up (because
+ * the field will be disabled).
+ */
+if (($config->host_url !== '') && (!$pagedresultssupported)) {
     $disabled = ' disabled="disabled"';
-    echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'));
+    echo $OUTPUT->notification(get_string('pagedresultsnotsupp', 'auth_ldap'), \core\output\notification::NOTIFY_INFO);
 }
 
 ?>
index 6f23523..a5285dd 100644 (file)
@@ -132,7 +132,7 @@ $string['ntlmsso_attempting'] = 'Attempting Single Sign On via NTLM...';
 $string['ntlmsso_failed'] = 'Auto-login failed, try the normal login page...';
 $string['ntlmsso_isdisabled'] = 'NTLM SSO is disabled.';
 $string['ntlmsso_unknowntype'] = 'Unknown ntlmsso type!';
-$string['pagedresultsnotsupp'] = 'LDAP paged results not supported (either your PHP version lacks support or you have configured Moodle to use LDAP protocol version 2)';
+$string['pagedresultsnotsupp'] = 'LDAP paged results not supported (either your PHP version lacks support, you have configured Moodle to use LDAP protocol version 2 or Moodle cannot contact your LDAP server to see if paged support is available.)';
 $string['pagesize'] = 'Make sure this value is smaller than your LDAP server result set size limit (the maximum number of entries that can be returned in a single query)';
 $string['pagesize_key'] = 'Page size';
 $string['pluginname'] = 'LDAP server';
index 2a98264..25081bf 100644 (file)
@@ -43,7 +43,7 @@ class core_badges_external extends external_api {
     /**
      * Describes the parameters for get_user_badges.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.1
      */
     public static function get_user_badges_parameters() {
index d9baedd..7343d92 100644 (file)
@@ -50,7 +50,7 @@ $PAGE->set_context($context);
 $title = get_string('backpackdetails', 'badges');
 $PAGE->set_title($title);
 $PAGE->set_heading(fullname($USER));
-$PAGE->set_pagelayout('mydashboard');
+$PAGE->set_pagelayout('standard');
 
 $backpack = $DB->get_record('badge_backpack', array('userid' => $USER->id));
 $badgescache = cache::make('core', 'externalbadges');
index 0ef4f68..8437ad4 100644 (file)
@@ -89,7 +89,7 @@ $PAGE->set_context($context);
 $title = get_string('badges', 'badges');
 $PAGE->set_title($title);
 $PAGE->set_heading(fullname($USER));
-$PAGE->set_pagelayout('mydashboard');
+$PAGE->set_pagelayout('standard');
 
 // Include JS files for backpack support.
 badges_setup_backpack_js();
index 92cc5ad..e2f504e 100644 (file)
@@ -357,4 +357,3 @@ Feature: Award badges
     When I press "Revoke badge"
     And I follow "Course Badge"
     Then I should see "Recipients (0)"
-
index 7af21b1..de4a149 100644 (file)
@@ -60,7 +60,7 @@ Feature: The activity results block displays student scores
     And I press "Save changes"
     And I follow "Course 1"
 
-Scenario: Configure the block on a non-graded activity to show 3 high scores
+  Scenario: Configure the block on a non-graded activity to show 3 high scores
     Given I follow "Test page name"
     And I add the "Activity results" block
     When I configure the "Activity results" block
@@ -78,7 +78,7 @@ Scenario: Configure the block on a non-graded activity to show 3 high scores
     And I should see "Student 3" in the "Activity results" "block"
     And I should see "70.00" in the "Activity results" "block"
 
-Scenario: Block should select current activity by default
+  Scenario: Block should select current activity by default
     Given I follow "Test assignment 1"
     When I add the "Activity results" block
     And I configure the "Activity results" block
index def045b..f53d8ea 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block doesn't displays student scores for unconfigured block
   In order to be display student scores
   As a user
   I need to see the activity results block
index 3aa10b9..d3f5c99 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block doesn't display student scores for unsupported activity
   In order to be display student scores
   As a user
   I need to properly configure the activity results block
index 4a2a8bc..620aac1 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays student high scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index 86dee32..d121c97 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores as scales
+Feature: The activity results block displays students high scores in group as scales
   In order to be display student scores as scales
   As a user
   I need to see the activity results block
index 536a00e..6b9c972 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores as scales
+Feature: The activity results block displays student in group high scores as scales
   In order to be display student scores as scales
   As a user
   I need to see the activity results block
index c764e80..0c1ccdd 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays student in separate groups scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index bf10d79..0e64d05 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays student in visible groups scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index a1496c6..e2c8729 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays student low scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index 587f863..5068a7b 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores as scales
+Feature: The activity results block displays student low scores as scales
   In order to be display student scores as scales
   As a user
   I need to see the activity results block
index f43348e..30602ff 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores as scales
+Feature: The activity results block displays students in groups low scores as scales
   In order to be display student scores as scales
   As a user
   I need to see the activity results block
@@ -146,4 +146,3 @@ Feature: The activity results block displays student scores as scales
     And I should see "User" in the "Activity results" "block"
     And I should see "Good" in the "Activity results" "block"
     And I should see "Average" in the "Activity results" "block"
-
index 640517e..5d73692 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays students in separate groups scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index 2010dcd..e137f92 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_activity_results
-Feature: The activity results block displays student scores
+Feature: The activity results block displays student in visible groups low scores
   In order to be display student scores
   As a user
   I need to see the activity results block
index f45f62f..76ceb37 100644 (file)
@@ -1,8 +1,8 @@
 @block @block_blog_menu
-Feature: Enable Block blog menu in a course
-  In order to enable the blog menu in a course
-  As a teacher
-  I can add blog menu block to a course
+Feature: Students can use block blog menu in a course
+  In order students to use the blog menu in a course
+  As a student
+  I view blog menu block in a course
 
   Background:
     Given the following "users" exist:
index ea2bcd1..ceb1898 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_calendar_upcoming
-Feature: View a site event on the dashboard
+Feature: View a upcoming 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
index 532950d..7d4c3f6 100644 (file)
@@ -52,7 +52,6 @@ Feature: Enable Block Completion in a course using manual completion by others
     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"
index 238591f..c91c551 100644 (file)
@@ -91,4 +91,3 @@ Feature: View the quiz being due
     And I should not see "Quiz 1B Past deadline" in the "Course overview" "block"
     And I should not see "Quiz 1D Future deadline" in the "Course overview" "block"
     And I should not see "Quiz 2A Future deadline" in the "Course overview" "block"
-
index fc6f19c..db31b91 100644 (file)
@@ -4,24 +4,24 @@ Feature: Random glossary entry block can be added to the frontpage
   As a teacher
   I can add the random glossary entry to the frontpage
 
-Scenario: Admin can add random glossary block to the frontpage
-  Given the following "activities" exist:
-    | activity   | name             | intro                          | course               | idnumber  |
-    | glossary   | Tips and Tricks  | Frontpage glossary description | Acceptance test site | glossary0 |
-  And I log in as "admin"
-  And I am on site homepage
-  And I turn editing mode on
-  And I add the "Random glossary entry" block
-  And I configure the "block_glossary_random" block
-  And I set the following fields to these values:
-    | Title                           | Tip of the day  |
-    | Take entries from this glossary | Tips and Tricks |
-  And I press "Save changes"
-  And I click on "Add a new entry" "link" in the "Tip of the day" "block"
-  And I set the following fields to these values:
-    | Concept    | Never come late               |
-    | Definition | Come in time for your classes |
-  And I press "Save changes"
-  When I log out
-  Then I should see "Never come late" in the "Tip of the day" "block"
-  And I should see "Come in time for your classes" in the "Tip of the day" "block"
+  Scenario: Admin can add random glossary block to the frontpage
+    Given the following "activities" exist:
+      | activity   | name             | intro                          | course               | idnumber  |
+      | glossary   | Tips and Tricks  | Frontpage glossary description | Acceptance test site | glossary0 |
+    And I log in as "admin"
+    And I am on site homepage
+    And I turn editing mode on
+    And I add the "Random glossary entry" block
+    And I configure the "block_glossary_random" block
+    And I set the following fields to these values:
+      | Title                           | Tip of the day  |
+      | Take entries from this glossary | Tips and Tricks |
+    And I press "Save changes"
+    And I click on "Add a new entry" "link" in the "Tip of the day" "block"
+    And I set the following fields to these values:
+      | Concept    | Never come late               |
+      | Definition | Come in time for your classes |
+    And I press "Save changes"
+    When I log out
+    Then I should see "Never come late" in the "Tip of the day" "block"
+    And I should see "Come in time for your classes" in the "Tip of the day" "block"
index 93b58de..a6cad0b 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_html
-Feature: Adding and configuring HTML blocks
+Feature: Adding and configuring multiple HTML blocks
   In order to have one or multiple HTML blocks on a page
   As admin
   I need to be able to create, configure and change HTML blocks
index ecde2a0..7f88d76 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_online_users
-Feature: The online users block allow you to see who is currently online
+Feature: The online users block allow you to see who is currently online on dashboard
   In order to use the online users block on the dashboard
   As a user
   I can view the online users block on my dashboard
index 3e4b56d..5909b1c 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_online_users
-Feature: The online users block allow you to see who is currently online
+Feature: The online users block allow you to see who is currently online on frontpage
   In order to enable the online users block on the front page page
   As an admin
   I can add the online users block to the front page page
index aaf59ff..d857871 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_private_files @_file_upload @javascript
-Feature: The private files block allows users to store files privately in moodle
+Feature: The private files block allows users to store files privately in moodle on activity page
   In order to store a private file in moodle
   As a teacher
   I can upload the file to my private files area using the private files block in an activity
index 43e7b8c..b56c15b 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_private_files @_file_upload @javascript
-Feature: The private files block allows users to store files privately in moodle
+Feature: The private files block allows users to store files privately in moodle on course page
   In order to store a private file in moodle
   As a teacher
   I can upload the file to my private files area using the private files block in a course
index 990cd96..3b6eb5c 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_private_files @_file_upload @javascript
-Feature: The private files block allows users to store files privately in moodle
+Feature: The private files block allows users to store files privately in moodle on dashboard
   In order to store a private file in moodle
   As a user
   I can upload the file to my private files area using the private files block on the dashboard
index ec611bb..7816060 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_private_files @_file_upload
-Feature: The private files block allows users to store files privately in moodle
+Feature: The private files block allows users to store files privately in moodle on front page.
   In order to store a private file in moodle
   As a teacher
   I can upload the file to my private files area using the private files block from the front page
index a52b9ad..dedc8bc 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_search_forums @mod_forum
-Feature: The search forums block allows users to search for forum posts
+Feature: The search forums block allows users to search for forum posts on course page
   In order to search for a forum post
   As a user
   I can use the search forums block
index 4d2aa52..1f76d15 100644 (file)
@@ -1,5 +1,5 @@
 @block @block_search_forums @mod_forum
-Feature: The search forums block allows users to search for forum posts
+Feature: The search forums block allows users to search for forum posts on frontpage
   In order to search for a forum post
   As an administrator
   I can add the search forums block
index 038a966..f43f6fd 100644 (file)
@@ -27,7 +27,6 @@ Feature: The section links block allows users to quickly navigate around a moodl
       | id_coursedisplay | Show one section per page |
     And I press "Save and display"
 
-
   Scenario: Add the section links block to a course.
     Given I add the "Section links" block
     And I turn editing mode off
index d41e9de..950577b 100644 (file)
@@ -48,4 +48,3 @@ Feature: Delete a blog entry
     And I press "Cancel"
     And I should see "Blog post one"
     And I should see "Blog post two"
-
index 7a0adc6..3c5bd42 100644 (file)
@@ -213,7 +213,7 @@ class cache implements cache_loader {
         $this->definition = $definition;
         $this->store = $store;
         $this->storetype = get_class($store);
-        $this->perfdebug = !empty($CFG->perfdebug);
+        $this->perfdebug = (!empty($CFG->perfdebug) and $CFG->perfdebug > 7);
         if ($loader instanceof cache_loader) {
             $this->loader = $loader;
             // Mark the loader as a sub (chained) loader.
index 537cbef..8da4f1e 100644 (file)
@@ -48,5 +48,11 @@ class cachestore_redis_addinstance_form extends cachestore_addinstance_form {
         $form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
         $form->addHelpButton('prefix', 'prefix', 'cachestore_redis');
         $form->addRule('prefix', get_string('prefixinvalid', 'cachestore_redis'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
+
+        $serializeroptions = cachestore_redis::config_get_serializer_options();
+        $form->addElement('select', 'serializer', get_string('useserializer', 'cachestore_redis'), $serializeroptions);
+        $form->addHelpButton('serializer', 'useserializer', 'cachestore_redis');
+        $form->setDefault('serializer', Redis::SERIALIZER_PHP);
+        $form->setType('serializer', PARAM_INT);
     }
 }
\ No newline at end of file
index e51e19f..a44a156 100644 (file)
@@ -30,7 +30,15 @@ $string['prefix_help'] = 'This prefix is used for all key names on the Redis ser
 * If you only have one Moodle instance using this server, you can leave this value default.
 * Due to key length restrictions, a maximum of 5 characters is permitted.';
 $string['prefixinvalid'] = 'Invalid prefix. You can only use a-z A-Z 0-9-_.';
+$string['serializer_igbinary'] = 'The igbinary serializer.';
+$string['serializer_php'] = 'The default PHP serializer.';
+$string['server'] = 'Server';
+$string['server_help'] = 'This sets the hostname or IP address of the Redis server to use.';
 $string['test_server'] = 'Test server';
 $string['test_server_desc'] = 'Redis server to use for testing.';
-$string['server'] = 'Server';
-$string['server_help'] = 'This sets the hostname or IP address of the Redis server to use.';
\ No newline at end of file
+$string['test_serializer'] = 'Serializer';
+$string['test_serializer_desc'] = 'Serializer to use for testing.';
+$string['useserializer'] = 'Use serializer';
+$string['useserializer_help'] = 'Specifies the serializer to use for serializing.
+The valid serializers are Redis::SERIALIZER_PHP or Redis::SERIALIZER_IGBINARY.
+The latter is supported only when phpredis is configured with --enable-redis-igbinary option and the igbinary extension is loaded.';
index 16ad4c2..fd19709 100644 (file)
@@ -73,6 +73,13 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
      */
     protected $redis;
 
+    /**
+     * Serializer for this store.
+     *
+     * @var int
+     */
+    protected $serializer = Redis::SERIALIZER_PHP;
+
     /**
      * Determines if the requirements for this type of store are met.
      *
@@ -124,6 +131,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
         if (!array_key_exists('server', $configuration) || empty($configuration['server'])) {
             return;
         }
+        if (array_key_exists('serializer', $configuration)) {
+            $this->serializer = (int)$configuration['serializer'];
+        }
         $prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
         $this->redis = $this->new_redis($configuration['server'], $prefix);
     }
@@ -145,7 +155,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
             $port = $serverconf[1];
         }
         if ($redis->connect($server, $port)) {
-            $redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
+            $redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
             if (!empty($prefix)) {
                 $redis->setOption(Redis::OPT_PREFIX, $prefix);
             }
@@ -426,7 +436,11 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
      * @return array
      */
     public static function config_get_configuration_array($data) {
-        return array('server' => $data->server, 'prefix' => $data->prefix);
+        return array(
+            'server' => $data->server,
+            'prefix' => $data->prefix,
+            'serializer' => $data->serializer
+        );
     }
 
     /**
@@ -440,6 +454,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
         $data = array();
         $data['server'] = $config['server'];
         $data['prefix'] = !empty($config['prefix']) ? $config['prefix'] : '';
+        if (!empty($config['serializer'])) {
+            $data['serializer'] = $config['serializer'];
+        }
         $editform->set_data($data);
     }
 
@@ -458,7 +475,11 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
         if (empty($config->test_server)) {
             return false;
         }
-        $cache = new cachestore_redis('Redis test', ['server' => $config->test_server]);
+        $configuration = array('server' => $config->test_server);
+        if (!empty($config->test_serializer)) {
+            $configuration['serializer'] = $config->test_serializer;
+        }
+        $cache = new cachestore_redis('Redis test', $configuration);
         $cache->initialise($definition);
 
         return $cache;
@@ -491,4 +512,19 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
     public static function ready_to_be_used_for_testing() {
         return defined('TEST_CACHESTORE_REDIS_TESTSERVERS');
     }
+
+    /**
+     * Gets an array of options to use as the serialiser.
+     * @return array
+     */
+    public static function config_get_serializer_options() {
+        $options = array(
+            Redis::SERIALIZER_PHP => get_string('serializer_php', 'cachestore_redis')
+        );
+
+        if (defined('Redis::SERIALIZER_IGBINARY')) {
+            $options[Redis::SERIALIZER_IGBINARY] = get_string('serializer_igbinary', 'cachestore_redis');
+        }
+        return $options;
+    }
 }
index 1d10cf8..0787815 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 $settings->add(
-  new admin_setting_configtext(
-    'cachestore_redis/test_server',
-    get_string('test_server', 'cachestore_redis'),
-    get_string('test_server_desc', 'cachestore_redis'),
-    '',
-    PARAM_TEXT,
-    16
-  )
+    new admin_setting_configtext(
+        'cachestore_redis/test_server',
+        get_string('test_server', 'cachestore_redis'),
+        get_string('test_server_desc', 'cachestore_redis'),
+        '',
+        PARAM_TEXT,
+        16
+    )
 );
+
+if (class_exists('Redis')) { // Only if Redis is available.
+
+    $options = array(Redis::SERIALIZER_PHP => get_string('serializer_php', 'cachestore_redis'));
+
+    if (defined('Redis::SERIALIZER_IGBINARY')) {
+        $options[Redis::SERIALIZER_IGBINARY] = get_string('serializer_igbinary', 'cachestore_redis');
+    }
+
+    $settings->add(new admin_setting_configselect(
+            'cachestore_redis/test_serializer',
+            get_string('test_serializer', 'cachestore_redis'),
+            get_string('test_serializer_desc', 'cachestore_redis'),
+            Redis::SERIALIZER_PHP,
+            $options
+        )
+    );
+}
index ecf06dd..72a0c80 100644 (file)
@@ -117,12 +117,19 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
      * @var bool
      */
     protected $simpledata = false;
+
     /**
      * The number of items currently being stored.
      * @var int
      */
     protected $storecount = 0;
 
+    /**
+     * igbinary extension available.
+     * @var bool
+     */
+    protected $igbinaryfound = false;
+
     /**
      * Constructs the store instance.
      *
@@ -203,6 +210,7 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
         $this->store = &self::register_store_id($this->storeid);
         $maxsize = $definition->get_maxsize();
         $this->simpledata = $definition->uses_simple_data();
+        $this->igbinaryfound = extension_loaded('igbinary');
         if ($maxsize !== null) {
             // Must be a positive int.
             $this->maxsize = abs((int)$maxsize);
@@ -219,6 +227,41 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
         return (is_array($this->store));
     }
 
+    /**
+     * Uses igbinary serializer if igbinary extension is loaded.
+     * Fallback to PHP serializer.
+     *
+     * @param mixed $data
+     * The value to be serialized.
+     * @return string a string containing a byte-stream representation of
+     * value that can be stored anywhere.
+     */
+    protected function serialize($data) {
+        if ($this->igbinaryfound) {
+            return igbinary_serialize($data);
+        } else {
+            return serialize($data);
+        }
+    }
+
+    /**
+     * Uses igbinary unserializer if igbinary extension is loaded.
+     * Fallback to PHP unserializer.
+     *
+     * @param string $str
+     * The serialized string.
+     * @return mixed The converted value is returned, and can be a boolean,
+     * integer, float, string,
+     * array or object.
+     */
+    protected function unserialize($str) {
+        if ($this->igbinaryfound) {
+            return igbinary_unserialize($str);
+        } else {
+            return unserialize($str);
+        }
+    }
+
     /**
      * Retrieves an item from the cache store given its key.
      *
@@ -233,7 +276,7 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
         $key = $key['key'];
         if (isset($this->store[$key])) {
             if ($this->store[$key]['serialized']) {
-                return unserialize($this->store[$key]['data']);
+                return $this->unserialize($this->store[$key]['data']);
             } else {
                 return $this->store[$key]['data'];
             }
@@ -261,7 +304,7 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
             $return[$key] = false;
             if (isset($this->store[$key])) {
                 if ($this->store[$key]['serialized']) {
-                    $return[$key] = unserialize($this->store[$key]['data']);
+                    $return[$key] = $this->unserialize($this->store[$key]['data']);
                 } else {
                     $return[$key] = $this->store[$key]['data'];
                 }
@@ -292,7 +335,7 @@ class cachestore_static extends static_data_store implements cache_is_key_aware,
             $this->store[$key]['data'] = $data;
             $this->store[$key]['serialized'] = false;
         } else {
-            $this->store[$key]['data'] = serialize($data);
+            $this->store[$key]['data'] = $this->serialize($data);
             $this->store[$key]['serialized'] = true;
         }
 
index 0597e9b..f2efb42 100644 (file)
@@ -117,4 +117,39 @@ class cachestore_static_test extends cachestore_tests {
             'key4', 'key5', 'key6', 'key7', 'keyA', 'keyB', 'keyC'
         )));
     }
-}
\ No newline at end of file
+
+    /**
+     * Simple test to verify igbinary availability and check basic serialization is working ok.
+     */
+    public function test_igbinary_serializer() {
+        // Skip if igbinary is not available.
+        if (!extension_loaded('igbinary')) {
+            $this->markTestSkipped('Cannot test igbinary serializer. Extension missing');
+        }
+        // Prepare the static instance.
+        $defid = 'phpunit/igbinary';
+        $config = cache_config_testing::instance();
+        $config->phpunit_add_definition($defid, array(
+            'mode' => cache_store::MODE_REQUEST,
+            'component' => 'phpunit',
+            'area' => 'testigbinary'
+        ));
+        $definition = cache_definition::load($defid, $config->get_definition_by_id($defid));
+        $instance = cachestore_static::initialise_test_instance($definition);
+        // Prepare an object.
+        $obj = new stdClass();
+        $obj->someint = 9;
+        $obj->somestring = '99';
+        $obj->somearray = [9 => 999, '99' => '9999'];
+        // Serialize and set.
+        $objser = igbinary_serialize($obj);
+        $instance->set('testigbinary', $objser);
+        // Get and unserialize.
+        $res = $instance->get('testigbinary');
+        $resunser = igbinary_unserialize($res);
+        // Check expectations.
+        $this->assertSame($objser, $res);     // Ok from cache (ig-serialized, 100% same string).
+        $this->assertEquals($obj, $resunser); // Ok ig-unserialized (equal
+        $this->assertNotSame($obj, $resunser);// but different objects, obviously).
+    }
+}
index 965a61b..ac6bfed 100644 (file)
@@ -2172,4 +2172,57 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
             $startstats[$requestid]['stores']['cachestore_static']['sets']);
     }
+
+    public function test_performance_debug_off() {
+        global $CFG;
+        $this->resetAfterTest(true);
+        $CFG->perfdebug = 7;
+
+        $instance = cache_config_testing::instance();
+        $applicationid = 'phpunit/applicationperfoff';
+        $instance->phpunit_add_definition($applicationid, array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'applicationperfoff'
+        ));
+        $sessionid = 'phpunit/sessionperfoff';
+        $instance->phpunit_add_definition($sessionid, array(
+            'mode' => cache_store::MODE_SESSION,
+            'component' => 'phpunit',
+            'area' => 'sessionperfoff'
+        ));
+        $requestid = 'phpunit/requestperfoff';
+        $instance->phpunit_add_definition($requestid, array(
+            'mode' => cache_store::MODE_REQUEST,
+            'component' => 'phpunit',
+            'area' => 'requestperfoff'
+        ));
+
+        $application = cache::make('phpunit', 'applicationperfoff');
+        $session = cache::make('phpunit', 'sessionperfoff');
+        $request = cache::make('phpunit', 'requestperfoff');
+
+        // Check that no stats are recorded for these definitions yet.
+        $stats = cache_helper::get_stats();
+        $this->assertArrayNotHasKey($applicationid, $stats);
+        $this->assertArrayNotHasKey($sessionid, $stats);
+        $this->assertArrayNotHasKey($requestid, $stats);
+
+        // Trigger cache misses, cache sets and cache hits.
+        $this->assertFalse($application->get('missMe'));
+        $this->assertTrue($application->set('setMe', 1));
+        $this->assertEquals(1, $application->get('setMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertTrue($session->set('setMe', 3));
+        $this->assertEquals(3, $session->get('setMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertTrue($request->set('setMe', 4));
+        $this->assertEquals(4, $request->get('setMe'));
+
+        // Check that no stats are being recorded for these definitions.
+        $endstats = cache_helper::get_stats();
+        $this->assertArrayNotHasKey($applicationid, $endstats);
+        $this->assertArrayNotHasKey($sessionid, $endstats);
+        $this->assertArrayNotHasKey($requestid, $endstats);
+    }
 }
index d18eae3..c026cfe 100644 (file)
@@ -111,4 +111,3 @@ Feature: Perform basic calendar functionality
       | Description | Wait, this event isn't that great. |
     And I press "Save changes"
     And I should see "Mediocre event"
-
index 899ec2c..e913193 100644 (file)
@@ -125,4 +125,3 @@ Feature: Access visible and hidden cohorts
     And the "Select members from cohort" select box should not contain "Cohort in category 2"
     And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
     And the "Select members from cohort" select box should not contain "System empty cohort"
-
index c32b0b1..7979e61 100644 (file)
@@ -43,7 +43,7 @@ class core_completion_external extends external_api {
     /**
      * Describes the parameters for update_activity_completion_status_manually.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 2.9
      */
     public static function update_activity_completion_status_manually_parameters() {
@@ -383,7 +383,7 @@ class core_completion_external extends external_api {
     /**
      * Describes the parameters for mark_course_self_completed.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.0
      */
     public static function mark_course_self_completed_parameters() {
index 5d80be0..c85f83d 100644 (file)
@@ -121,13 +121,14 @@ class behat_completion extends behat_base {
     public function activity_marked_as_complete($activityname, $activitytype, $completiontype) {
         if ($completiontype == "manual") {
             $imgalttext = get_string("completion-alt-manual-y", 'core_completion', $activityname);
+            $xpathtocheck = "//input[@type='image'][contains(@alt, '$imgalttext')]";
         } else {
             $imgalttext = get_string("completion-alt-auto-y", 'core_completion', $activityname);
+            $xpathtocheck = "//img[contains(@alt, '$imgalttext')]";
         }
         $activityxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
         $activityxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
 
-        $xpathtocheck = "//img[contains(@alt, '$imgalttext')]";
         $this->execute("behat_general::should_exist_in_the",
             array($xpathtocheck, "xpath_element", $activityxpath, "xpath_element")
         );
@@ -142,13 +143,14 @@ class behat_completion extends behat_base {
     public function activity_marked_as_not_complete($activityname, $activitytype, $completiontype) {
         if ($completiontype == "manual") {
             $imgalttext = get_string("completion-alt-manual-n", 'core_completion', $activityname);
+            $xpathtocheck = "//input[@type='image'][contains(@alt, '$imgalttext')]";
         } else {
             $imgalttext = get_string("completion-alt-auto-n", 'core_completion', $activityname);
+            $xpathtocheck = "//img[contains(@alt, '$imgalttext')]";
         }
         $activityxpath = "//li[contains(concat(' ', @class, ' '), ' modtype_" . strtolower($activitytype) . " ')]";
         $activityxpath .= "[descendant::*[contains(text(), '" . $activityname . "')]]";
 
-        $xpathtocheck = "//img[contains(@alt, '$imgalttext')]";
         $this->execute("behat_general::should_exist_in_the",
             array($xpathtocheck, "xpath_element", $activityxpath, "xpath_element")
         );
index c7aeb27..443dc22 100644 (file)
@@ -25,15 +25,16 @@ Feature: Allow students to manually mark an activity as complete
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
     And I press "Save and display"
-    When I add a "Forum" to section "1" and I fill the form with:
+    And I add a "Forum" to section "1" and I fill the form with:
       | Forum name | Test forum name |
       | Description | Test forum description |
-    Then "Student First" user has not completed "Test forum name" activity
+    And "Student First" user has not completed "Test forum name" activity
     And I log out
     And I log in as "student1"
     And I am on site homepage
     And I follow "Course 1"
-    And I press "Mark as complete: Test forum name"
+    When I press "Mark as complete: Test forum name"
+    Then the "Test forum name" "forum" activity with "manual" completion should be marked as complete
     And I log out
     And I log in as "teacher1"
     And I am on site homepage
index 68ea7c3..9c795b6 100644 (file)
@@ -7,6 +7,6 @@
     "require-dev": {
         "phpunit/phpunit": "5.5.*",
         "phpunit/dbUnit": "1.4.*",
-        "moodlehq/behat-extension": "3.33.0"
+        "moodlehq/behat-extension": "3.33.1"
     }
 }
index f389eb3..c757473 100644 (file)
@@ -4,27 +4,27 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "e92c5d907a1058736ad80ca004530e8a",
-    "content-hash": "5131188e8dcb035bbea39a1fbe84ad42",
+    "content-hash": "f4cfcd74744fbbced495458ea82fd314",
     "packages": [],
     "packages-dev": [
         {
             "name": "behat/behat",
-            "version": "v3.2.2",
+            "version": "v3.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/Behat/Behat.git",
-                "reference": "30aa3836825416f28581ee55fcfe6a5b0cdeeb85"
+                "reference": "15a3a1857457eaa29cdf41564a5e421effb09526"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/Behat/Behat/zipball/30aa3836825416f28581ee55fcfe6a5b0cdeeb85",
-                "reference": "30aa3836825416f28581ee55fcfe6a5b0cdeeb85",
+                "url": "https://api.github.com/repos/Behat/Behat/zipball/15a3a1857457eaa29cdf41564a5e421effb09526",
+                "reference": "15a3a1857457eaa29cdf41564a5e421effb09526",
                 "shasum": ""
             },
             "require": {
                 "behat/gherkin": "^4.4.4",
                 "behat/transliterator": "~1.0",
+                "container-interop/container-interop": "^1.1",
                 "ext-mbstring": "*",
                 "php": ">=5.3.3",
                 "symfony/class-loader": "~2.1||~3.0",
@@ -87,7 +87,7 @@
                 "symfony",
                 "testing"
             ],
-            "time": "2016-11-05 17:13:53"
+            "time": "2016-12-25T13:43:52+00:00"
         },
         {
             "name": "behat/gherkin",
                 "gherkin",
                 "parser"
             ],
-            "time": "2016-10-30 11:50:56"
+            "time": "2016-10-30T11:50:56+00:00"
         },
         {
             "name": "behat/mink",
                 "testing",
                 "web"
             ],
-            "time": "2016-03-05 08:26:18"
+            "time": "2016-03-05T08:26:18+00:00"
         },
         {
             "name": "behat/mink-browserkit-driver",
                 "browser",
                 "testing"
             ],
-            "time": "2016-03-05 08:59:47"
+            "time": "2016-03-05T08:59:47+00:00"
         },
         {
             "name": "behat/mink-extension",
                 "test",
                 "web"
             ],
-            "time": "2016-02-15 07:55:18"
+            "time": "2016-02-15T07:55:18+00:00"
         },
         {
             "name": "behat/mink-goutte-driver",
                 "headless",
                 "testing"
             ],
-            "time": "2016-03-05 09:04:22"
+            "time": "2016-03-05T09:04:22+00:00"
         },
         {
             "name": "behat/mink-selenium2-driver",
                 "testing",
                 "webdriver"
             ],
-            "time": "2016-03-05 09:10:18"
+            "time": "2016-03-05T09:10:18+00:00"
         },
         {
             "name": "behat/transliterator",
                 "slug",
                 "transliterator"
             ],
-            "time": "2015-09-28 16:26:35"
+            "time": "2015-09-28T16:26:35+00:00"
+        },
+        {
+            "name": "container-interop/container-interop",
+            "version": "1.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/container-interop/container-interop.git",
+                "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/container-interop/container-interop/zipball/fc08354828f8fd3245f77a66b9e23a6bca48297e",
+                "reference": "fc08354828f8fd3245f77a66b9e23a6bca48297e",
+                "shasum": ""
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "Interop\\Container\\": "src/Interop/Container/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
+            "time": "2014-12-30T15:22:37+00:00"
         },
         {
             "name": "doctrine/instantiator",
                 "constructor",
                 "instantiate"
             ],
-            "time": "2015-06-14 21:17:01"
+            "time": "2015-06-14T21:17:01+00:00"
         },
         {
             "name": "fabpot/goutte",
-            "version": "v3.2.0",
+            "version": "v3.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/FriendsOfPHP/Goutte.git",
-                "reference": "8cc89de5e71daf84051859616891d3320d88a9e8"
+                "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/8cc89de5e71daf84051859616891d3320d88a9e8",
-                "reference": "8cc89de5e71daf84051859616891d3320d88a9e8",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/db5c28f4a010b4161d507d5304e28a7ebf211638",
+                "reference": "db5c28f4a010b4161d507d5304e28a7ebf211638",
                 "shasum": ""
             },
             "require": {
             "keywords": [
                 "scraper"
             ],
-            "time": "2016-11-15 16:27:29"
+            "time": "2017-01-03T13:21:43+00:00"
         },
         {
             "name": "guzzlehttp/guzzle",
                 "rest",
                 "web service"
             ],
-            "time": "2016-10-08 15:01:37"
+            "time": "2016-10-08T15:01:37+00:00"
         },
         {
             "name": "guzzlehttp/promises",
-            "version": "1.3.0",
+            "version": "v1.3.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/guzzle/promises.git",
-                "reference": "2693c101803ca78b27972d84081d027fca790a1e"
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/guzzle/promises/zipball/2693c101803ca78b27972d84081d027fca790a1e",
-                "reference": "2693c101803ca78b27972d84081d027fca790a1e",
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.5.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "~4.0"
+                "phpunit/phpunit": "^4.0"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.0-dev"
+                    "dev-master": "1.4-dev"
                 }
             },
             "autoload": {
             "keywords": [
                 "promise"
             ],
-            "time": "2016-11-18 17:47:58"
+            "time": "2016-12-20T10:07:11+00:00"
         },
         {
             "name": "guzzlehttp/psr7",
                 "stream",
                 "uri"
             ],
-            "time": "2016-06-24 23:00:38"
+            "time": "2016-06-24T23:00:38+00:00"
         },
         {
             "name": "instaclick/php-webdriver",
                 "webdriver",
                 "webtest"
             ],
-            "time": "2015-06-15 20:19:33"
+            "time": "2015-06-15T20:19:33+00:00"
         },
         {
             "name": "moodlehq/behat-extension",
-            "version": "v3.33.0",
+            "version": "v3.33.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/moodlehq/moodle-behat-extension.git",
-                "reference": "d363b92f62770acdd8cd878810777f3a61eada4d"
+                "reference": "a1f956fb13ef4c430ceb37c6c1ffcd355d956a22"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d363b92f62770acdd8cd878810777f3a61eada4d",
-                "reference": "d363b92f62770acdd8cd878810777f3a61eada4d",
+                "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/a1f956fb13ef4c430ceb37c6c1ffcd355d956a22",
+                "reference": "a1f956fb13ef4c430ceb37c6c1ffcd355d956a22",
                 "shasum": ""
             },
             "require": {
-                "behat/behat": "3.2.*",
+                "behat/behat": "3.3.*",
                 "behat/mink": "~1.7",
                 "behat/mink-extension": "~2.2",
                 "behat/mink-goutte-driver": "~1.2",
                 "Behat",
                 "moodle"
             ],
-            "time": "2016-11-10 23:36:48"
+            "time": "2017-01-20T02:48:22+00:00"
         },
         {
             "name": "myclabs/deep-copy",
                 "object",
                 "object graph"
             ],
-            "time": "2016-10-31 17:19:45"
+            "time": "2016-10-31T17:19:45+00:00"
         },
         {
             "name": "phpdocumentor/reflection-common",
                 "reflection",
                 "static analysis"
             ],
-            "time": "2015-12-27 11:43:31"
+            "time": "2015-12-27T11:43:31+00:00"
         },
         {
             "name": "phpdocumentor/reflection-docblock",
                 }
             ],
             "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
-            "time": "2016-09-30 07:12:33"
+            "time": "2016-09-30T07:12:33+00:00"
         },
         {
             "name": "phpdocumentor/type-resolver",
                     "email": "me@mikevanriel.com"
                 }
             ],
-            "time": "2016-11-25 06:54:22"
+            "time": "2016-11-25T06:54:22+00:00"
         },
         {
             "name": "phpspec/prophecy",
                 "spy",
                 "stub"
             ],
-            "time": "2016-11-21 14:58:47"
+            "time": "2016-11-21T14:58:47+00:00"
         },
         {
             "name": "phpunit/dbunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2015-08-07 04:57:38"
+            "time": "2015-08-07T04:57:38+00:00"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "4.0.3",
+            "version": "4.0.4",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929"
+                "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/903fd6318d0a90b4770a009ff73e4a4e9c437929",
-                "reference": "903fd6318d0a90b4770a009ff73e4a4e9c437929",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c14196e64a78570034afd0b7a9f3757ba71c2a0a",
+                "reference": "c14196e64a78570034afd0b7a9f3757ba71c2a0a",
                 "shasum": ""
             },
             "require": {
                 "testing",
                 "xunit"
             ],
-            "time": "2016-11-28 16:00:31"
+            "time": "2016-12-20T15:22:42+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
                 "filesystem",
                 "iterator"
             ],
-            "time": "2016-10-03 07:40:28"
+            "time": "2016-10-03T07:40:28+00:00"
         },
         {
             "name": "phpunit/php-text-template",
             "keywords": [
                 "template"
             ],
-            "time": "2015-06-21 13:50:34"
+            "time": "2015-06-21T13:50:34+00:00"
         },
         {
             "name": "phpunit/php-timer",
             "keywords": [
                 "timer"
             ],
-            "time": "2016-05-12 18:03:57"
+            "time": "2016-05-12T18:03:57+00:00"
         },
         {
             "name": "phpunit/php-token-stream",
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2016-11-15 14:06:22"
+            "time": "2016-11-15T14:06:22+00:00"
         },
         {
             "name": "phpunit/phpunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2016-10-03 13:04:15"
+            "time": "2016-10-03T13:04:15+00:00"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
                 "mock",
                 "xunit"
             ],
-            "time": "2016-12-08 20:27:08"
+            "time": "2016-12-08T20:27:08+00:00"
         },
         {
             "name": "psr/http-message",
                 "request",
                 "response"
             ],
-            "time": "2016-08-06 14:39:51"
+            "time": "2016-08-06T14:39:51+00:00"
         },
         {
             "name": "psr/log",
                 "psr",
                 "psr-3"
             ],
-            "time": "2016-10-10 12:19:37"
+            "time": "2016-10-10T12:19:37+00:00"
         },
         {
             "name": "sebastian/code-unit-reverse-lookup",
             ],
             "description": "Looks up which function or method a line of code belongs to",
             "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
-            "time": "2016-02-13 06:45:14"
+            "time": "2016-02-13T06:45:14+00:00"
         },
         {
             "name": "sebastian/comparator",
                 "compare",
                 "equality"
             ],
-            "time": "2016-11-19 09:18:40"
+            "time": "2016-11-19T09:18:40+00:00"
         },
         {
             "name": "sebastian/diff",
             "keywords": [
                 "diff"
             ],
-            "time": "2015-12-08 07:14:41"
+            "time": "2015-12-08T07:14:41+00:00"
         },
         {
             "name": "sebastian/environment",
                 "environment",
                 "hhvm"
             ],
-            "time": "2016-11-26 07:53:53"
+            "time": "2016-11-26T07:53:53+00:00"
         },
         {
             "name": "sebastian/exporter",
                 "export",
                 "exporter"
             ],
-            "time": "2016-06-17 09:04:28"
+            "time": "2016-06-17T09:04:28+00:00"
         },
         {
             "name": "sebastian/global-state",
             "keywords": [
                 "global state"
             ],
-            "time": "2015-10-12 03:26:01"
+            "time": "2015-10-12T03:26:01+00:00"
         },
         {
             "name": "sebastian/object-enumerator",
             ],
             "description": "Traverses array structures and object graphs to enumerate all referenced objects",
             "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
-            "time": "2016-01-28 13:25:10"
+            "time": "2016-01-28T13:25:10+00:00"
         },
         {
             "name": "sebastian/recursion-context",
             ],
             "description": "Provides functionality to recursively process PHP variables",
             "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
-            "time": "2015-11-11 19:50:13"
+            "time": "2015-11-11T19:50:13+00:00"
         },
         {
             "name": "sebastian/resource-operations",
             ],
             "description": "Provides a list of PHP built-in functions that operate on resources",
             "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
-            "time": "2015-07-28 20:34:47"
+            "time": "2015-07-28T20:34:47+00:00"
         },
         {
             "name": "sebastian/version",
             ],
             "description": "Library that helps with managing the version number of Git-hosted PHP projects",
             "homepage": "https://github.com/sebastianbergmann/version",
-            "time": "2016-10-03 07:35:21"
+            "time": "2016-10-03T07:35:21+00:00"
         },
         {
             "name": "symfony/browser-kit",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/browser-kit.git",
-                "reference": "34348c2691ce6254e8e008026f4c5e72c22bb318"
+                "reference": "548f8230bad9f77463b20b15993a008f03e96db5"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/34348c2691ce6254e8e008026f4c5e72c22bb318",
-                "reference": "34348c2691ce6254e8e008026f4c5e72c22bb318",
+                "url": "https://api.github.com/repos/symfony/browser-kit/zipball/548f8230bad9f77463b20b15993a008f03e96db5",
+                "reference": "548f8230bad9f77463b20b15993a008f03e96db5",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony BrowserKit Component",
             "homepage": "https://symfony.com",
-            "time": "2016-10-13 13:35:11"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/class-loader",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/class-loader.git",
-                "reference": "87cd4e69435d98de01d0162c5f9c0ac017075c63"
+                "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/class-loader/zipball/87cd4e69435d98de01d0162c5f9c0ac017075c63",
-                "reference": "87cd4e69435d98de01d0162c5f9c0ac017075c63",
+                "url": "https://api.github.com/repos/symfony/class-loader/zipball/0152f7a47acd564ca62c652975c2b32ac6d613a6",
+                "reference": "0152f7a47acd564ca62c652975c2b32ac6d613a6",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony ClassLoader Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-29 08:26:13"
+            "time": "2017-01-10T14:14:38+00:00"
         },
         {
             "name": "symfony/config",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "4a68f8953180bf77ea65f585020f4db0b18600b4"
+                "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/4a68f8953180bf77ea65f585020f4db0b18600b4",
-                "reference": "4a68f8953180bf77ea65f585020f4db0b18600b4",
+                "url": "https://api.github.com/repos/symfony/config/zipball/c5ea878b5a7f6a01b9a2f182f905831711b9ff3f",
+                "reference": "c5ea878b5a7f6a01b9a2f182f905831711b9ff3f",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-29 11:12:32"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/console",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/console.git",
-                "reference": "09d0fd33560e3573185a2ea17614e37ba38716c5"
+                "reference": "4f9e449e76996adf310498a8ca955c6deebe29dd"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/console/zipball/09d0fd33560e3573185a2ea17614e37ba38716c5",
-                "reference": "09d0fd33560e3573185a2ea17614e37ba38716c5",
+                "url": "https://api.github.com/repos/symfony/console/zipball/4f9e449e76996adf310498a8ca955c6deebe29dd",
+                "reference": "4f9e449e76996adf310498a8ca955c6deebe29dd",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Console Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-16 22:18:16"
+            "time": "2017-01-08T20:47:33+00:00"
         },
         {
             "name": "symfony/css-selector",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/css-selector.git",
-                "reference": "e1241f275814827c411d922ba8e64cf2a00b2994"
+                "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/css-selector/zipball/e1241f275814827c411d922ba8e64cf2a00b2994",
-                "reference": "e1241f275814827c411d922ba8e64cf2a00b2994",
+                "url": "https://api.github.com/repos/symfony/css-selector/zipball/f0e628f04fc055c934b3211cfabdb1c59eefbfaa",
+                "reference": "f0e628f04fc055c934b3211cfabdb1c59eefbfaa",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony CssSelector Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-03 08:11:03"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/debug",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/debug.git",
-                "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231"
+                "reference": "810ba5c1c5352a4ddb15d4719e8936751dff0b05"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/debug/zipball/9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
-                "reference": "9f923e68d524a3095c5a2ae5fc7220c7cbc12231",
+                "url": "https://api.github.com/repos/symfony/debug/zipball/810ba5c1c5352a4ddb15d4719e8936751dff0b05",
+                "reference": "810ba5c1c5352a4ddb15d4719e8936751dff0b05",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Debug Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-16 22:18:16"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "f5419adad083c90e0dfd8588ef83683d7dbcc20d"
+                "reference": "22b2c97cffc6a612db82084f9e7823b095958751"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f5419adad083c90e0dfd8588ef83683d7dbcc20d",
-                "reference": "f5419adad083c90e0dfd8588ef83683d7dbcc20d",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/22b2c97cffc6a612db82084f9e7823b095958751",
+                "reference": "22b2c97cffc6a612db82084f9e7823b095958751",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-25 12:32:42"
+            "time": "2017-01-10T14:21:25+00:00"
         },
         {
             "name": "symfony/dom-crawler",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dom-crawler.git",
-                "reference": "c6b6111f5aae7c58698cdc10220785627ac44a2c"
+                "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c6b6111f5aae7c58698cdc10220785627ac44a2c",
-                "reference": "c6b6111f5aae7c58698cdc10220785627ac44a2c",
+                "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/27d9790840a4efd3b7bb8f5f4f9efc27b36b7024",
+                "reference": "27d9790840a4efd3b7bb8f5f4f9efc27b36b7024",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony DomCrawler Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-25 12:32:42"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "e8f47a327c2f0fd5aa04fa60af2b693006ed7283"
+                "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e8f47a327c2f0fd5aa04fa60af2b693006ed7283",
-                "reference": "e8f47a327c2f0fd5aa04fa60af2b693006ed7283",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9137eb3a3328e413212826d63eeeb0217836e2b6",
+                "reference": "9137eb3a3328e413212826d63eeeb0217836e2b6",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2016-10-13 06:29:04"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4"
+                "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4",
-                "reference": "8d4cf7561a5b17e5eb7a02b80d0b8f014a3796d4",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/a0c6ef2dc78d33b58d91d3a49f49797a184d06f4",
+                "reference": "a0c6ef2dc78d33b58d91d3a49f49797a184d06f4",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-24 00:46:43"
+            "time": "2017-01-08T20:47:33+00:00"
         },
         {
             "name": "symfony/polyfill-mbstring",
                 "portable",
                 "shim"
             ],
-            "time": "2016-11-14 01:06:16"
+            "time": "2016-11-14T01:06:16+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.14",
+            "version": "v2.8.16",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f"
+                "reference": "ebb3c2abe0940a703f08e0cbe373f62d97d40231"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f",
-                "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f",
+                "url": "https://api.github.com/repos/symfony/process/zipball/ebb3c2abe0940a703f08e0cbe373f62d97d40231",
+                "reference": "ebb3c2abe0940a703f08e0cbe373f62d97d40231",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2016-09-29 14:03:54"
+            "time": "2017-01-02T20:30:24+00:00"
         },
         {
             "name": "symfony/translation",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/translation.git",
-                "reference": "64ab6fc0b42e5386631f408e6adcaaff8eee5ba1"
+                "reference": "6520f3d4cce604d9dd1e86cac7af954984dd9bda"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/translation/zipball/64ab6fc0b42e5386631f408e6adcaaff8eee5ba1",
-                "reference": "64ab6fc0b42e5386631f408e6adcaaff8eee5ba1",
+                "url": "https://api.github.com/repos/symfony/translation/zipball/6520f3d4cce604d9dd1e86cac7af954984dd9bda",
+                "reference": "6520f3d4cce604d9dd1e86cac7af954984dd9bda",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Translation Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-18 21:17:59"
+            "time": "2017-01-02T20:32:22+00:00"
         },
         {
             "name": "symfony/yaml",
-            "version": "v3.2.0",
+            "version": "v3.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/yaml.git",
-                "reference": "f2300ba8fbb002c028710b92e1906e7457410693"
+                "reference": "50eadbd7926e31842893c957eca362b21592a97d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/yaml/zipball/f2300ba8fbb002c028710b92e1906e7457410693",
-                "reference": "f2300ba8fbb002c028710b92e1906e7457410693",
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/50eadbd7926e31842893c957eca362b21592a97d",
+                "reference": "50eadbd7926e31842893c957eca362b21592a97d",
                 "shasum": ""
             },
             "require": {
             ],
             "description": "Symfony Yaml Component",
             "homepage": "https://symfony.com",
-            "time": "2016-11-18 21:17:59"
+            "time": "2017-01-03T13:51:32+00:00"
         },
         {
             "name": "webmozart/assert",
                 "check",
                 "validate"
             ],
-            "time": "2016-11-23 20:04:58"
+            "time": "2016-11-23T20:04:58+00:00"
         }
     ],
     "aliases": [],
index eb759d8..7c60ee0 100644 (file)
@@ -774,6 +774,10 @@ M.course_dndupload = {
                                 resel.li.outerHTML = unescape(resel.li.outerHTML);
                             }
                             self.add_editing(result.elementid);
+                            // Fire the content updated event.
+                            require(['core/event', 'jquery'], function(event, $) {
+                                event.notifyFilterContentUpdated($(result.fullcontent));
+                            });
                         } else {
                             // Error - remove the dummy element
                             resel.parent.removeChild(resel.li);
index 73277e0..d509033 100644 (file)
@@ -2012,7 +2012,7 @@ class core_course_external extends external_api {
     /**
      * Describes the parameters for delete_modules.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 2.5
      */
     public static function delete_modules_parameters() {
@@ -2398,6 +2398,16 @@ class core_course_external extends external_api {
                 'timemodified' => new external_value(PARAM_INT, 'Last time  the course was updated', VALUE_OPTIONAL),
                 'requested' => new external_value(PARAM_INT, 'If is a requested course', VALUE_OPTIONAL),
                 'cacherev' => new external_value(PARAM_INT, 'Cache revision number', VALUE_OPTIONAL),
+                'filters' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'filter'  => new external_value(PARAM_PLUGIN, 'Filter plugin name'),
+                            'localstate' => new external_value(PARAM_INT, 'Filter state: 1 for on, -1 for off, 0 if inherit'),
+                            'inheritedstate' => new external_value(PARAM_INT, '1 or 0 to use when localstate is set to inherit'),
+                        )
+                    ),
+                    'Course filters', VALUE_OPTIONAL
+                ),
             );
             $coursestructure = array_merge($coursestructure, $extra);
         }
@@ -2923,6 +2933,7 @@ class core_course_external extends external_api {
     public static function get_courses_by_field($field = '', $value = '') {
         global $DB, $CFG;
         require_once($CFG->libdir . '/coursecatlib.php');
+        require_once($CFG->libdir . '/filterlib.php');
 
         $params = self::validate_parameters(self::get_courses_by_field_parameters(),
             array(
@@ -2985,6 +2996,9 @@ class core_course_external extends external_api {
                 'groupmode', 'groupmodeforce', 'defaultgroupingid', 'enablecompletion', 'completionnotify', 'lang', 'theme',
                 'sortorder', 'marker');
 
+            // Course filters.
+            $coursesdata[$course->id]['filters'] = filter_get_available_in_context($context);
+
             // Information for managers only.
             if ($canupdatecourse) {
                 $managerfields = array('idnumber', 'legacyfiles', 'calendartype', 'timecreated', 'timemodified', 'requested',
index 2f474b2..4c83c5a 100644 (file)
@@ -4,7 +4,7 @@ Feature: Change number of discussions displayed
   As a teacher
   I need to edit the course and change the number of sections displayed.
 
-Background:
+  Background:
     Given the following "users" exist:
       | username | firstname | lastname | email |
       | teacher1 | Teacher | 1 | teacher1@example.com |
@@ -96,28 +96,28 @@ Background:
     And I wait to be redirected
     And I follow "Course 1"
 
-Scenario: When number of discussions is decreased fewer discussions appear
+  Scenario: When number of discussions is decreased fewer discussions appear
     Given I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
-    | numdiscussions | 5 |
+      | numdiscussions | 5 |
     When I press "Save and display"
     Then I should see "This is forum post one"
     And I should see "This is forum post five"
     And I should not see "This is forum post six"
 
-Scenario: When number of discussions is decreased to less than 1 only 1 discussion should appear
+  Scenario: When number of discussions is decreased to less than 1 only 1 discussion should appear
     Given I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
-    | numdiscussions | -1 |
+      | numdiscussions | -1 |
     When I press "Save and display"
     Then I should see "This is forum post one"
     And I should not see "This is forum post two"
     And I should not see "This is forum post ten"
 
-Scenario: When number of discussions is increased more discussions appear
+  Scenario: When number of discussions is increased more discussions appear
     Given I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
-    | numdiscussions | 9 |
+      | numdiscussions | 9 |
     When I press "Save and display"
     Then I should see "This is forum post one"
     And I should see "This is forum post five"
index 1bb7129..042094c 100644 (file)
@@ -255,64 +255,64 @@ Feature: We can change the visibility of categories in the management interface.
     And course in management listing should be dimmed "C2"
     And course in management listing should be visible "C3"
 
-    @javascript @_cross_browser
-    Scenario: Test courses are hidden when selected category parent is hidden.
-      Given the following "categories" exist:
-        | name | category | idnumber |
-        | Cat 1 | 0 | CAT1 |
-        | Cat 2 | CAT1 | CAT2 |
-        | Cat 3 | CAT2 | CAT3 |
-      And the following "courses" exist:
-        | category | fullname | shortname | idnumber |
-        | CAT3 | Course 1 | Course 1 | C1 |
+  @javascript @_cross_browser
+  Scenario: Test courses are hidden when selected category parent is hidden.
+    Given the following "categories" exist:
+      | name | category | idnumber |
+      | Cat 1 | 0 | CAT1 |
+      | Cat 2 | CAT1 | CAT2 |
+      | Cat 3 | CAT2 | CAT3 |
+    And the following "courses" exist:
+      | category | fullname | shortname | idnumber |
+      | CAT3 | Course 1 | Course 1 | C1 |
 
-      And I log in as "admin"
-      And I go to the courses management page
-      And I start watching to see if a new page loads
-      And I should see the "Course categories and courses" management page
-      And I click on category "Cat 1" in the management interface
-      And a new page should have loaded since I started watching
-      And I start watching to see if a new page loads
-      And I should see the "Course categories and courses" management page
-      And I click on category "Cat 2" in the management interface
-      And a new page should have loaded since I started watching
-      And I start watching to see if a new page loads
-      And I should see the "Course categories and courses" management page
-      And I click on category "Cat 3" in the management interface
-      And a new page should have loaded since I started watching
-      And I start watching to see if a new page loads
-      And I should see the "Course categories and courses" management page
-      And category in management listing should be visible "CAT1"
-      And category in management listing should be visible "CAT2"
-      And category in management listing should be visible "CAT3"
-      And course in management listing should be visible "C1"
-      And I toggle visibility of category "CAT1" in management listing
-      And a new page should not have loaded since I started watching
-      And category in management listing should be dimmed "CAT1"
-      And category in management listing should be dimmed "CAT2"
-      And category in management listing should be dimmed "CAT3"
-      And course in management listing should be dimmed "C1"
-      And I toggle visibility of category "CAT1" in management listing
-      And a new page should not have loaded since I started watching
-      And category in management listing should be visible "CAT1"
-      And category in management listing should be visible "CAT2"
-      And category in management listing should be visible "CAT3"
-      And course in management listing should be visible "C1"
-      And I toggle visibility of course "C1" in management listing
-      And a new page should not have loaded since I started watching
-      And category in management listing should be visible "CAT1"
-      And category in management listing should be visible "CAT2"
-      And category in management listing should be visible "CAT3"
-      And course in management listing should be dimmed "C1"
-      And I toggle visibility of category "CAT1" in management listing
-      And a new page should not have loaded since I started watching
-      And category in management listing should be dimmed "CAT1"
-      And category in management listing should be dimmed "CAT2"
-      And category in management listing should be dimmed "CAT3"
-      And course in management listing should be dimmed "C1"
-      And I toggle visibility of category "CAT1" in management listing
-      And a new page should not have loaded since I started watching
-      And category in management listing should be visible "CAT1"
-      And category in management listing should be visible "CAT2"
-      And category in management listing should be visible "CAT3"
-      And course in management listing should be dimmed "C1"
+    And I log in as "admin"
+    And I go to the courses management page
+    And I start watching to see if a new page loads
+    And I should see the "Course categories and courses" management page
+    And I click on category "Cat 1" in the management interface
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
+    And I should see the "Course categories and courses" management page
+    And I click on category "Cat 2" in the management interface
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
+    And I should see the "Course categories and courses" management page
+    And I click on category "Cat 3" in the management interface
+    And a new page should have loaded since I started watching
+    And I start watching to see if a new page loads
+    And I should see the "Course categories and courses" management page
+    And category in management listing should be visible "CAT1"
+    And category in management listing should be visible "CAT2"
+    And category in management listing should be visible "CAT3"
+    And course in management listing should be visible "C1"
+    And I toggle visibility of category "CAT1" in management listing
+    And a new page should not have loaded since I started watching
+    And category in management listing should be dimmed "CAT1"
+    And category in management listing should be dimmed "CAT2"
+    And category in management listing should be dimmed "CAT3"
+    And course in management listing should be dimmed "C1"
+    And I toggle visibility of category "CAT1" in management listing
+    And a new page should not have loaded since I started watching
+    And category in management listing should be visible "CAT1"
+    And category in management listing should be visible "CAT2"
+    And category in management listing should be visible "CAT3"
+    And course in management listing should be visible "C1"
+    And I toggle visibility of course "C1" in management listing
+    And a new page should not have loaded since I started watching
+    And category in management listing should be visible "CAT1"
+    And category in management listing should be visible "CAT2"
+    And category in management listing should be visible "CAT3"
+    And course in management listing should be dimmed "C1"
+    And I toggle visibility of category "CAT1" in management listing
+    And a new page should not have loaded since I started watching
+    And category in management listing should be dimmed "CAT1"
+    And category in management listing should be dimmed "CAT2"
+    And category in management listing should be dimmed "CAT3"
+    And course in management listing should be dimmed "C1"
+    And I toggle visibility of category "CAT1" in management listing
+    And a new page should not have loaded since I started watching
+    And category in management listing should be visible "CAT1"
+    And category in management listing should be visible "CAT2"
+    And category in management listing should be visible "CAT3"
+    And course in management listing should be dimmed "C1"
index 24bfaad..5dfda56 100644 (file)
@@ -5,12 +5,11 @@ Feature: Courses can be searched for and moved in bulk.
   I need to be able to search courses in bulk and move them around
 
   Background:
-     Given the following "categories" exist:
+    Given the following "categories" exist:
       | name | category | idnumber |
       | Science | 0 | SCI |
       | English | 0 | ENG |
       | Miscellaneous | 0 | MISC |
-
     And the following "courses" exist:
       | fullname | shortname | category |
       | Biology Y1 | BIO1 | MISC |
index 07333d1..284a59d 100644 (file)
@@ -48,4 +48,3 @@ Feature: Force group mode in a course
     And I press "Save and display"
     Then "//a/child::img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist
     And "//img[contains(@alt, 'No groups (forced mode)')]" "xpath_element" should not exist
-
index 9730e53..14194c0 100644 (file)
@@ -28,40 +28,40 @@ Feature: View subfolders in a course in-line
     And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element"
     And I press "Save changes"
 
-    @javascript
-    Scenario: Add a folder with two subfolders - view on separate page
-      Given I follow "Course 1"
-      And I should not see "Test subfolder 1"
-      And I follow "Test folder"
-      And I should see "Test subfolder 1"
-      And I press "Edit"
-      And I press "Create folder"
-      And I set the field "New folder name" to "Test subfolder 2"
-      And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element"
-      And I press "Save changes"
-      When I follow "Course 1"
-      Then I should not see "Test subfolder 2"
-      And I follow "Test folder"
-      And I should see "Test subfolder 2"
+  @javascript
+  Scenario: Add a folder with two subfolders - view on separate page
+    Given I follow "Course 1"
+    And I should not see "Test subfolder 1"
+    And I follow "Test folder"
+    And I should see "Test subfolder 1"
+    And I press "Edit"
+    And I press "Create folder"
+    And I set the field "New folder name" to "Test subfolder 2"
+    And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element"
+    And I press "Save changes"
+    When I follow "Course 1"
+    Then I should not see "Test subfolder 2"
+    And I follow "Test folder"
+    And I should see "Test subfolder 2"
 
-    @javascript
-    Scenario: Make the subfolders viewable inline on the course page
-      Given I press "Edit"
-      And I click on "div.fp-filename" "css_element" in the "div.fp-filename-field" "css_element"
-      And I press "Create folder"
-      And I set the field "New folder name" to "Test sub subfolder"
-      And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element"
-      And I press "Save changes"
-      And I should see "Test sub subfolder"
-      And I navigate to "Edit settings" in current page administration
-      And I set the field "Display folder contents" to "Inline on a course page"
-      And I set the field "Show subfolders expanded" to ""
-      And I press "Save and return to course"
-      And I should see "Test subfolder 1"
-      And I should not see "Test sub subfolder"
-      And I open "Test folder" actions menu
-      When I click on "Edit settings" "link" in the "Test folder" activity
-      And I set the field "Show subfolders expanded" to "1"
-      And I press "Save and return to course"
-      Then I should see "Test subfolder 1"
-      And I should see "Test sub subfolder"
+  @javascript
+  Scenario: Make the subfolders viewable inline on the course page
+    Given I press "Edit"
+    And I click on "div.fp-filename" "css_element" in the "div.fp-filename-field" "css_element"
+    And I press "Create folder"
+    And I set the field "New folder name" to "Test sub subfolder"
+    And I click on "button.fp-dlg-butcreate" "css_element" in the "div.fp-mkdir-dlg" "css_element"
+    And I press "Save changes"
+    And I should see "Test sub subfolder"
+    And I navigate to "Edit settings" in current page administration
+    And I set the field "Display folder contents" to "Inline on a course page"
+    And I set the field "Show subfolders expanded" to ""
+    And I press "Save and return to course"
+    And I should see "Test subfolder 1"
+    And I should not see "Test sub subfolder"
+    And I open "Test folder" actions menu
+    When I click on "Edit settings" "link" in the "Test folder" activity
+    And I set the field "Show subfolders expanded" to "1"
+    And I press "Save and return to course"
+    Then I should see "Test subfolder 1"
+    And I should see "Test sub subfolder"
index b6a9045..e67ec73 100644 (file)
@@ -2019,7 +2019,7 @@ class core_course_courselib_testcase extends advanced_testcase {
 
         // Create the course with sections.
         $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
-        $sections = $DB->get_records('course_sections', array('course' => $course->id));
+        $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
         $coursecontext = context_course::instance($course->id);
         $section = array_pop($sections);
         course_delete_section($course, $section);
index 70d25af..7d732b0 100644 (file)
@@ -1976,16 +1976,16 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(3, $result['courses']);
         // Expect to receive all the fields.
-        $this->assertCount(35, $result['courses'][0]);
-        $this->assertCount(35, $result['courses'][1]);
-        $this->assertCount(35, $result['courses'][2]);
+        $this->assertCount(36, $result['courses'][0]);
+        $this->assertCount(36, $result['courses'][1]);
+        $this->assertCount(36, $result['courses'][2]);
 
         $result = core_course_external::get_courses_by_field('id', $course1->id);
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(1, $result['courses']);
         $this->assertEquals($course1->id, $result['courses'][0]['id']);
         // Expect to receive all the fields.
-        $this->assertCount(35, $result['courses'][0]);
+        $this->assertCount(36, $result['courses'][0]);
 
         $result = core_course_external::get_courses_by_field('id', $course2->id);
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
@@ -1996,6 +1996,10 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(2, $result['courses']);
 
+        // Check default filters.
+        $this->assertCount(3, $result['courses'][0]['filters']);
+        $this->assertCount(3, $result['courses'][1]['filters']);
+
         $result = core_course_external::get_courses_by_field('category', $category1->id);
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(1, $result['courses']);
@@ -2015,20 +2019,34 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(0, $result['courses']);
 
+        // Change filter value.
+        filter_set_local_state('mediaplugin', context_course::instance($course1->id)->id, TEXTFILTER_OFF);
+
         self::setUser($student1);
         // All visible courses  (including front page) for normal student.
         $result = core_course_external::get_courses_by_field();
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(2, $result['courses']);
-        $this->assertCount(28, $result['courses'][0]);
-        $this->assertCount(28, $result['courses'][1]);
+        $this->assertCount(29, $result['courses'][0]);
+        $this->assertCount(29, $result['courses'][1]);
 
         $result = core_course_external::get_courses_by_field('id', $course1->id);
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(1, $result['courses']);
         $this->assertEquals($course1->id, $result['courses'][0]['id']);
         // Expect to receive all the files that a student can see.
-        $this->assertCount(28, $result['courses'][0]);
+        $this->assertCount(29, $result['courses'][0]);
+
+        // Check default filters.
+        $filters = $result['courses'][0]['filters'];
+        $this->assertCount(3, $filters);
+        $found = false;
+        foreach ($filters as $filter) {
+            if ($filter['filter'] == 'mediaplugin' and $filter['localstate'] == TEXTFILTER_OFF) {
+                $found = true;
+            }
+        }
+        $this->assertTrue($found);
 
         // Course 2 is not visible.
         $result = core_course_external::get_courses_by_field('id', $course2->id);
@@ -2062,7 +2080,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $result = core_course_external::get_courses_by_field();
         $result = external_api::clean_returnvalue(core_course_external::get_courses_by_field_returns(), $result);
         $this->assertCount(2, $result['courses']);
-        $this->assertCount(28, $result['courses'][0]);  // Site course.
+        $this->assertCount(29, $result['courses'][0]);  // Site course.
         $this->assertCount(12, $result['courses'][1]);  // Only public information, not enrolled.
 
         $result = core_course_external::get_courses_by_field('id', $course1->id);
index 047e0be..b38361f 100644 (file)
@@ -1,6 +1,10 @@
 This files describes API changes in /course/*,
 information provided here is intended especially for developers.
 
+=== 3.3 ===
+
+ * External function core_course_external::get_courses_by_field now returns the course filters list and status.
+
 === 3.2 ===
 
  * External function core_course_external::get_course_contents now returns the section's number in the course (new section field).
index e2d1f9e..c724a4b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="enrol/lti/db" VERSION="20160809" COMMENT="XMLDB file for Moodle enrol/lti"
+<XMLDB PATH="enrol/lti/db" VERSION="20170113" COMMENT="XMLDB file for Moodle enrol/lti"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="true"/>
         <FIELD NAME="consumerid" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="value" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="value" TYPE="char" LENGTH="64" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="expires" TYPE="int" LENGTH="10"  NOTNULL="true" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
index 94a90cc..14042f7 100644 (file)
@@ -245,5 +245,18 @@ function xmldb_enrol_lti_upgrade($oldversion) {
     // Automatically generated Moodle v3.2.0 release upgrade line.
     // Put any upgrade step following this.
 
+    if ($oldversion < 2017011300) {
+
+        // Changing precision of field value on table enrol_lti_lti2_nonce to (64).
+        $table = new xmldb_table('enrol_lti_lti2_nonce');
+        $field = new xmldb_field('value', XMLDB_TYPE_CHAR, '64', null, XMLDB_NOTNULL, null, null, 'consumerid');
+
+        // Launch change of precision for field value.
+        $dbman->change_field_precision($table, $field);
+
+        // Lti savepoint reached.
+        upgrade_plugin_savepoint(true, 2017011300, 'enrol', 'lti');
+    }
+
     return true;
 }
index 366f925..11167f1 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2016120500; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2017011300; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires = 2016112900; // Requires this Moodle version (3.1)
 $plugin->component = 'enrol_lti'; // Full name of the plugin (used for diagnostics).
index 03e2e38..0ed9a61 100644 (file)
@@ -4,7 +4,6 @@ Feature: Add a new custom file type
   As an admin
   I need to add a new custom file type
 
-
   @javascript
   Scenario: Add custom file type
     Given the following "courses" exist:
index 2d9378b..d154df1 100644 (file)
@@ -919,6 +919,11 @@ class grade_report_grader extends grade_report {
         // user.
         if (!$this->canviewhidden) {
             $allgradeitems = grade_item::fetch_all(array('courseid' => $this->courseid));
+
+            // But hang on - don't include ones which are set to not show the grade at all.
+            $allgradeitems = array_filter($allgradeitems, function($item) {
+                return $item->gradetype != GRADE_TYPE_NONE;
+            });
         }
 
         foreach ($this->users as $userid => $user) {
index e274a36..dddab82 100644 (file)
@@ -4,7 +4,6 @@ Feature: Using the AJAX grading feature of Grader report to update grades and fe
   As a teacher
   I need to be able to update and verify grades
 
-
   Background:
     Given the following "courses" exist:
       | fullname | shortname | category | groupmode |
index abdb080..8b73f27 100644 (file)
@@ -42,7 +42,7 @@ class gradereport_overview_external extends external_api {
     /**
      * Describes the parameters for get_course_grades.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.2
      */
     public static function get_course_grades_parameters() {
index 9ed53bd..d002d00 100644 (file)
@@ -208,7 +208,7 @@ class gradereport_user_external extends external_api {
     /**
      * Describes the parameters for get_grades_table.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 2.9
      */
     public static function get_grades_table_parameters() {
@@ -424,7 +424,7 @@ class gradereport_user_external extends external_api {
     /**
      * Describes the parameters for get_grade_items.
      *
-     * @return external_external_function_parameters
+     * @return external_function_parameters
      * @since Moodle 3.2
      */
     public static function get_grade_items_parameters() {
index c0bebe6..3fc9563 100644 (file)
@@ -8,10 +8,10 @@ Feature: We can use the user report
       | fullname | shortname | category |
       | Course 1 | C1 | 0 |
 
-    Scenario: Verify we can view a user grade report with no users enrolled.
-      Given I log in as "admin"
-      And I am on site homepage
-      And I follow "Course 1"
-      And I navigate to "View > User report" in the course gradebook
-      And I select "All users (0)" from the "Select all or one user" singleselect
-      Then I should see "No students enrolled in this course yet"
+  Scenario: Verify we can view a user grade report with no users enrolled.
+    Given I log in as "admin"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I navigate to "View > User report" in the course gradebook
+    And I select "All users (0)" from the "Select all or one user" singleselect
+    Then I should see "No students enrolled in this course yet"
index 4a1bbe9..bc1f2ea 100644 (file)
@@ -301,16 +301,4 @@ class behat_grade extends behat_base {
 
         $this->select_in_gradebook_tabs($gradepath);
     }
-
-    /**
-     * Navigates to the course gradebook and selects a specified item from the grade navigation tabs.
-     *
-     * @todo MDL-57282 deprecate in Moodle 3.3
-     *
-     * @Given /^I go to "(?P<gradepath_string>(?:[^"]|\\")*)" in the course gradebook$/
-     * @param string $gradepath
-     */
-    public function i_go_to_in_the_course_gradebook($gradepath) {
-        $this->execute('behat_grade::i_navigate_to_in_the_course_gradebook', $gradepath);
-    }
 }
index a8ecfae..5704689 100644 (file)
@@ -52,7 +52,7 @@ Feature: Calculated grade items can be used in the gradebook
 
   @javascript
   Scenario: Changing max grade for a category item with a calculation that has existing grades will display the same points with the new max grade values immediately.
-  Given I press "Add category"
+    Given I press "Add category"
     And I set the following fields to these values:
       | Category name | Calc cat |
     And I press "Save changes"
index fa38d4a..ef381aa 100644 (file)
@@ -1,5 +1,5 @@
 @core_grades
-Feature: Editing a grade item
+Feature: Grade item validation
   In order to ensure validation is provided to the teacher
   As a teacher
   I need to know why I can not add/edit values on the grade item form
index 072c32d..8ec6daf 100644 (file)
@@ -1,5 +1,5 @@
 @core @core_grades
-Feature: We can customise the letter boundary of a course.
+Feature: We can customise the letter boundary of a course in gradebook version 20160518.
   In order to change the letter boundary of a course
   As a teacher
   I need to add assessments to the gradebook.
index 1f10b75..82dbd8d 100644 (file)
@@ -229,39 +229,37 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
 
   @javascript
   Scenario: With one grade item set as extra credit, when I reset the weights for a category they return to the natural weights.
-
-  When I set the following settings for grade item "Test assignment five":
-    | Extra credit | 1 |
-  And I set the field "Override weight of Test assignment six" to "1"
-  And I set the field "Override weight of Test assignment seven" to "1"
-  And I set the field "Weight of Test assignment six" to "55"
-  And I set the field "Weight of Test assignment seven" to "40"
-  And I press "Save changes"
-  And I reset weights for grade category "Sub category 1"
-  Then the field "Weight of Test assignment five" matches value "80.0"
-  And the field "Weight of Test assignment six" matches value "40.0"
-  And the field "Weight of Test assignment seven" matches value "60.0"
+    When I set the following settings for grade item "Test assignment five":
+      | Extra credit | 1 |
+    And I set the field "Override weight of Test assignment six" to "1"
+    And I set the field "Override weight of Test assignment seven" to "1"
+    And I set the field "Weight of Test assignment six" to "55"
+    And I set the field "Weight of Test assignment seven" to "40"
+    And I press "Save changes"
+    And I reset weights for grade category "Sub category 1"
+    Then the field "Weight of Test assignment five" matches value "80.0"
+    And the field "Weight of Test assignment six" matches value "40.0"
+    And the field "Weight of Test assignment seven" matches value "60.0"
 
   @javascript
   Scenario: Overriding a grade item with a negative value results in the value being changed to zero.
-
-  When I set the field "Override weight of Test assignment five" to "1"
-  And I set the field "Weight of Test assignment five" to "-15"
-  And I press "Save changes"
-  Then the field "Weight of Test assignment five" matches value "0.0"
-  And the field "Weight of Test assignment six" matches value "40.0"
-  And the field "Weight of Test assignment seven" matches value "60.0"
-  And I set the field "Override weight of Test assignment six" to "1"
-  And I set the field "Weight of Test assignment six" to "-25"
-  And I press "Save changes"
-  And the field "Weight of Test assignment six" matches value "0.0"
-  And the field "Weight of Test assignment seven" matches value "100.0"
-  And I reset weights for grade category "Sub category 1"
-  And I set the field "Override weight of Test assignment five" to "1"
-  And I set the field "Override weight of Test assignment six" to "1"
-  And I set the field "Weight of Test assignment five" to "-10"
-  And I set the field "Weight of Test assignment six" to "120"
-  And I press "Save changes"
-  And the field "Weight of Test assignment five" matches value "0.0"
-  And the field "Weight of Test assignment six" matches value "100.0"
-  And the field "Weight of Test assignment seven" matches value "0.0"
+    When I set the field "Override weight of Test assignment five" to "1"
+    And I set the field "Weight of Test assignment five" to "-15"
+    And I press "Save changes"
+    Then the field "Weight of Test assignment five" matches value "0.0"
+    And the field "Weight of Test assignment six" matches value "40.0"
+    And the field "Weight of Test assignment seven" matches value "60.0"
+    And I set the field "Override weight of Test assignment six" to "1"
+    And I set the field "Weight of Test assignment six" to "-25"
+    And I press "Save changes"
+    And the field "Weight of Test assignment six" matches value "0.0"
+    And the field "Weight of Test assignment seven" matches value "100.0"
+    And I reset weights for grade category "Sub category 1"
+    And I set the field "Override weight of Test assignment five" to "1"
+    And I set the field "Override weight of Test assignment six" to "1"
+    And I set the field "Weight of Test assignment five" to "-10"
+    And I set the field "Weight of Test assignment six" to "120"
+    And I press "Save changes"
+    And the field "Weight of Test assignment five" matches value "0.0"
+    And the field "Weight of Test assignment six" matches value "100.0"
+    And the field "Weight of Test assignment seven" matches value "0.0"
index 8282265..d65e285 100644 (file)
@@ -230,6 +230,74 @@ class core_grade_report_graderlib_testcase extends advanced_testcase {
         $this->assertEquals(count($toobigvalue['gradesonly']) - 1, count($report1->collapsed['gradesonly']));
     }
 
+    /**
+     * Tests the get_right_rows function with one 'normal' and one 'ungraded' quiz.
+     *
+     * Previously, with an ungraded quiz (which results in a grade item with type GRADETYPE_NONE)
+     * there was a bug in get_right_rows in some situations.
+     */
+    public function test_get_right_rows() {
+        global $USER, $DB;
+        $this->resetAfterTest(true);
+
+        // Create manager and student on a course.
+        $generator = $this->getDataGenerator();
+        $manager = $generator->create_user();
+        $student = $generator->create_user();
+        $course = $generator->create_course();
+        $generator->enrol_user($manager->id, $course->id, 'manager');
+        $generator->enrol_user($student->id, $course->id, 'student');
+
+        // Create a couple of quizzes on the course.
+        $normalquiz = $generator->create_module('quiz', array('course' => $course->id,
+                'name' => 'NormalQuiz'));
+        $ungradedquiz = $generator->create_module('quiz', array('course' => $course->id,
+                'name' => 'UngradedQuiz'));
+
+        // Set the grade for the second one to 0 (note, you have to do this after creating it,
+        // otherwise it doesn't create an ungraded grade item).
+        $ungradedquiz->instance = $ungradedquiz->id;
+        quiz_set_grade(0, $ungradedquiz);
+
+        // Set current user.
+        $this->setUser($manager);
+        $USER->gradeediting[$course->id] = false;
+
+        // Get the report.
+        $report = $this->create_report($course);
+        $report->load_users();
+        $report->load_final_grades();
+        $result = $report->get_right_rows(false);
+
+        // There should be 3 rows (2 header rows, plus the grades for the first user).
+        $this->assertCount(3, $result);
+
+        // The second row should contain 2 cells - one for the graded quiz and course total.
+        $this->assertCount(2, $result[1]->cells);
+        $this->assertContains('NormalQuiz', $result[1]->cells[0]->text);
+        $this->assertContains('Course total', $result[1]->cells[1]->text);
+
+        // User row should contain grade values '-'.
+        $this->assertCount(2, $result[2]->cells);
+        $this->assertContains('>-<', $result[2]->cells[0]->text);
+        $this->assertContains('>-<', $result[2]->cells[1]->text);
+
+        // Supposing the user cannot view hidden grades, this shouldn't make any difference (due
+        // to a bug, it previously did).
+        $context = context_course::instance($course->id);
+        $managerroleid = $DB->get_field('role', 'id', array('shortname' => 'manager'));
+        assign_capability('moodle/grade:viewhidden', CAP_PROHIBIT, $managerroleid, $context->id, true);
+        $context->mark_dirty();
+        $this->assertFalse(has_capability('moodle/grade:viewhidden', $context));
+
+        // Recreate the report. Confirm it returns successfully still.
+        $report = $this->create_report($course);
+        $report->load_users();
+        $report->load_final_grades();
+        $result = $report->get_right_rows(false);
+        $this->assertCount(3, $result);
+    }
+
     private function create_grade_category($course) {
         static $cnt = 0;
         $cnt++;
index 000ecc8..855e234 100644 (file)
@@ -30,6 +30,9 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['cannotcreatedboninstall'] = '<p> لا يمكن إنشاء قاعدة البيانات. </p>
+<p> لا وجود لقاعدة البيانات المحددة والمستخدم المُعين ليس لديه إذن لإنشاء قاعدة البيانات. </ p>
+<p> المسؤول عن الموقع يجب أن يتحقق من إعدادات قاعدة بيانات. </p>';
 $string['cannotcreatelangdir'] = 'لا يمكن إنشاء مجلد اللغة';
 $string['cannotcreatetempdir'] = 'لا يمكن إنشاء المجلد المؤقت';
 $string['cannotdownloadcomponents'] = 'لم يتم تحميل العناصر';
@@ -39,4 +42,7 @@ $string['cannotsavemd5file'] = 'لم يتم حفظ ملف  md5';
 $string['cannotsavezipfile'] = 'لم يتم حفظ الملف المضغوط';
 $string['cannotunzipfile'] = 'لم يتم فك الملف المضغوط';
 $string['componentisuptodate'] = 'العنصر محدث';
+$string['dmlexceptiononinstall'] = '<p>حدث خطأ في قاعدة البيانات[{$a->errorcode}].<br />{$a->debuginfo}</p>';
+$string['downloadedfilecheckfailed'] = 'فشل التحقق من الملف الذي تم تنزيله';
+$string['invalidmd5'] = 'المتغير المُختار خاطئ - حاول مرة أخرى';
 $string['missingrequiredfield'] = 'بعض الحقول المطلوبة مفقودة';
index 0aa04d5..45cf7ad 100644 (file)
@@ -31,4 +31,4 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['parentlanguage'] = 'es_mx';
-$string['thislanguage'] = 'Español - México - kids';
+$string['thislanguage'] = 'Español de México para niños';
index 38d796f..c883b16 100644 (file)
@@ -36,6 +36,7 @@ $string['chooselanguagehead'] = 'انتخاب زبان';
 $string['chooselanguagesub'] = 'لطفاً زبانی را به جهت استفاده در حین نصب انتخاب نمایید. زبانی که در این صفحه انتخاب می‌کنید به عنوان زبان پیش‌فرض سایت نیز مورد استفاده قرار خواهد گرفت. البته می‌توانید بعداً آن را تغییر دهید.<br />ترجمهٔ فارسی این نسخه با همکاری <a href="http://foodle.org" target="_blank">گروه فودل</a> آماده شده است.';
 $string['databasehost'] = 'میزبان پایگاه داده';
 $string['databasename'] = 'نام پایگاه داده';
+$string['databasetypehead'] = 'راه‌انداز پایگاه داده را انتخاب کنید';
 $string['dataroot'] = 'دایرکتوری داده';
 $string['dbprefix'] = 'پیشوند جدول‌ها';
 $string['dirroot'] = 'دایرکتوری مودل';
index ee24963..d6f3b71 100644 (file)
@@ -31,6 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Valoda';
+$string['moodlelogo'] = 'Moodle logo';
 $string['next'] = 'Nākamais';
 $string['previous'] = 'Iepriekšējais';
 $string['reload'] = 'Atkārtoti ielādēt';
index 810f999..8d256d3 100644 (file)
@@ -32,4 +32,3 @@ Feature: Atto accessibility helper
     # This shows the current HTML tags applied to the selected text.
     # This is required because they are not always read by a screen reader.
     Then I should see "UL, LI"
-
index 1303890..e6af81b 100644 (file)
@@ -53,4 +53,3 @@ Feature: Atto align text
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "style=\"text-align:center;\""
-
index c458ee6..0e63a89 100644 (file)
@@ -20,4 +20,3 @@ Feature: Atto clear button
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should not see "<i>Pisa"
-
index 88f1aab..f6ff892 100644 (file)
@@ -11,4 +11,3 @@ Feature: Atto collapse button
     Then "Equation editor" "button" should be visible
     And I click on "Show fewer buttons" "button"
     Then "Equation editor" "button" should not be visible
-
index 4afc696..ae1d92c 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-debug.js differ
index 5740db9..a5ff287 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button-min.js differ
index 0f955ef..f98b58c 100644 (file)
Binary files a/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js and b/lib/editor/atto/plugins/equation/yui/build/moodle-atto_equation-button/moodle-atto_equation-button.js differ
index 45e90d3..849a967 100644 (file)
@@ -448,13 +448,15 @@ Y.namespace('M.atto_equation').Button = Y.Base.create('button', Y.M.editor_atto.
         while (equation.charAt(currentPos) === '\\' && currentPos >= 0) {
             currentPos -= 1;
         }
-        isChar = /[a-zA-Z\{\}]/;
+        isChar = /[a-zA-Z\{]/;
         if (currentPos !== 0) {
-            // Now match to the end of the line.
-            while (isChar.test(equation.charAt(currentPos)) &&
-                   currentPos < equation.length &&
-                   isChar.test(equation.charAt(currentPos - 1))) {
-                currentPos += 1;
+            if (equation.charAt(currentPos - 1) != '{') {
+                // Now match to the end of the line.
+                while (isChar.test(equation.charAt(currentPos)) &&
+                       currentPos < equation.length &&
+                       isChar.test(equation.charAt(currentPos - 1))) {
+                    currentPos += 1;
+                }
             }
         }
         // Save the cursor position - for insertion from the library.
index 1357cfc..f4a7472 100644 (file)
@@ -11,4 +11,3 @@ Feature: Atto edit HTML
     And I click on "Show more buttons" "button"
     And I click on "HTML" "button"
     Then the field "Description" matches value "<p style=\"color: blue;\">Smurf</p>"
-
index 4d02eb6..f948733 100644 (file)
@@ -24,4 +24,3 @@ Feature: Add links to Atto
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "Super cool</a>"
-
index 60ec2c7..03cc0de 100644 (file)
@@ -3,164 +3,164 @@ Feature: Add media to Atto
   To write rich text - I need to add media.
 
   Background:
-  Given I log in as "admin"
-  And I follow "Manage private files..."
-  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.webm" file to "Files" filemanager
-  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.mp4" file to "Files" filemanager
-  And I upload "lib/editor/atto/tests/fixtures/moodle-logo.png" file to "Files" filemanager
-  And I upload "lib/editor/atto/tests/fixtures/pretty-good-en.vtt" file to "Files" filemanager
-  And I upload "lib/editor/atto/tests/fixtures/pretty-good-sv.vtt" file to "Files" filemanager
-  And I click on "Save changes" "button"
-  And I follow "Profile" in the user menu
-  And I follow "Blog entries"
-  And I follow "Add a new entry"
-  And I set the field "Blog entry body" to "<p>Media test</p>"
-  And I select the text in the "Blog entry body" Atto editor
-  And I set the field "Entry title" to "The best video in the entire world (not really)"
-  And I click on "Media" "button"
+    Given I log in as "admin"
+    And I follow "Manage private files..."
+    And I upload "lib/editor/atto/tests/fixtures/moodle-logo.webm" file to "Files" filemanager
+    And I upload "lib/editor/atto/tests/fixtures/moodle-logo.mp4" file to "Files" filemanager
+    And I upload "lib/editor/atto/tests/fixtures/moodle-logo.png" file to "Files" filemanager
+    And I upload "lib/editor/atto/tests/fixtures/pretty-good-en.vtt" file to "Files" filemanager
+    And I upload "lib/editor/atto/tests/fixtures/pretty-good-sv.vtt" file to "Files" filemanager
+    And I click on "Save changes" "button"
+    And I follow "Profile" in the user menu
+    And I follow "Blog entries"
+    And I follow "Add a new entry"
+    And I set the field "Blog entry body" to "<p>Media test</p>"
+    And I select the text in the "Blog entry body" Atto editor
+    And I set the field "Entry title" to "The best video in the entire world (not really)"
+    And I click on "Media" "button"
 
   @javascript
   Scenario: Insert some media as a link
-  Given I click on "Browse repositories..." "button" in the "#id_summary_editor_link .atto_media_source.atto_media_link_source" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.webm" "link"
-  And I click on "Select this file" "button"
-  And the field "Enter name" matches value "moodle-logo.webm"
-  And I wait until the page is ready
-  And I click on "Insert media" "button"
-  When I click on "Save changes" "button"
-  Then "//a[. = 'moodle-logo.webm']" "xpath_element" should exist
+    Given I click on "Browse repositories..." "button" in the "#id_summary_editor_link .atto_media_source.atto_media_link_source" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.webm" "link"
+    And I click on "Select this file" "button"
+    And the field "Enter name" matches value "moodle-logo.webm"
+    And I wait until the page is ready
+    And I click on "Insert media" "button"
+    When I click on "Save changes" "button"
+    Then "//a[. = 'moodle-logo.webm']" "xpath_element" should exist
 
   @javascript
   Scenario: Insert some media as a plain video
-  Given I click on "Video" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.webm" "link"
-  And I click on "Select this file" "button"
-  And I click on "Add alternative source" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source:nth-of-type(2)" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.mp4" "link"
-  And I click on "Select this file" "button"
-  When I click on "Insert media" "button"
-  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::source[contains(@src, 'moodle-logo.mp4')]]" "xpath_element" should exist
+    Given I click on "Video" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.webm" "link"
+    And I click on "Select this file" "button"
+    And I click on "Add alternative source" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source:nth-of-type(2)" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.mp4" "link"
+    And I click on "Select this file" "button"
+    When I click on "Insert media" "button"
+    Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::source[contains(@src, 'moodle-logo.mp4')]]" "xpath_element" should exist
 
   @javascript
   Scenario: Insert some media as a video with display settings
-  Given I click on "Video" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.webm" "link"
-  And I click on "Select this file" "button"
-  And I click on "Display options" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_poster_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "moodle-logo.png" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I change window size to "large"
-  And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_width_entry ')]" to "420"
-  And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_height_entry ')]" to "69"
-  And I click on "Display options" "link"
-  When I click on "Insert media" "button"
-  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][contains(@poster, 'moodle-logo.png')][@width=420][@height=69]" "xpath_element" should exist
+    Given I click on "Video" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.webm" "link"
+    And I click on "Select this file" "button"
+    And I click on "Display options" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_poster_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "moodle-logo.png" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I change window size to "large"
+    And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_width_entry ')]" to "420"
+    And I set the field with xpath "//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_height_entry ')]" to "69"
+    And I click on "Display options" "link"
+    When I click on "Insert media" "button"
+    Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][contains(@poster, 'moodle-logo.png')][@width=420][@height=69]" "xpath_element" should exist
 
   @javascript
   Scenario: Insert some media as a video with advanced settings
-  Given I click on "Video" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.webm" "link"
-  And I click on "Select this file" "button"
-  And I click on "Advanced settings" "link"
-  And the field "Show controls" matches value "1"
-  And I set the field "Play automatically" to "1"
-  And I set the field "Muted" to "1"
-  And I set the field "Loop" to "1"
-  When I click on "Insert media" "button"
-  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][@controls='true'][@loop='true'][@autoplay='true'][@autoplay='true']" "xpath_element" should exist
+    Given I click on "Video" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.webm" "link"
+    And I click on "Select this file" "button"
+    And I click on "Advanced settings" "link"
+    And the field "Show controls" matches value "1"
+    And I set the field "Play automatically" to "1"
+    And I set the field "Muted" to "1"
+    And I set the field "Loop" to "1"
+    When I click on "Insert media" "button"
+    Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][@controls='true'][@loop='true'][@autoplay='true'][@autoplay='true']" "xpath_element" should exist
 
   @javascript
   Scenario: Insert some media as a video with tracks
-  Given I click on "Video" "link"
-  And I change window size to "large"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
-  And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
-  And I click on "moodle-logo.webm" "link"
-  And I click on "Select this file" "button"
-  And I click on "Subtitles and captions" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-sv.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And the field "Label" matches value "Swedish"
-  And the field "Language" matches value "sv"
-  And I click on "Add subtitle track" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-en.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[2]" matches value "English"
-  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[1]" to "1"
-  And I click on "Captions" "link" in the ".nav-item[data-track-kind='captions']" "css_element"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-sv.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[3]" matches value "Swedish"
-  And I click on "Add caption track" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-en.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[4]" matches value "English"
-  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[4]" to "1"
-  And I click on "Descriptions" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And  I click on "pretty-good-sv.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[5]" matches value "Swedish"
-  And I click on "Add description track" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-en.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[6]" matches value "English"
-  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[5]" to "1"
-  And I click on "Chapters" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And  I click on "pretty-good-sv.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[7]" matches value "Swedish"
-  And I click on "Add chapter track" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-en.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[8]" matches value "English"
-  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[8]" to "1"
-  And I click on "Metadata" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And  I click on "pretty-good-sv.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[9]" matches value "Swedish"
-  And I click on "Add metadata track" "link"
-  And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
-  And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
-  And I click on "pretty-good-en.vtt" "link"
-  And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
-  And I click on "Overwrite" "button"
-  And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[10]" matches value "English"
-  And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[9]" to "1"
-  When I click on "Insert media" "button"
-  Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='subtitles'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='subtitles'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='captions'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='captions'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='descriptions'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='descriptions'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='chapters'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='chapters'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='metadata'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='metadata'][@label='English'][@srclang='en'][not(@default)]]" "xpath_element" should exist
+    Given I click on "Video" "link"
+    And I change window size to "large"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video .atto_media_source.atto_media_media_source" "css_element"
+    And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
+    And I click on "moodle-logo.webm" "link"
+    And I click on "Select this file" "button"
+    And I click on "Subtitles and captions" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-sv.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And the field "Label" matches value "Swedish"
+    And the field "Language" matches value "sv"
+    And I click on "Add subtitle track" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_subtitles .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-en.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[2]" matches value "English"
+    And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[1]" to "1"
+    And I click on "Captions" "link" in the ".nav-item[data-track-kind='captions']" "css_element"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-sv.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[3]" matches value "Swedish"
+    And I click on "Add caption track" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_captions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-en.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[4]" matches value "English"
+    And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[4]" to "1"
+    And I click on "Descriptions" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And  I click on "pretty-good-sv.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[5]" matches value "Swedish"
+    And I click on "Add description track" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_descriptions .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-en.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[6]" matches value "English"
+    And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[5]" to "1"
+    And I click on "Chapters" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And  I click on "pretty-good-sv.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[7]" matches value "Swedish"
+    And I click on "Add chapter track" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_chapters .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-en.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[8]" matches value "English"
+    And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[8]" to "1"
+    And I click on "Metadata" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And  I click on "pretty-good-sv.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[9]" matches value "Swedish"
+    And I click on "Add metadata track" "link"
+    And I click on "Browse repositories..." "button" in the "#id_summary_editor_video_metadata .atto_media_track~.atto_media_track .atto_media_source.atto_media_track_source" "css_element"
+    And I click on "Private files" "link" in the ".moodle-dialogue-base[aria-hidden='false'] .fp-repo-area" "css_element"
+    And I click on "pretty-good-en.vtt" "link"
+    And I click on "Select this file" "button" in the ".moodle-dialogue-base[aria-hidden='false']" "css_element"
+    And I click on "Overwrite" "button"
+    And the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_label_entry ')])[10]" matches value "English"
+    And I set the field with xpath "(//*[contains(concat(' ', normalize-space(@class), ' '), ' atto_media_track_default ')])[9]" to "1"
+    When I click on "Insert media" "button"
+    Then "//video[descendant::source[contains(@src, 'moodle-logo.webm')]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='subtitles'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='subtitles'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='captions'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='captions'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='descriptions'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='descriptions'][@label='English'][@srclang='en'][not(@default)]][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='chapters'][@label='Swedish'][@srclang='sv'][not(@default)]][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='chapters'][@label='English'][@srclang='en'][@default='true']][descendant::track[contains(@src, 'pretty-good-sv.vtt')][@kind='metadata'][@label='Swedish'][@srclang='sv'][@default='true']][descendant::track[contains(@src, 'pretty-good-en.vtt')][@kind='metadata'][@label='English'][@srclang='en'][not(@default)]]" "xpath_element" should exist
index 5c38924..26ad2bf 100644 (file)
@@ -18,4 +18,3 @@ Feature: Atto strike button
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "<strike>MUA</strike>"
-
index 8581a8b..ac629ce 100644 (file)
@@ -35,4 +35,3 @@ Feature: Atto subscript button
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "<sub>Submarine</sub>"
-
index 2e3ed49..44dd871 100644 (file)
@@ -35,4 +35,3 @@ Feature: Atto superscript button
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "<sup>Helicopter</sup>"
-
index 527f284..f145be3 100644 (file)
@@ -18,4 +18,3 @@ Feature: Atto title
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "<h3>How The Rock"
-
index c30716c..5733155 100644 (file)
@@ -18,4 +18,3 @@ Feature: Atto underline button
     And I press "Save changes"
     And I click on "Edit profile" "link" in the "region-main" "region"
     Then I should see "<u>Deprecated HTML Tag</u>"
-
index edab4a1..90e0996 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-debug.js differ
index b706ced..22df82b 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor-min.js differ
index 9d68afe..1b88fea 100644 (file)
Binary files a/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js and b/lib/editor/atto/yui/build/moodle-editor_atto-editor/moodle-editor_atto-editor.js differ
index bd167c5..1763547 100644 (file)
@@ -147,9 +147,9 @@ EditorClean.prototype = {
                     isHTML = (types.indexOf('text/html') > -1);
                 }
 
+                var content;
                 if (isHTML) {
                     // Get the clipboard content.
-                    var content;
                     try {
                         content = event.clipboardData.getData('text/html');
                     } catch (error) {
@@ -179,10 +179,16 @@ EditorClean.prototype = {
                     this.updateOriginal();
                     return false;
                 } else {
-                    // Due to poor cross browser clipboard compatibility, the failure to find html doesn't mean it isn't there.
-                    // Wait for the clipboard event to finish then fallback clean the entire editor.
-                    this.fallbackPasteCleanupDelayed();
-                    return true;
+                    try {
+                        // Plaintext clipboard content can only be retrieved this way.
+                        content = event.clipboardData.getData('text');
+                    } catch (error) {
+                        // Something went wrong. Fallback.
+                        // Due to poor cross browser clipboard compatibility, the failure to find html doesn't mean it isn't there.
+                        // Wait for the clipboard event to finish then fallback clean the entire editor.
+                        this.fallbackPasteCleanupDelayed();
+                        return true;
+                    }
                 }
             } else {
                 // If we reached a here, this probably means the browser has limited (or no) clipboard support.
index 452a899..8bb17af 100644 (file)
@@ -1179,6 +1179,10 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only
  * Returns an array of joins, wheres and params that will limit the group of
  * users to only those enrolled and with given capability (if specified).
  *
+ * Note this join will return duplicate rows for users who have been enrolled
+ * several times (e.g. as manual enrolment, and as self enrolment). You may
+ * need to use a SELECT DISTINCT in your query (see get_enrolled_sql for example).
+ *
  * @param context $context
  * @param string $prefix optional, a prefix to the user id column
  * @param string|array $capability optional, may include a capability name, or array of names.
index 0fb9922..c481ebe 100644 (file)
@@ -799,10 +799,12 @@ class file_storage {
      * @param string $sort A fragment of SQL to use for sorting
      * @param bool $includedirs whether or not include directories
      * @param int $updatedsince return files updated since this time
+     * @param int $limitfrom return a subset of records, starting at this point (optional).
+     * @param int $limitnum return a subset comprising this many records in total (optional, required if $limitfrom is set).
      * @return stored_file[] array of stored_files indexed by pathanmehash
      */
     public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort = "itemid, filepath, filename",
-                                    $includedirs = true, $updatedsince = 0) {
+            $includedirs = true, $updatedsince = 0, $limitfrom = 0, $limitnum = 0) {
         global $DB;
 
         list($areasql, $conditions) = $DB->get_in_or_equal($filearea, SQL_PARAMS_NAMED);
@@ -824,6 +826,16 @@ class file_storage {
             $updatedsincesql = 'AND f.timemodified > :time';
         }
 
+        $includedirssql = '';
+        if (!$includedirs) {
+            $includedirssql = 'AND f.filename != :dot';
+            $conditions['dot'] = '.';
+        }
+
+        if ($limitfrom && !$limitnum) {
+            throw new coding_exception('If specifying $limitfrom you must also specify $limitnum');
+        }
+
         $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
                   FROM {files} f
              LEFT JOIN {files_reference} r
@@ -831,6 +843,7 @@ class file_storage {
                  WHERE f.contextid = :contextid
                        AND f.component = :component
                        AND f.filearea $areasql
+                       $includedirssql
                        $updatedsincesql
                        $itemidsql";
         if (!empty($sort)) {
@@ -838,11 +851,8 @@ class file_storage {
         }
 
         $result = array();
-        $filerecords = $DB->get_records_sql($sql, $conditions);
+        $filerecords = $DB->get_records_sql($sql, $conditions, $limitfrom, $limitnum);
         foreach ($filerecords as $filerecord) {
-            if (!$includedirs and $filerecord->filename === '.') {
-                continue;
-            }
             $result[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
         }
         return $result;
index 45b89cb..f16ada7 100644 (file)
@@ -466,6 +466,17 @@ class core_files_file_storage_testcase extends advanced_testcase {
             $this->assertEquals($key, $file->get_pathnamehash());
         }
 
+        // Test the limit feature to retrieve each individual file.
+        $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
+                0, 0, 1);
+        $mapfunc = function($f) {
+            return $f->get_filename();
+        };
+        $this->assertEquals(array('1.txt'), array_values(array_map($mapfunc, $limited)));
+        $limited = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'filename', false,
+                0, 1, 50);
+        $this->assertEquals(array('2.txt', '3.txt'), array_values(array_map($mapfunc, $limited)));
+
         // Test with an itemid with no files.
         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', 666, 'sortorder', false);
         // Should be none.