Merge branch 'wip-MDL-52566-master' of https://github.com/cdsmith-umn/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 15 Feb 2016 07:17:13 +0000 (15:17 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 15 Feb 2016 07:17:13 +0000 (15:17 +0800)
249 files changed:
Gruntfile.js
auth/ldap/auth.php
auth/mnet/land.php
backup/backup.php
backup/restore.php
blocks/activity_results/tests/behat/addunsupportedactivity.feature
blocks/activity_results/tests/behat/highscoreswithscales.feature
blocks/activity_results/tests/behat/highscoreswithscalesandgroups.feature
blocks/activity_results/tests/behat/lowscoreswithscales.feature
blocks/activity_results/tests/behat/lowscoreswithscalesandgroups.feature
blocks/blog_tags/block_blog_tags.php
blocks/blog_tags/tests/behat/blogtag.feature
blocks/glossary_random/backup/moodle2/restore_glossary_random_block_task.class.php
blocks/navigation/amd/build/ajax_response_renderer.min.js [new file with mode: 0644]
blocks/navigation/amd/build/nav_loader.min.js [new file with mode: 0644]
blocks/navigation/amd/build/navblock.min.js [new file with mode: 0644]
blocks/navigation/amd/build/site_admin_loader.min.js [new file with mode: 0644]
blocks/navigation/amd/src/ajax_response_renderer.js [new file with mode: 0644]
blocks/navigation/amd/src/nav_loader.js [new file with mode: 0644]
blocks/navigation/amd/src/navblock.js [new file with mode: 0644]
blocks/navigation/amd/src/site_admin_loader.js [new file with mode: 0644]
blocks/navigation/block_navigation.php
blocks/navigation/renderer.php
blocks/navigation/styles.css
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js [deleted file]
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js [deleted file]
blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js [deleted file]
blocks/navigation/yui/src/navigation/build.json [deleted file]
blocks/navigation/yui/src/navigation/js/navigation.js [deleted file]
blocks/navigation/yui/src/navigation/meta/navigation.json [deleted file]
blocks/settings/amd/build/settingsblock.min.js [new file with mode: 0644]
blocks/settings/amd/src/settingsblock.js [new file with mode: 0644]
blocks/settings/block_settings.php
blocks/settings/renderer.php
blocks/settings/styles.css
blocks/tags/block_tags.php
blocks/tags/edit_form.php
blocks/tags/lang/en/block_tags.php
blocks/tags/tests/behat/tagcloud.feature
blocks/tests/behat/configure_block_throughout_site.feature
blocks/upgrade.txt
blog/external_blogs.php
blog/tests/lib_test.php
completion/criteria/completion_criteria_grade.php
completion/tests/behat/behat_completion.php
config-dist.php
course/modlib.php
course/moodleform_mod.php
course/tests/behat/behat_course.php
course/tests/behat/coursetags.feature
enrol/ldap/lib.php
enrol/ldap/tests/ldap_test.php
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js
filter/glossary/yui/src/autolinker/js/autolinker.js
grade/edit/tree/item.php
grade/edit/tree/item_form.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_calculated_grade_items.feature
grade/tests/behat/grade_calculated_grade_items_20150627.feature
grade/tests/behat/grade_grade_minmax_change.feature [new file with mode: 0644]
grade/tests/behat/grade_item_validation.feature [new file with mode: 0644]
grade/tests/behat/grade_minmax.feature
grade/tests/behat/grade_point_maximum.feature
grade/tests/behat/grade_scales_aggregation.feature
install/lang/bg/moodle.php
install/lang/dk_kursus/langconfig.php [new file with mode: 0644]
install/lang/he/error.php
install/lang/he/install.php
install/lang/oc_lnc/error.php
install/lang/or/langconfig.php [new file with mode: 0644]
install/lang/ro/install.php
install/lang/th/moodle.php
lang/en/deprecated.txt
lang/en/error.php
lang/en/grades.php
lang/en/role.php
lang/en/tag.php
lib/amd/build/fragment.min.js [new file with mode: 0644]
lib/amd/build/tag.min.js
lib/amd/build/tree.min.js [new file with mode: 0644]
lib/amd/src/fragment.js [new file with mode: 0644]
lib/amd/src/tag.js
lib/amd/src/tree.js [new file with mode: 0644]
lib/behat/behat_base.php
lib/blocklib.php
lib/classes/plugininfo/availability.php
lib/classes/task/delete_incomplete_users_task.php
lib/db/install.xml
lib/db/services.php
lib/db/tag.php
lib/db/upgrade.php
lib/deprecatedlib.php
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/selection.js
lib/excellib.class.php
lib/external/externallib.php
lib/filelib.php
lib/form/advcheckbox.php
lib/form/modgrade.php
lib/form/tags.php
lib/form/tests/behat/modgrade_validation.feature [new file with mode: 0644]
lib/formslib.php
lib/grade/grade_item.php
lib/grade/tests/grade_item_test.php
lib/ldaplib.php
lib/moodlelib.php
lib/navigationlib.php
lib/outputfragmentrequirementslib.php [new file with mode: 0644]
lib/pagelib.php
lib/pear/HTML/QuickForm.php
lib/pear/HTML/QuickForm/DHTMLRulesTableless.php
lib/pear/HTML/QuickForm/Renderer/Tableless.php
lib/pear/README_MOODLE.txt
lib/phpmailer/moodle_phpmailer.php
lib/phpunit/classes/util.php
lib/testing/classes/util.php
lib/testing/generator/data_generator.php
lib/tests/behat/behat_hooks.php
lib/tests/behat/behat_navigation.php
lib/tests/formslib_test.php
lib/tests/ldaplib_test.php
lib/tests/moodlelib_test.php
lib/upgrade.txt
login/signup_form.php
login/token.php
message/tests/behat/delete_messages.feature
mnet/peer.php
mod/assign/feedback/editpdf/tests/behat/annotate_pdf.feature
mod/assign/gradingtable.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/tests/behat/allow_another_attempt.feature
mod/assign/tests/behat/edit_previous_feedback.feature
mod/assign/tests/behat/rescale_grades.feature [new file with mode: 0644]
mod/assign/tests/events_test.php
mod/assign/tests/externallib_test.php
mod/assign/tests/locallib_test.php
mod/assign/yui/build/moodle-mod_assign-history/moodle-mod_assign-history-debug.js
mod/assign/yui/build/moodle-mod_assign-history/moodle-mod_assign-history-min.js
mod/assign/yui/build/moodle-mod_assign-history/moodle-mod_assign-history.js
mod/assign/yui/src/history/js/history.js
mod/choice/classes/event/answer_deleted.php [new file with mode: 0644]
mod/choice/classes/event/report_downloaded.php [new file with mode: 0644]
mod/choice/lang/en/choice.php
mod/choice/lib.php
mod/choice/report.php
mod/choice/tests/events_test.php
mod/choice/version.php
mod/choice/view.php
mod/data/js.php
mod/folder/db/install.xml
mod/folder/db/upgrade.php
mod/folder/download_folder.php [new file with mode: 0644]
mod/folder/lang/en/folder.php
mod/folder/lib.php
mod/folder/mod_form.php
mod/folder/renderer.php
mod/folder/settings.php
mod/folder/tests/externallib_test.php
mod/folder/version.php
mod/forum/db/messages.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/templates/forum_post_email_htmlemail.mustache
mod/forum/templates/forum_post_email_htmlemail_body.mustache [new file with mode: 0644]
mod/forum/templates/forum_post_emaildigestfull_htmlemail.mustache
mod/forum/tests/generator/lib.php
mod/forum/tests/lib_test.php
mod/forum/tests/maildigest_test.php
mod/forum/version.php
mod/imscp/tests/externallib_test.php
mod/lti/tests/externallib_test.php
mod/page/tests/externallib_test.php
mod/quiz/classes/structure.php
mod/resource/tests/externallib_test.php
mod/scorm/locallib.php
mod/scorm/tests/lib_test.php
mod/scorm/version.php
mod/survey/report.php
mod/url/tests/externallib_test.php
mod/wiki/parser/markups/html.php
mod/wiki/parser/markups/wikimarkup.php
mod/wiki/tests/behat/edit_tags.feature
mod/wiki/tests/fixtures/input/html/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/creole/2
mod/wiki/tests/fixtures/output/creole/3
mod/wiki/tests/fixtures/output/creole/4
mod/wiki/tests/fixtures/output/creole/5
mod/wiki/tests/fixtures/output/creole/7
mod/wiki/tests/fixtures/output/creole/8
mod/wiki/tests/fixtures/output/creole/9
mod/wiki/tests/fixtures/output/html/1
mod/wiki/tests/fixtures/output/html/3 [new file with mode: 0644]
mod/wiki/tests/fixtures/output/nwiki/1
mod/wiki/tests/fixtures/output/nwiki/2
mod/wiki/tests/fixtures/output/nwiki/3
mod/wiki/tests/wikiparser_test.php
mod/workshop/classes/event/submission_deleted.php [new file with mode: 0644]
mod/workshop/db/access.php
mod/workshop/lang/en/workshop.php
mod/workshop/submission.php
mod/workshop/tests/behat/delete_submission.feature [new file with mode: 0644]
mod/workshop/version.php
npm-shrinkwrap.json
package.json
question/engine/datalib.php
question/tests/previewlib_test.php [new file with mode: 0644]
question/type/ddimageortext/styles.css
question/type/ddwtos/styles.css
tag/classes/area.php
tag/classes/areas_table.php
tag/classes/collection.php
tag/classes/external.php
tag/classes/manage_table.php
tag/classes/output/tag.php
tag/classes/output/tagcloud.php
tag/classes/output/taglist.php
tag/classes/renderer.php
tag/classes/tag.php
tag/edit.php
tag/edit_form.php
tag/manage.php
tag/templates/tagcloud.mustache
tag/templates/tagisstandard.mustache [moved from tag/templates/tagtype.mustache with 66% similarity]
tag/templates/taglist.mustache
tag/tests/behat/collections.feature
tag/tests/behat/delete_tag.feature
tag/tests/behat/edit_tag.feature
tag/tests/behat/flag_tags.feature
tag/tests/behat/official_tags.feature [deleted file]
tag/tests/behat/standard_tags.feature [new file with mode: 0644]
tag/tests/external_test.php
tag/tests/taglib_test.php
tag/upgrade.txt
theme/bootstrapbase/less/moodle/bootstrapoverride.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/style/moodle.css
user/edit_form.php
user/editadvanced_form.php
user/editlib.php
user/forum.php
user/lib.php
user/tests/behat/name_fields.feature [new file with mode: 0644]
version.php

index 3dd3ef0..7412970 100644 (file)
@@ -12,6 +12,7 @@
 //
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+/* jshint node: true, browser: false */
 
 /**
  * @copyright  2014 Andrew Nicols
 
 module.exports = function(grunt) {
     var path = require('path'),
-        fs = require('fs'),
         tasks = {},
-        cwd = process.env.PWD || process.cwd(),
-        inAMD = path.basename(cwd) == 'amd';
+        cwd = process.env.PWD || process.cwd();
+
+    // Windows users can't run grunt in a subdirectory, so allow them to set
+    // the root by passing --root=path/to/dir.
+    if (grunt.option('root')) {
+        var root = grunt.option('root');
+        if (grunt.file.exists(__dirname, root)) {
+            cwd = path.join(__dirname, root);
+            grunt.log.ok('Setting root to '+cwd);
+        } else {
+            grunt.fail.fatal('Setting root to '+root+' failed - path does not exist');
+        }
+    }
+
+    var inAMD = path.basename(cwd) == 'amd';
+
+    // Globbing pattern for matching all AMD JS source files.
+    var amdSrc = [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js'];
+
+    /**
+     * Function to generate the destination for the uglify task
+     * (e.g. build/file.min.js). This function will be passed to
+     * the rename property of files array when building dynamically:
+     * http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
+     *
+     * @param {String} destPath the current destination
+     * @param {String} srcPath the  matched src path
+     * @return {String} The rewritten destination path.
+     */
+    var uglify_rename = function (destPath, srcPath) {
+        destPath = srcPath.replace('src', 'build');
+        destPath = destPath.replace('.js', '.min.js');
+        destPath = path.resolve(cwd, destPath);
+        return destPath;
+    };
 
     // Project configuration.
     grunt.initConfig({
         jshint: {
             options: {jshintrc: '.jshintrc'},
-            files: [inAMD ? cwd + '/src/*.js' : '**/amd/src/*.js']
+            amd: { src: amdSrc }
         },
         uglify: {
-            dynamic_mappings: {
-                files: grunt.file.expandMapping(
-                    ['**/src/*.js', '!**/node_modules/**'],
-                    '',
-                    {
-                        cwd: cwd,
-                        rename: function(destBase, destPath) {
-                            destPath = destPath.replace('src', 'build');
-                            destPath = destPath.replace('.js', '.min.js');
-                            destPath = path.resolve(cwd, destPath);
-                            return destPath;
-                        }
-                    }
-                )
+            amd: {
+                files: [{
+                    expand: true,
+                    src: amdSrc,
+                    rename: uglify_rename
+                }]
             }
         },
         less: {
@@ -62,54 +87,79 @@ module.exports = function(grunt) {
                     compress: true
                 }
            }
+        },
+        watch: {
+            options: {
+                nospawn: true // We need not to spawn so config can be changed dynamically.
+            },
+            amd: {
+                files: ['**/amd/src/**/*.js'],
+                tasks: ['amd']
+            },
+            bootstrapbase: {
+                files: ["theme/bootstrapbase/less/**/*.less"],
+                tasks: ["less:bootstrapbase"]
+            },
+            yui: {
+                files: ['**/yui/src/**/*.js'],
+                tasks: ['shifter']
+            },
+        },
+        shifter: {
+            options: {
+                recursive: true,
+                paths: [cwd]
+            }
         }
     });
 
+    /**
+     * Shifter task. Is configured with a path to a specific file or a directory,
+     * in the case of a specific file it will work out the right module to be built.
+     *
+     * Note that this task runs the invidiaul shifter jobs async (becase it spawns
+     * so be careful to to call done().
+     */
     tasks.shifter = function() {
-       var  exec = require('child_process').spawn,
+        var async = require('async'),
             done = this.async(),
-            args = [],
-            options = {
-                recursive: true,
-                watch: false,
-                walk: false,
-                module: false
-            },
-            shifter;
+            options = grunt.config('shifter.options');
 
+        // Run the shifter processes one at a time to avoid confusing output.
+        async.eachSeries(options.paths, function (src, filedone) {
+            var args = [];
             args.push( path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
 
+            // Always ignore the node_modules directory.
+            args.push('--excludes', 'node_modules');
+
             // Determine the most appropriate options to run with based upon the current location.
-            if (path.basename(cwd) === 'src') {
-                // Detect whether we're in a src directory.
+            if (grunt.file.isMatch('**/yui/**/*.js', src)) {
+                // When passed a JS file, build our containing module (this happen with
+                // watch).
+                grunt.log.debug('Shifter passed a specific JS file');
+                src = path.dirname(path.dirname(src));
+                options.recursive = false;
+            } else if (grunt.file.isMatch('**/yui/src', src)) {
+                // When in a src directory --walk all modules.
                 grunt.log.debug('In a src directory');
                 args.push('--walk');
-                options.walk = true;
-            } else if (path.basename(path.dirname(cwd)) === 'src') {
-                // Detect whether we're in a module directory.
+                options.recursive = false;
+            } else if (grunt.file.isMatch('**/yui/src/*', src)) {
+                // When in module, only build our module.
                 grunt.log.debug('In a module directory');
-                options.module = true;
-            }
-
-            if (grunt.option('watch')) {
-                if (!options.walk && !options.module) {
-                    grunt.fail.fatal('Unable to watch unless in a src or module directory');
-                }
-
-                // It is not advisable to run with recursivity and watch - this
-                // leads to building the build directory in a race-like fashion.
-                grunt.log.debug('Detected a watch - disabling recursivity');
                 options.recursive = false;
-                args.push('--watch');
+            } else if (grunt.file.isMatch('**/yui/src/*/js', src)) {
+                // When in module src, only build our module.
+                grunt.log.debug('In a source directory');
+                src = path.dirname(src);
+                options.recursive = false;
             }
 
-            if (options.recursive) {
-                args.push('--recursive');
+            if (grunt.option('watch')) {
+                grunt.fail.fatal('The --watch option has been removed, please use `grunt watch` instead');
             }
 
-            // Always ignore the node_modules directory.
-            args.push('--excludes', 'node_modules');
-
             // Add the stderr option if appropriate
             if (grunt.option('verbose')) {
                 args.push('--lint-stderr');
@@ -121,19 +171,17 @@ module.exports = function(grunt) {
 
             var execShifter = function() {
 
-                shifter = exec("node", args, {
-                    cwd: cwd,
-                    stdio: 'inherit',
-                    env: process.env
-                });
-
-                // Tidy up after exec.
-                shifter.on('exit', function (code) {
+                grunt.log.ok("Running shifter on " + src);
+                grunt.util.spawn({
+                    cmd: "node",
+                    args: args,
+                    opts: {cwd: src, stdio: 'inherit', env: process.env}
+                }, function (error, result, code) {
                     if (code) {
                         grunt.fail.fatal('Shifter failed with code: ' + code);
                     } else {
                         grunt.log.ok('Shifter build complete.');
-                        done();
+                        filedone();
                     }
                 });
             };
@@ -143,79 +191,15 @@ module.exports = function(grunt) {
                 execShifter();
             } else {
                 // Check that there are yui modules otherwise shifter ends with exit code 1.
-                var found = false;
-                var hasYuiModules = function(directory, callback) {
-                    fs.readdir(directory, function(err, files) {
-                        if (err) {
-                            return callback(err, null);
-                        }
-
-                        // If we already found a match there is no need to continue scanning.
-                        if (found === true) {
-                            return;
-                        }
-
-                        // We need to track the number of files to know when we return a result.
-                        var pending = files.length;
-
-                        // We first check files, so if there is a match we don't need further
-                        // async calls and we just return a true.
-                        for (var i = 0; i < files.length; i++) {
-                            if (files[i] === 'yui') {
-                                return callback(null, true);
-                            }
-                        }
-
-                        // Iterate through subdirs if there were no matches.
-                        files.forEach(function (file) {
-
-                            var p = path.join(directory, file);
-                            stat = fs.statSync(p);
-                            if (!stat.isDirectory()) {
-                                pending--;
-                            } else {
-
-                                // We defer the pending-1 until we scan the whole dir and subdirs.
-                                hasYuiModules(p, function(err, result) {
-                                    if (err) {
-                                        return callback(err);
-                                    }
-
-                                    if (result === true) {
-                                        // Once we get a true we notify the caller.
-                                        found = true;
-                                        return callback(null, true);
-                                    }
-
-                                    pending--;
-                                    if (pending === 0) {
-                                        // Notify the caller that the whole dir has been scaned and there are no matches.
-                                        return callback(null, false);
-                                    }
-                                });
-                            }
-
-                            // No subdirs here, otherwise the return would be deferred until all subdirs are scanned.
-                            if (pending === 0) {
-                                return callback(null, false);
-                            }
-                        });
-                    });
-                };
-
-                hasYuiModules(cwd, function(err, result) {
-                    if (err) {
-                        grunt.fail.fatal(err.message);
-                    }
-
-                    if (result === true) {
-                        execShifter();
-                    } else {
-                        grunt.log.ok('No YUI modules to build.');
-                        done();
-                    }
-                });
+                if (grunt.file.expand({cwd: src}, '**/yui/src/**/*.js').length > 0) {
+                    args.push('--recursive');
+                    execShifter();
+                } else {
+                    grunt.log.ok('No YUI modules to build.');
+                    filedone();
+                }
             }
+        }, done);
     };
 
     tasks.startup = function() {
@@ -232,11 +216,28 @@ module.exports = function(grunt) {
         }
     };
 
+    // On watch, we dynamically modify config to build only affected files. This
+    // method is slightly complicated to deal with multiple changed files at once (copied
+    // from the grunt-contrib-watch readme).
+    var changedFiles = Object.create(null);
+    var onChange = grunt.util._.debounce(function() {
+          var files = Object.keys(changedFiles);
+          grunt.config('jshint.amd.src', files);
+          grunt.config('uglify.amd.files', [{ expand: true, src: files, rename: uglify_rename }]);
+          grunt.config('shifter.options.paths', files);
+          changedFiles = Object.create(null);
+    }, 200);
+
+    grunt.event.on('watch', function(action, filepath) {
+          changedFiles[filepath] = action;
+          onChange();
+    });
 
     // Register NPM tasks.
     grunt.loadNpmTasks('grunt-contrib-uglify');
     grunt.loadNpmTasks('grunt-contrib-jshint');
     grunt.loadNpmTasks('grunt-contrib-less');
+    grunt.loadNpmTasks('grunt-contrib-watch');
 
     // Register JS tasks.
     grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
index 9d3a4ad..0739c59 100644 (file)
@@ -113,31 +113,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         }
 
         // Hack prefix to objectclass
-        if (empty($this->config->objectclass)) {
-            // Can't send empty filter
-            $this->config->objectclass = '(objectClass=*)';
-        } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
-            // Value is 'objectClass=some-string-here', so just add ()
-            // around the value (filter _must_ have them).
-            $this->config->objectclass = '('.$this->config->objectclass.')';
-        } else if (strpos($this->config->objectclass, '(') !== 0) {
-            // Value is 'some-string-not-starting-with-left-parentheses',
-            // which is assumed to be the objectClass matching value.
-            // So build a valid filter with it.
-            $this->config->objectclass = '(objectClass='.$this->config->objectclass.')';
-        } else {
-            // There is an additional possible value
-            // '(some-string-here)', that can be used to specify any
-            // valid filter string, to select subsets of users based
-            // on any criteria. For example, we could select the users
-            // whose objectClass is 'user' and have the
-            // 'enabledMoodleUser' attribute, with something like:
-            //
-            //   (&(objectClass=user)(enabledMoodleUser=1))
-            //
-            // In this particular case we don't need to do anything,
-            // so leave $this->config->objectclass as is.
-        }
+        $this->config->objectclass = ldap_normalise_objectclass($this->config->objectclass);
     }
 
     /**
index 59adbf3..8be9cf3 100644 (file)
@@ -35,6 +35,7 @@ $wantsremoteurl = optional_param('remoteurl', false, PARAM_BOOL);
 $url = new moodle_url('/auth/mnet/jump.php', array('token'=>$token, 'idp'=>$remotewwwroot, 'wantsurl'=>$wantsurl));
 if ($wantsremoteurl !== false) $url->param('remoteurl', $wantsremoteurl);
 $PAGE->set_url($url);
+$PAGE->set_context(context_system::instance());
 
 $site = get_site();
 
index 688726d..4e8ba89 100644 (file)
@@ -138,7 +138,9 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
     }
 
     // Get HTML from logger.
-    $loghtml = $logger->get_html();
+    if ($CFG->debugdisplay) {
+        $loghtml = $logger->get_html();
+    }
 
     // Hide the progress display and first backup step bar (the 'finished' step will show next).
     echo html_writer::end_div();
index 25a216c..ab97369 100644 (file)
@@ -110,7 +110,9 @@ if (!$restore->is_independent()) {
             // Do actual restore.
             $restore->execute();
             // Get HTML from logger.
-            $loghtml = $logger->get_html();
+            if ($CFG->debugdisplay) {
+                $loghtml = $logger->get_html();
+            }
             // Hide this section because we are now going to make the page show 'finished'.
             echo html_writer::end_div();
             echo html_writer::script('document.getElementById("executionprogress").style.display = "none";');
index 111c96f..012549e 100644 (file)
@@ -35,6 +35,6 @@ Feature: The activity results block displays student scores
     When I follow "Test assignment"
     And I click on "Edit settings" "link" in the "Administration" "block"
     And I set the following fields to these values:
-      | id_modgrade_type | None |
+      | id_grade_modgrade_type | None |
     And I press "Save and return to course"
     Then I should see "Error: the activity selected uses a grading method that is not supported by this block." in the "Activity results" "block"
index 1263382..b5e52f8 100644 (file)
@@ -39,8 +39,8 @@ Feature: The activity results block displays student scores as scales
       | Assignment name | Test assignment |
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
-      | id_modgrade_type | Scale |
-      | id_modgrade_scale | My Scale |
+      | id_grade_modgrade_type | Scale |
+      | id_grade_modgrade_scale | My Scale |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I turn editing mode on
index 7856da5..d546fda 100644 (file)
@@ -56,8 +56,8 @@ Feature: The activity results block displays student scores as scales
       | Assignment name | Test assignment |
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
-      | id_modgrade_type | Scale |
-      | id_modgrade_scale | My Scale |
+      | id_grade_modgrade_type | Scale |
+      | id_grade_modgrade_scale | My Scale |
       | Group mode | Separate groups |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
index 149e0f6..00f31f6 100644 (file)
@@ -39,8 +39,8 @@ Feature: The activity results block displays student scores as scales
       | Assignment name | Test assignment |
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
-      | id_modgrade_type | Scale |
-      | id_modgrade_scale | My Scale |
+      | id_grade_modgrade_type | Scale |
+      | id_grade_modgrade_scale | My Scale |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
     And I turn editing mode on
index 9134339..be5ae33 100644 (file)
@@ -56,8 +56,8 @@ Feature: The activity results block displays student scores as scales
       | Assignment name | Test assignment |
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
-      | id_modgrade_type | Scale |
-      | id_modgrade_scale | My Scale |
+      | id_grade_modgrade_type | Scale |
+      | id_grade_modgrade_scale | My Scale |
       | Group mode | Separate groups |
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
index 6edc7b5..f46bd41 100644 (file)
@@ -121,7 +121,7 @@ class block_blog_tags extends block_base {
             $type = " AND (p.publishstate = 'site' or p.publishstate='public')";
         }
 
-        $sql  = "SELECT t.id, t.tagtype, t.rawname, t.name, COUNT(DISTINCT ti.id) AS ct
+        $sql  = "SELECT t.id, t.isstandard, t.rawname, t.name, COUNT(DISTINCT ti.id) AS ct
                    FROM {tag} t, {tag_instance} ti, {post} p, {blog_association} ba
                   WHERE t.id = ti.tagid AND p.id = ti.itemid
                         $type
@@ -136,7 +136,7 @@ class block_blog_tags extends block_base {
         }
 
         $sql .= "
-               GROUP BY t.id, t.tagtype, t.name, t.rawname
+               GROUP BY t.id, t.isstandard, t.name, t.rawname
                ORDER BY ct DESC, t.name ASC";
 
         if ($tags = $DB->get_records_sql($sql, null, 0, $this->config->numberoftags)) {
@@ -165,7 +165,7 @@ class block_blog_tags extends block_base {
                     $size = 20 - ( (int)((($currenttag - 1) / $totaltags) * 20) );
                 }
 
-                $tag->class = "$tag->tagtype s$size";
+                $tag->class = ($tag->isstandard ? "standardtag " : "") . "s$size";
                 $etags[] = $tag;
 
             }
index 192ba5e..d840c38 100644 (file)
@@ -14,8 +14,8 @@ Feature: Adding blog tag block
       | fullname  | shortname |
       | Course 1  | c1        |
     And the following "tags" exist:
-      | name         | tagtype  |
-      | Neverusedtag | official |
+      | name         | isstandard  |
+      | Neverusedtag | 1           |
     And the following "course enrolments" exist:
       | user     | course | role           |
       | teacher1 | c1     | editingteacher |
index 017e407..cfc5bc1 100644 (file)
@@ -62,9 +62,16 @@ class restore_glossary_random_block_task extends restore_block_task {
             if (!empty($config->glossary)) {
                 // Get glossary mapping and replace it in config
                 if ($glossarymap = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'glossary', $config->glossary)) {
-                    $config->glossary = $glossarymap->newitemid;
+                    $mappedglossary = $DB->get_record('glossary', array('id' => $glossarymap->newitemid),
+                        'id,course,globalglossary', MUST_EXIST);
+                    $config->glossary = $mappedglossary->id;
+                    $config->courseid = $mappedglossary->course;
+                    $config->globalglossary = $mappedglossary->globalglossary;
                     $configdata = base64_encode(serialize($config));
                     $DB->set_field('block_instances', 'configdata', $configdata, array('id' => $blockid));
+                } else {
+                    // The block refers to a glossary not present in the backup file.
+                    $DB->set_field('block_instances', 'configdata', '', array('id' => $blockid));
                 }
             }
         }
diff --git a/blocks/navigation/amd/build/ajax_response_renderer.min.js b/blocks/navigation/amd/build/ajax_response_renderer.min.js
new file mode 100644 (file)
index 0000000..b7cfef8
Binary files /dev/null and b/blocks/navigation/amd/build/ajax_response_renderer.min.js differ
diff --git a/blocks/navigation/amd/build/nav_loader.min.js b/blocks/navigation/amd/build/nav_loader.min.js
new file mode 100644 (file)
index 0000000..183a19f
Binary files /dev/null and b/blocks/navigation/amd/build/nav_loader.min.js differ
diff --git a/blocks/navigation/amd/build/navblock.min.js b/blocks/navigation/amd/build/navblock.min.js
new file mode 100644 (file)
index 0000000..5a5d7b9
Binary files /dev/null and b/blocks/navigation/amd/build/navblock.min.js differ
diff --git a/blocks/navigation/amd/build/site_admin_loader.min.js b/blocks/navigation/amd/build/site_admin_loader.min.js
new file mode 100644 (file)
index 0000000..9bd76cb
Binary files /dev/null and b/blocks/navigation/amd/build/site_admin_loader.min.js differ
diff --git a/blocks/navigation/amd/src/ajax_response_renderer.js b/blocks/navigation/amd/src/ajax_response_renderer.js
new file mode 100644 (file)
index 0000000..33f508c
--- /dev/null
@@ -0,0 +1,146 @@
+// 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/>.
+
+/**
+ * Parse the response from the navblock ajax page and render the correct DOM
+ * structure for the tree from it.
+ *
+ * @module     block_navigation/ajax_response_renderer
+ * @package    core
+ * @copyright  2015 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery'], function($) {
+
+    // Mappings for the different types of nodes coming from the navigation.
+    // Copied from lib/navigationlib.php navigation_node constants.
+    var NODETYPE = {
+        // @type int Activity (course module) = 40.
+        ACTIVITY : 40,
+        // @type int Resource (course module = 50.
+        RESOURCE : 50,
+    };
+
+    /**
+     * Build DOM.
+     *
+     * @method buildDOM
+     * @param {Object} rootElement the root element of DOM.
+     * @param {object} nodes jquery object representing the nodes to be build.
+     * @return
+     */
+    function buildDOM(rootElement, nodes) {
+        var ul = $('<ul></ul>');
+        ul.attr('role', 'group');
+
+        $.each(nodes, function(index, node) {
+            if (typeof node !== 'object') {
+                return;
+            }
+
+            var li = $('<li></li>');
+            var p = $('<p></p>');
+            var icon = null;
+            var isBranch = (node.expandable || node.haschildren) ? true : false;
+
+            p.addClass('tree_item');
+            p.attr('id', node.id);
+            li.attr('role', 'treeitem');
+
+            if (node.requiresajaxloading) {
+                li.attr('data-requires-ajax', true);
+                li.attr('data-node-id', node.id);
+                li.attr('data-node-key', node.key);
+                li.attr('data-node-type', node.type);
+            }
+
+            if (isBranch) {
+                li.addClass('collapsed contains_branch');
+                li.attr('aria-expanded', false);
+                p.addClass('branch');
+            }
+
+            if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {
+                li.addClass('item_with_icon');
+                p.addClass('hasicon');
+
+                icon = $('<img/>');
+                icon.attr('alt', node.icon.alt);
+                icon.attr('title', node.icon.title);
+                icon.attr('src', M.util.image_url(node.icon.pix, node.icon.component));
+                $.each(node.icon.classes, function(index, className) {
+                    icon.addClass(className);
+                });
+            }
+
+            if (node.link) {
+                var link = $('<a title="' + node.title + '" href="' + node.link + '"></a>');
+
+                if (icon) {
+                    link.append(icon);
+                    link.append('<span class="item-content-wrap">'+node.name+'</span>');
+                } else {
+                    link.text(node.name);
+                }
+
+                if (node.hidden) {
+                    link.addClass('dimmed');
+                }
+
+                p.append(link);
+            } else {
+                var span = $('<span></span>');
+
+                if (icon) {
+                    span.append(icon);
+                    span.append('<span class="item-content-wrap">'+node.name+'</span>');
+                } else {
+                    span.text(node.name);
+                }
+
+                if (node.hidden) {
+                    span.addClass('dimmed');
+                }
+
+                p.append(span);
+            }
+
+            li.append(p);
+            ul.append(li);
+
+            if (node.children && node.children.length) {
+                buildDOM(li, node.children);
+            } else if (isBranch && !node.requiresajaxloading) {
+                li.removeClass('contains_branch');
+                li.addClass('emptybranch');
+            }
+        });
+
+        rootElement.append(ul);
+    }
+
+    return {
+        render: function(element, nodes) {
+            // The first element of the response is the existing node so we start with processing the children.
+            if (nodes.children && nodes.children.length) {
+                buildDOM(element, nodes.children);
+            } else {
+                if (element.hasClass('contains_branch')) {
+                    element.removeClass('contains_branch').addClass('emptybranch');
+                }
+            }
+        }
+    };
+});
diff --git a/blocks/navigation/amd/src/nav_loader.js b/blocks/navigation/amd/src/nav_loader.js
new file mode 100644 (file)
index 0000000..2cb5137
--- /dev/null
@@ -0,0 +1,64 @@
+// 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/>.
+
+/**
+ * Load the nav tree items via ajax and render the response.
+ *
+ * @module     block_navigation/nav_loader
+ * @package    core
+ * @copyright  2015 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'],
+    function($, ajax, config, renderer) {
+        var URL = config.wwwroot + '/lib/ajax/getnavbranch.php';
+
+        /**
+         * Get the block instance id.
+         *
+         * @function getBlockInstanceId
+         * @param element
+         * @returns {*}
+         */
+        function getBlockInstanceId(element) {
+            return element.closest('[data-block]').attr('data-instanceid');
+        }
+
+    return {
+        load: function(element) {
+            element = $(element);
+            var promise = $.Deferred();
+            var data = {
+                elementid: element.attr('data-node-id'),
+                id: element.attr('data-node-key'),
+                type: element.attr('data-node-type'),
+                sesskey: config.sesskey,
+                instance: getBlockInstanceId(element)
+            };
+            var settings = {
+                type: 'POST',
+                dataType: 'json',
+                data: data
+            };
+
+            $.ajax(URL, settings).done(function(nodes) {
+                renderer.render(element, nodes);
+                promise.resolve();
+            });
+
+            return promise;
+        }
+    };
+});
diff --git a/blocks/navigation/amd/src/navblock.js b/blocks/navigation/amd/src/navblock.js
new file mode 100644 (file)
index 0000000..14b14bb
--- /dev/null
@@ -0,0 +1,30 @@
+// 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/>.
+
+/**
+ * Load the navigation tree javascript.
+ *
+ * @module     block_navigation/navblock
+ * @package    core
+ * @copyright  2015 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/tree'], function($, Tree) {
+    return {
+        init: function() {
+            new Tree(".block_navigation .block_tree");
+        }
+    };
+});
diff --git a/blocks/navigation/amd/src/site_admin_loader.js b/blocks/navigation/amd/src/site_admin_loader.js
new file mode 100644 (file)
index 0000000..b203aac
--- /dev/null
@@ -0,0 +1,52 @@
+// 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/>.
+
+/**
+ * Load the site admin nav tree via ajax and render the response.
+ *
+ * @module     block_navigation/site_admin_loader
+ * @package    core
+ * @copyright  2015 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/ajax', 'core/config', 'block_navigation/ajax_response_renderer'],
+        function($, ajax, config, renderer) {
+
+    var SITE_ADMIN_NODE_TYPE = 71;
+    var URL = config.wwwroot + '/lib/ajax/getsiteadminbranch.php';
+
+    return {
+        load: function(element) {
+            element = $(element);
+            var promise = $.Deferred();
+            var data = {
+                type: SITE_ADMIN_NODE_TYPE,
+                sesskey: config.sesskey
+            };
+            var settings = {
+                type: 'POST',
+                dataType: 'json',
+                data: data
+            };
+
+            $.ajax(URL, settings).done(function(nodes) {
+                renderer.render(element, nodes);
+                promise.resolve();
+            });
+
+            return promise;
+        }
+    };
+});
index 17a2509..31a3fc8 100644 (file)
@@ -107,25 +107,9 @@ class block_navigation extends block_base {
      * Gets Javascript that may be required for navigation
      */
     function get_required_javascript() {
-        global $CFG;
         parent::get_required_javascript();
-        $limit = 20;
-        if (!empty($CFG->navcourselimit)) {
-            $limit = $CFG->navcourselimit;
-        }
-        $expansionlimit = 0;
-        if (!empty($this->config->expansionlimit)) {
-            $expansionlimit = $this->config->expansionlimit;
-        }
-        $arguments = array(
-            'id'             => $this->instance->id,
-            'instance'       => $this->instance->id,
-            'candock'        => $this->instance_can_be_docked(),
-            'courselimit'    => $limit,
-            'expansionlimit' => $expansionlimit
-        );
         $this->page->requires->string_for_js('viewallcourses', 'moodle');
-        $this->page->requires->yui_module('moodle-block_navigation-navigation', 'M.block_navigation.init_add_tree', array($arguments));
+        $this->page->requires->js_call_amd('block_navigation/navblock', 'init', array());
     }
 
     /**
@@ -134,6 +118,7 @@ class block_navigation extends block_base {
      * @return object $this->content
      */
     function get_content() {
+        global $CFG;
         // First check if we have already generated, don't waste cycles
         if ($this->contentgenerated === true) {
             return $this->content;
@@ -196,7 +181,21 @@ class block_navigation extends block_base {
             }
         }
 
-        $this->page->requires->data_for_js('navtreeexpansions'.$this->instance->id, $expandable);
+        $limit = 20;
+        if (!empty($CFG->navcourselimit)) {
+            $limit = $CFG->navcourselimit;
+        }
+        $expansionlimit = 0;
+        if (!empty($this->config->expansionlimit)) {
+            $expansionlimit = $this->config->expansionlimit;
+        }
+        $arguments = array(
+            'id'             => $this->instance->id,
+            'instance'       => $this->instance->id,
+            'candock'        => $this->instance_can_be_docked(),
+            'courselimit'    => $limit,
+            'expansionlimit' => $expansionlimit
+        );
 
         $options = array();
         $options['linkcategories'] = (!empty($this->config->linkcategories) && $this->config->linkcategories == 'yes');
index 2a92c9f..131c559 100644 (file)
@@ -42,7 +42,11 @@ class block_navigation_renderer extends plugin_renderer_base {
      */
     public function navigation_tree(global_navigation $navigation, $expansionlimit, array $options = array()) {
         $navigation->add_class('navigation_node');
-        $content = $this->navigation_node(array($navigation), array('class'=>'block_tree list'), $expansionlimit, $options);
+        $navigationattrs = array(
+            'class' => 'block_tree list',
+            'role' => 'tree',
+            'data-ajax-loader' => 'block_navigation/nav_loader');
+        $content = $this->navigation_node(array($navigation), $navigationattrs, $expansionlimit, $options);
         if (isset($navigation->id) && !is_numeric($navigation->id) && !empty($content)) {
             $content = $this->output->box($content, 'block_tree_box', $navigation->id);
         }
@@ -66,7 +70,9 @@ class block_navigation_renderer extends plugin_renderer_base {
 
         // Turn our navigation items into list items.
         $lis = array();
+        $number = 0;
         foreach ($items as $item) {
+            $number++;
             if (!$item->display && !$item->contains_active_node()) {
                 continue;
             }
@@ -100,7 +106,8 @@ class block_navigation_renderer extends plugin_renderer_base {
                 continue;
             }
 
-            $attributes = array();
+            $nodetextid = 'label_' . $depth . '_' . $number;
+            $attributes = array('tabindex' => '-1', 'id' => $nodetextid);
             if ($title !== '') {
                 $attributes['title'] = $title;
             }
@@ -110,7 +117,6 @@ class block_navigation_renderer extends plugin_renderer_base {
             if (is_string($item->action) || empty($item->action) ||
                     (($item->type === navigation_node::TYPE_CATEGORY || $item->type === navigation_node::TYPE_MY_CATEGORY) &&
                     empty($options['linkcategories']))) {
-                $attributes['tabindex'] = '0'; //add tab support to span but still maintain character stream sequence.
                 $content = html_writer::tag('span', $content, $attributes);
             } else if ($item->action instanceof action_link) {
                 //TODO: to be replaced with something else
@@ -129,12 +135,28 @@ class block_navigation_renderer extends plugin_renderer_base {
             $divclasses = array('tree_item');
 
             $liexpandable = array();
-            if ($item->has_children() && (!$item->forceopen || $item->collapse)) {
-                $liclasses[] = 'collapsed';
-            }
+            $lirole = array('role' => 'treeitem');
             if ($isbranch) {
                 $liclasses[] = 'contains_branch';
-                $liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
+                if ($depth == 1) {
+                    $liexpandable = array(
+                        'data-expandable' => 'false',
+                        'data-collapsible' => 'false'
+                    );
+                } else {
+                    $liexpandable = array(
+                        'aria-expanded' => ($item->has_children() &&
+                            (!$item->forceopen || $item->collapse)) ? "false" : "true");
+                }
+
+                if ($item->requiresajaxloading) {
+                    $liexpandable['data-requires-ajax'] = 'true';
+                    $liexpandable['data-loaded'] = 'false';
+                    $liexpandable['data-node-id'] = $item->id;
+                    $liexpandable['data-node-key'] = $item->key;
+                    $liexpandable['data-node-type'] = $item->type;
+                }
+
                 $divclasses[] = 'branch';
             } else {
                 $divclasses[] = 'leaf';
@@ -152,7 +174,7 @@ class block_navigation_renderer extends plugin_renderer_base {
             }
 
             // Now build attribute arrays.
-            $liattr = array('class' => join(' ', $liclasses)) + $liexpandable;
+            $liattr = array('class' => join(' ', $liclasses)) + $liexpandable + $lirole;
             $divattr = array('class'=>join(' ', $divclasses));
             if (!empty($item->id)) {
                 $divattr['id'] = $item->id;
@@ -161,11 +183,16 @@ class block_navigation_renderer extends plugin_renderer_base {
             // Create the structure.
             $content = html_writer::tag('p', $content, $divattr);
             if ($isexpandable) {
-                $content .= $this->navigation_node($item->children, array(), $expansionlimit, $options, $depth+1);
+                $content .= $this->navigation_node($item->children, array('role' => 'group'), $expansionlimit,
+                    $options, $depth + 1);
             }
             if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
                 $content = html_writer::empty_tag('hr') . $content;
             }
+            if ($depth == 1) {
+                $liattr['tabindex'] = '0';
+            }
+            $liattr['aria-labelledby'] = $nodetextid;
             $content = html_writer::tag('li', $content, $liattr);
             $lis[] = $content;
         }
index c35c764..e4e0392 100644 (file)
     background-image: url('[[pix:i/loading_small]]');
 }
 
+.block_navigation .block_tree .loading .tree_item.branch {
+    background-image: url('[[pix:i/loading_small]]');
+}
+
+.block_navigation .block_tree .emptybranch .tree_item,
+.block_navigation .block_tree [aria-expanded="false"].emptybranch .tree_item.branch {
+    padding-left: 21px;
+    background-image: url('[[pix:t/collapsed_empty]]');
+}
+
 .block_navigation .block_tree .tree_item img {
     width: 16px;
     height: 16px;
     list-style: none;
 }
 
-.jsenabled .block_navigation .block_tree li.collapsed ul {
+.jsenabled .block_navigation .block_tree [aria-expanded="false"] ul {
     display: none;
 }
 
-.jsenabled .block_navigation .block_tree li.collapsed .tree_item.branch {
+.jsenabled .block_navigation .block_tree [aria-expanded="false"] .tree_item.branch {
     background-image: url('[[pix:t/collapsed]]');
 }
 
+.jsenabled .block_navigation .block_tree [aria-expanded="false"].loading .tree_item.branch {
+    background-image: url('[[pix:i/loading_small]]');
+}
+
+.jsenabled .block_navigation .block_tree [aria-expanded="false"].emptybranch .tree_item.branch {
+    padding-left: 21px;
+    background-image: url('[[pix:t/collapsed_empty]]');
+}
+
 .jsenabled .block_navigation.dock_on_load {
     display: none;
 }
     padding-left: 0;
 }
 
-.dir-rtl .block_navigation .block_tree .tree_item.emptybranch {
+.dir-rtl .block_navigation .block_tree .tree_item.emptybranch,
+.dir-rtl .block_navigation .block_tree .emptybranch .tree_item {
     padding-right: 21px;
     padding-left: 0;
     background-image: url('[[pix:t/collapsed_empty_rtl]]');
     margin: 0 16px 0 0;
 }
 
-.dir-rtl.jsenabled .block_navigation .block_tree .collapsed .tree_item.branch {
+.dir-rtl.jsenabled .block_navigation .block_tree [aria-expanded="false"] .tree_item.branch {
     background-image: url('[[pix:t/collapsed_rtl]]');
 }
+
+.dir-rtl.jsenabled .block_navigation .block_tree [aria-expanded="false"].emptybranch .tree_item.branch {
+    background-image: url('[[pix:t/collapsed_empty_rtl]]');
+}
diff --git a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js
deleted file mode 100644 (file)
index 2d58702..0000000
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-debug.js and /dev/null differ
diff --git a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js
deleted file mode 100644 (file)
index 7c67d98..0000000
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation-min.js and /dev/null differ
diff --git a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js b/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js
deleted file mode 100644 (file)
index b661bfd..0000000
Binary files a/blocks/navigation/yui/build/moodle-block_navigation-navigation/moodle-block_navigation-navigation.js and /dev/null differ
diff --git a/blocks/navigation/yui/src/navigation/build.json b/blocks/navigation/yui/src/navigation/build.json
deleted file mode 100644 (file)
index 5e28a6c..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-  "name": "moodle-block_navigation-navigation",
-  "builds": {
-    "moodle-block_navigation-navigation": {
-      "jsfiles": [
-        "navigation.js"
-      ]
-    }
-  }
-}
diff --git a/blocks/navigation/yui/src/navigation/js/navigation.js b/blocks/navigation/yui/src/navigation/js/navigation.js
deleted file mode 100644 (file)
index 3678bff..0000000
+++ /dev/null
@@ -1,897 +0,0 @@
-/**
- * Navigation block JS.
- *
- * This file contains the Navigation block JS..
- *
- * @module moodle-block_navigation-navigation
- */
-
-/**
- * This namespace will contain all of the contents of the navigation blocks
- * global navigation and settings.
- * @class M.block_navigation
- * @static
- */
-M.block_navigation = M.block_navigation || {};
-/**
- * The number of expandable branches in existence.
- *
- * @property expandablebranchcount
- * @protected
- * @static
- * @type Number
- */
-M.block_navigation.expandablebranchcount = 1;
-/**
- * The maximum number of courses to show as part of a branch.
- *
- * @property courselimit
- * @protected
- * @static
- * @type Number
- */
-M.block_navigation.courselimit = 20;
-/**
- * Add new instance of navigation tree to tree collection
- *
- * @method init_add_tree
- * @static
- * @param {Object} properties
- */
-M.block_navigation.init_add_tree = function(properties) {
-    if (properties.courselimit) {
-        this.courselimit = properties.courselimit;
-    }
-    new TREE(properties);
-};
-
-/**
- * A 'actionkey' Event to help with Y.delegate().
- * The event consists of the left arrow, right arrow, enter and space keys.
- * More keys can be mapped to action meanings.
- * actions: collapse , expand, toggle, enter.
- *
- * This event is delegated to branches in the navigation tree.
- * The on() method to subscribe allows specifying the desired trigger actions as JSON.
- *
- * @namespace M.block_navigation
- * @class ActionKey
- */
-Y.Event.define("actionkey", {
-    // Webkit and IE repeat keydown when you hold down arrow keys.
-    // Opera links keypress to page scroll; others keydown.
-    // Firefox prevents page scroll via preventDefault() on either
-    // keydown or keypress.
-    _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
-
-    /**
-     * The keys to trigger on.
-     * @method _keys
-     */
-    _keys: {
-        //arrows
-        '37': 'collapse',
-        '39': 'expand',
-        '32': 'toggle',
-        '13': 'enter'
-    },
-
-    /**
-     * Handles key events
-     * @method _keyHandler
-     * @param {EventFacade} e
-     * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
-     * @param {Object} args
-     */
-    _keyHandler: function (e, notifier, args) {
-        var actObj;
-        if (!args.actions) {
-            actObj = {collapse:true, expand:true, toggle:true, enter:true};
-        } else {
-            actObj = args.actions;
-        }
-        if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
-            e.action = this._keys[e.keyCode];
-            notifier.fire(e);
-        }
-    },
-
-    /**
-     * Subscribes to events.
-     * @method on
-     * @param {Node} node The node this subscription was applied to.
-     * @param {Subscription} sub The object tracking this subscription.
-     * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
-     */
-    on: function (node, sub, notifier) {
-        // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
-        if (sub.args === null) {
-            //no actions given
-            sub._detacher = node.on(this._event, this._keyHandler,this, notifier, {actions:false});
-        } else {
-            sub._detacher = node.on(this._event, this._keyHandler,this, notifier, sub.args[0]);
-        }
-    },
-
-    /**
-     * Detaches an event listener
-     * @method detach
-     */
-    detach: function (node, sub) {
-        //detach our _detacher handle of the subscription made in on()
-        sub._detacher.detach();
-    },
-
-    /**
-     * Creates a delegated event listener.
-     * @method delegate
-     * @param {Node} node The node this subscription was applied to.
-     * @param {Subscription} sub The object tracking this subscription.
-     * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
-     * @param {String|function} filter Selector string or function that accpets an event object and returns null.
-     */
-    delegate: function (node, sub, notifier, filter) {
-        // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
-        if (sub.args === null) {
-            //no actions given
-            sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, {actions:false});
-        } else {
-            sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, sub.args[0]);
-        }
-    },
-
-    /**
-     * Detaches a delegated event listener.
-     * @method detachDelegate
-     * @param {Node} node The node this subscription was applied to.
-     * @param {Subscription} sub The object tracking this subscription.
-     * @param {SyntheticEvent.Notifier} notifier The notifier used to trigger the execution of subscribers
-     * @param {String|function} filter Selector string or function that accpets an event object and returns null.
-     */
-    detachDelegate: function (node, sub) {
-        sub._delegateDetacher.detach();
-    }
-});
-
-var EXPANSIONLIMIT_EVERYTHING = 0,
-    EXPANSIONLIMIT_COURSE     = 20,
-    EXPANSIONLIMIT_SECTION    = 30,
-    EXPANSIONLIMIT_ACTIVITY   = 40;
-
-// Mappings for the different types of nodes coming from the navigation.
-// Copied from lib/navigationlib.php navigation_node constants.
-var NODETYPE = {
-    // @type int Root node = 0
-    ROOTNODE : 0,
-    // @type int System context = 1
-    SYSTEM : 1,
-    // @type int Course category = 10
-    CATEGORY : 10,
-    // @type int MYCATEGORY = 11
-    MYCATEGORY : 11,
-    // @type int Course = 20
-    COURSE : 20,
-    // @type int Course section = 30
-    SECTION : 30,
-    // @type int Activity (course module) = 40
-    ACTIVITY : 40,
-    // @type int Resource (course module = 50
-    RESOURCE : 50,
-    // @type int Custom node (could be anything) = 60
-    CUSTOM : 60,
-    // @type int Setting = 70
-    SETTING : 70,
-    // @type int site administration = 71
-    SITEADMIN : 71,
-    // @type int User context = 80
-    USER : 80,
-    // @type int Container = 90
-    CONTAINER : 90
-};
-
-/**
- * Navigation tree class.
- *
- * This class establishes the tree initially, creating expandable branches as
- * required, and delegating the expand/collapse event.
- *
- * @namespace M.block_navigation
- * @class Tree
- * @constructor
- * @extends Base
- */
-var TREE = function() {
-    TREE.superclass.constructor.apply(this, arguments);
-};
-TREE.prototype = {
-    /**
-     * The tree's ID, normally its block instance id.
-     * @property id
-     * @type Number
-     * @protected
-     */
-    id : null,
-    /**
-     * An array of initialised branches.
-     * @property branches
-     * @type Array
-     * @protected
-     */
-    branches : [],
-    /**
-     * Initialise the tree object when its first created.
-     * @method initializer
-     * @param {Object} config
-     */
-    initializer : function(config) {
-        Y.log('Initialising navigation block tree', 'note', 'moodle-block_navigation');
-
-        this.id = parseInt(config.id, 10);
-
-        var node = Y.one('#inst'+config.id);
-
-        // Can't find the block instance within the page
-        if (node === null) {
-            return;
-        }
-
-        // Delegate event to toggle expansion
-        Y.delegate('click', this.toggleExpansion, node.one('.block_tree'), '.tree_item.branch', this);
-        Y.delegate('actionkey', this.toggleExpansion, node.one('.block_tree'), '.tree_item.branch', this);
-
-        // Gather the expandable branches ready for initialisation.
-        var expansions = [];
-        if (config.expansions) {
-            expansions = config.expansions;
-        } else if (window['navtreeexpansions'+config.id]) {
-            expansions = window['navtreeexpansions'+config.id];
-        }
-        // Establish each expandable branch as a tree branch.
-        for (var i in expansions) {
-            var branch = new BRANCH({
-                tree:this,
-                branchobj:expansions[i],
-                overrides : {
-                    expandable : true,
-                    children : [],
-                    haschildren : true
-                }
-            }).wire();
-            M.block_navigation.expandablebranchcount++;
-            this.branches[branch.get('id')] = branch;
-        }
-        // Create siteadmin branch.
-        if (window.siteadminexpansion) {
-            var siteadminbranch = new BRANCH({
-                tree: this,
-                branchobj: window.siteadminexpansion,
-                overrides : {
-                    expandable : true,
-                    children : [],
-                    haschildren : true
-                }
-            }).wire();
-            M.block_navigation.expandablebranchcount++;
-            this.branches[siteadminbranch.get('id')] = siteadminbranch;
-            // Remove link on site admin with JS to keep old UI.
-            if (siteadminbranch.node) {
-                var siteadminlinknode = siteadminbranch.node.get('childNodes').item(0);
-                if (siteadminlinknode) {
-                    var siteadminnode = Y.Node.create('<span tabindex="0">'+siteadminlinknode.get('innerHTML')+'</span>');
-                    siteadminbranch.node.replaceChild(siteadminnode, siteadminlinknode);
-                }
-            }
-        }
-        if (M.block_navigation.expandablebranchcount > 0) {
-            // Delegate some events to handle AJAX loading.
-            Y.delegate('click', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
-            Y.delegate('actionkey', this.fire_branch_action, node.one('.block_tree'), '.tree_item.branch[data-expandable]', this);
-        }
-    },
-    /**
-     * Fire actions for a branch when an event occurs.
-     * @method fire_branch_action
-     * @param {EventFacade} event
-     */
-    fire_branch_action : function(event) {
-        var id = event.currentTarget.getAttribute('id');
-        var branch = this.branches[id];
-        branch.ajaxLoad(event);
-    },
-    /**
-     * This is a callback function responsible for expanding and collapsing the
-     * branches of the tree. It is delegated to rather than multiple event handles.
-     * @method toggleExpansion
-     * @param {EventFacade} e
-     * @return Boolean
-     */
-    toggleExpansion : function(e) {
-        // First check if they managed to click on the li iteslf, then find the closest
-        // LI ancestor and use that
-
-        if (e.target.test('a') && (e.keyCode === 0 || e.keyCode === 13)) {
-            // A link has been clicked (or keypress is 'enter') don't fire any more events just do the default.
-            e.stopPropagation();
-            return;
-        }
-
-        // Makes sure we can get to the LI containing the branch.
-        var target = e.target;
-        if (!target.test('li')) {
-            target = target.ancestor('li');
-        }
-        if (!target) {
-            return;
-        }
-
-        // Toggle expand/collapse providing its not a root level branch.
-        if (!target.hasClass('depth_1')) {
-            if (e.type === 'actionkey') {
-                switch (e.action) {
-                    case 'expand' :
-                        target.removeClass('collapsed');
-                        target.set('aria-expanded', true);
-                        break;
-                    case 'collapse' :
-                        target.addClass('collapsed');
-                        target.set('aria-expanded', false);
-                        break;
-                    default :
-                        target.toggleClass('collapsed');
-                        target.set('aria-expanded', !target.hasClass('collapsed'));
-                }
-                e.halt();
-            } else {
-                target.toggleClass('collapsed');
-                target.set('aria-expanded', !target.hasClass('collapsed'));
-            }
-        }
-
-        // If the accordian feature has been enabled collapse all siblings.
-        if (this.get('accordian')) {
-            target.siblings('li').each(function(){
-                if (this.get('id') !== target.get('id') && !this.hasClass('collapsed')) {
-                    this.addClass('collapsed');
-                    this.set('aria-expanded', false);
-                }
-            });
-        }
-
-        // If this block can dock tell the dock to resize if required and check
-        // the width on the dock panel in case it is presently in use.
-        if (this.get('candock') && M.core.dock.notifyBlockChange) {
-            M.core.dock.notifyBlockChange(this.id);
-        }
-        return true;
-
-    }
-};
-// The tree extends the YUI base foundation.
-Y.extend(TREE, Y.Base, TREE.prototype, {
-    NAME : 'navigation-tree',
-    ATTRS : {
-        /**
-         * True if the block can dock.
-         * @attribute candock
-         * @type Boolean
-         */
-        candock : {
-            validator : Y.Lang.isBool,
-            value : false
-        },
-        /**
-         * If set to true nodes will be opened/closed in an accordian fashion.
-         * @attribute accordian
-         * @type Boolean
-         */
-        accordian : {
-            validator : Y.Lang.isBool,
-            value : false
-        },
-        /**
-         * The nodes that get shown.
-         * @attribute expansionlimit
-         * @type Number
-         */
-        expansionlimit : {
-            value : 0,
-            setter : function(val) {
-                val = parseInt(val, 10);
-                if (val !== EXPANSIONLIMIT_EVERYTHING &&
-                    val !== EXPANSIONLIMIT_COURSE &&
-                    val !== EXPANSIONLIMIT_SECTION &&
-                    val !== EXPANSIONLIMIT_ACTIVITY) {
-                    val = EXPANSIONLIMIT_EVERYTHING;
-                }
-                return val;
-            }
-        },
-        /**
-         * The navigation tree block instance.
-         *
-         * @attribute instance
-         * @default false
-         * @type Number
-         */
-        instance : {
-            value : false,
-            setter : function(val) {
-                return parseInt(val, 10);
-            }
-        }
-    }
-});
-
-/**
- * The Branch class.
- *
- * This class is used to manage a tree branch, in particular its ability to load
- * its contents by AJAX.
- *
- * @namespace M.block_navigation
- * @class Branch
- * @constructor
- * @extends Base
- */
-var BRANCH = function() {
-    BRANCH.superclass.constructor.apply(this, arguments);
-};
-BRANCH.prototype = {
-    /**
-     * The node for this branch (p)
-     * @property node
-     * @type Node
-     * @protected
-     */
-    node : null,
-    /**
-     * Initialises the branch when it is first created.
-     * @method initializer
-     * @param {Object} config
-     */
-    initializer : function(config) {
-        var i,
-            children;
-        if (config.branchobj !== null) {
-            // Construct from the provided xml
-            for (i in config.branchobj) {
-                this.set(i, config.branchobj[i]);
-            }
-            children = this.get('children');
-            this.set('haschildren', (children.length > 0));
-        }
-        if (config.overrides !== null) {
-            // Construct from the provided xml
-            for (i in config.overrides) {
-                this.set(i, config.overrides[i]);
-            }
-        }
-        // Get the node for this branch
-        this.node = Y.one('#'+this.get('id'));
-        var expansionlimit = this.get('tree').get('expansionlimit');
-        var type = this.get('type');
-        if (expansionlimit !== EXPANSIONLIMIT_EVERYTHING &&  type >= expansionlimit && type <= EXPANSIONLIMIT_ACTIVITY) {
-            this.set('expandable', false);
-            this.set('haschildren', false);
-        }
-    },
-    /**
-     * Draws the branch within the tree.
-     *
-     * This function creates a DOM structure for the branch and then injects
-     * it into the navigation tree at the correct point.
-     *
-     * It is important that this is kept in check with block_navigation_renderer::navigation_node as that produces
-     * the same thing as this but on the php side.
-     *
-     * @method draw
-     * @chainable
-     * @param {Node} element
-     * @return Branch
-     */
-    draw : function(element) {
-
-        var isbranch = (this.get('expandable') || this.get('haschildren'));
-        var branchli = Y.Node.create('<li></li>');
-        var link = this.get('link');
-        var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
-        var name;
-        if (!link) {
-            //add tab focus if not link (so still one focus per menu node).
-            // it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
-            branchp.setAttribute('tabindex', '0');
-        }
-        if (isbranch) {
-            branchli.addClass('collapsed').addClass('contains_branch');
-            branchli.set('aria-expanded', false);
-            branchp.addClass('branch');
-        }
-
-        // Prepare the icon, should be an object representing a pix_icon
-        var branchicon = false;
-        var icon = this.get('icon');
-        if (icon && (!isbranch || this.get('type') === NODETYPE.ACTIVITY || this.get('type') === NODETYPE.RESOURCE)) {
-            branchicon = Y.Node.create('<img alt="" />');
-            branchicon.setAttribute('src', M.util.image_url(icon.pix, icon.component));
-            branchli.addClass('item_with_icon');
-            branchp.addClass('hasicon');
-            if (icon.alt) {
-                branchicon.setAttribute('alt', icon.alt);
-            }
-            if (icon.title) {
-                branchicon.setAttribute('title', icon.title);
-            }
-            if (icon.classes) {
-                for (var i in icon.classes) {
-                    branchicon.addClass(icon.classes[i]);
-                }
-            }
-        }
-
-        if (!link) {
-            var branchspan = Y.Node.create('<span></span>');
-            if (branchicon) {
-                branchspan.appendChild(branchicon);
-                name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
-            } else {
-                name = this.get('name');
-            }
-            branchspan.append(name);
-            if (this.get('hidden')) {
-                branchspan.addClass('dimmed_text');
-            }
-            branchp.appendChild(branchspan);
-        } else {
-            var branchlink = Y.Node.create('<a title="'+this.get('title')+'" href="'+link+'"></a>');
-            if (branchicon) {
-                branchlink.appendChild(branchicon);
-                name = '<span class="item-content-wrap">' + this.get('name') + '</span>';
-            } else {
-                name = this.get('name');
-            }
-            branchlink.append(name);
-            if (this.get('hidden')) {
-                branchlink.addClass('dimmed');
-            }
-            branchp.appendChild(branchlink);
-        }
-
-        branchli.appendChild(branchp);
-        element.appendChild(branchli);
-        this.node = branchp;
-        return this;
-    },
-    /**
-     * Attaches required events to the branch structure.
-     *
-     * @chainable
-     * @method wire
-     * @return {BRANCH} This function is chainable, it always returns itself.
-     */
-    wire : function() {
-        this.node = this.node || Y.one('#'+this.get('id'));
-        if (!this.node) {
-            return this;
-        }
-        if (this.get('expandable')) {
-            this.node.setAttribute('data-expandable', '1');
-            this.node.setAttribute('data-loaded', '0');
-        }
-        return this;
-    },
-    /**
-     * Gets the UL element that children for this branch should be inserted into.
-     * @method getChildrenUL
-     * @return Node
-     */
-    getChildrenUL : function() {
-        var ul = this.node.next('ul');
-        if (!ul) {
-            ul = Y.Node.create('<ul></ul>');
-            this.node.ancestor().append(ul);
-        }
-        return ul;
-    },
-    /**
-     * Load the content of the branch via AJAX.
-     *
-     * This function calls ajaxProcessResponse with the result of the AJAX
-     * request made here.
-     *
-     * @method ajaxLoad
-     * @param {EventFacade} e
-     * @return Bool
-     */
-    ajaxLoad : function(e) {
-        if (e.type === 'actionkey' && e.action !== 'enter') {
-            e.halt();
-        } else {
-            e.stopPropagation();
-        }
-        if ((e.type === 'actionkey' && e.action === 'enter') || e.target.test('a')) {
-            // No ajaxLoad for enter.
-            this.node.setAttribute('data-expandable', '0');
-            this.node.setAttribute('data-loaded', '1');
-            return true;
-        }
-
-        if (this.node.hasClass('loadingbranch')) {
-            // Already loading. Just skip.
-            return true;
-        }
-
-        if (this.node.getAttribute('data-loaded') === '1') {
-            // We've already loaded this stuff.
-            return true;
-        }
-        Y.log('Loading navigation branch via AJAX: '+this.get('key'), 'note', 'moodle-block_navigation');
-        this.node.addClass('loadingbranch');
-
-        var params = {
-            elementid : this.get('id'),
-            id : this.get('key'),
-            type : this.get('type'),
-            sesskey : M.cfg.sesskey,
-            instance : this.get('tree').get('instance')
-        };
-
-        var ajaxfile = '/lib/ajax/getnavbranch.php';
-        // For siteadmin navigation get tree from getsiteadminbranch.php.
-        if (this.get('type') === NODETYPE.SITEADMIN) {
-            ajaxfile = '/lib/ajax/getsiteadminbranch.php';
-        }
-
-        Y.io(M.cfg.wwwroot + ajaxfile, {
-            method:'POST',
-            data:  params,
-            on: {
-                complete: this.ajaxProcessResponse
-            },
-            context:this
-        });
-        return true;
-    },
-    /**
-     * Processes an AJAX request to load the content of this branch through
-     * AJAX.
-     *
-     * @method ajaxProcessResponse
-     * @param {Int} tid The transaction id.
-     * @param {Object} outcome
-     * @return Boolean
-     */
-    ajaxProcessResponse : function(tid, outcome) {
-        this.node.removeClass('loadingbranch');
-        this.node.setAttribute('data-loaded', '1');
-        try {
-            var object = Y.JSON.parse(outcome.responseText);
-            if (object.error) {
-                Y.use('moodle-core-notification-ajaxexception', function () {
-                    return new M.core.ajaxException(object).show();
-                });
-                return false;
-            }
-            if (object.children && object.children.length > 0) {
-                var coursecount = 0;
-                for (var i in object.children) {
-                    if (typeof(object.children[i])==='object') {
-                        if (object.children[i].type === NODETYPE.COURSE) {
-                            coursecount++;
-                        }
-                        this.addChild(object.children[i]);
-                    }
-                }
-                if ((this.get('type') === NODETYPE.CATEGORY ||
-                     this.get('type') === NODETYPE.ROOTNODE ||
-                     this.get('type') === NODETYPE.MYCATEGORY)
-                     && coursecount >= M.block_navigation.courselimit) {
-                    this.addViewAllCoursesChild(this);
-                }
-                Y.log('AJAX loading complete.', 'note', 'moodle-block_navigation');
-                // If this block can dock tell the dock to resize if required and check
-                // the width on the dock panel in case it is presently in use.
-                if (this.get('tree').get('candock') && M.core.dock.notifyBlockChange) {
-                    M.core.dock.notifyBlockChange(this.get('tree').id);
-                }
-                return true;
-            }
-            Y.log('AJAX loading complete but there were no children.', 'note', 'moodle-block_navigation');
-        } catch (error) {
-            if (outcome && outcome.status && outcome.status > 0) {
-                // If we got here then there was an error parsing the result.
-                Y.log('Error parsing AJAX response or adding branches to the navigation tree', 'error', 'moodle-block_navigation');
-                Y.use('moodle-core-notification-exception', function () {
-                    return new M.core.exception(error).show();
-                });
-            }
-
-            return false;
-        }
-        // The branch is empty so class it accordingly
-        this.node.replaceClass('branch', 'emptybranch');
-        return true;
-    },
-    /**
-     * Turns the branch object passed to the method into a proper branch object
-     * and then adds it as a child of this branch.
-     *
-     * @method addChild
-     * @param {Object} branchobj
-     * @return Boolean
-     */
-    addChild : function(branchobj) {
-        // Make the new branch into an object
-        var branch = new BRANCH({tree:this.get('tree'), branchobj:branchobj});
-        if (branch.draw(this.getChildrenUL())) {
-            this.get('tree').branches[branch.get('id')] = branch;
-            branch.wire();
-            var count = 0, i, children = branch.get('children');
-            for (i in children) {
-                // Add each branch to the tree
-                if (children[i].type === NODETYPE.COURSE) {
-                    count++;
-                }
-                if (typeof(children[i]) === 'object') {
-                    branch.addChild(children[i]);
-                }
-            }
-            if ((branch.get('type') === NODETYPE.CATEGORY || branch.get('type') === NODETYPE.MYCATEGORY)
-                && count >= M.block_navigation.courselimit) {
-                this.addViewAllCoursesChild(branch);
-            }
-        }
-        return true;
-    },
-
-    /**
-     * Add a link to view all courses in a category
-     *
-     * @method addViewAllCoursesChild
-     * @param {BRANCH} branch
-     */
-    addViewAllCoursesChild: function(branch) {
-        var url = null;
-        if (branch.get('type') === NODETYPE.ROOTNODE) {
-            if (branch.get('key') === 'mycourses') {
-                url = M.cfg.wwwroot + '/my';
-            } else {
-                url = M.cfg.wwwroot + '/course/index.php';
-            }
-        } else {
-            url = M.cfg.wwwroot+'/course/index.php?categoryid=' + branch.get('key');
-        }
-        branch.addChild({
-            name : M.util.get_string('viewallcourses', 'moodle'),
-            title : M.util.get_string('viewallcourses', 'moodle'),
-            link : url,
-            haschildren : false,
-            icon : {'pix':"i/navigationitem",'component':'moodle'}
-        });
-    }
-};
-Y.extend(BRANCH, Y.Base, BRANCH.prototype, {
-    NAME : 'navigation-branch',
-    ATTRS : {
-        /**
-         * The Tree this branch belongs to.
-         * @attribute tree
-         * @type TREE
-         * @required
-         * @writeOnce
-         */
-        tree : {
-            writeOnce : 'initOnly',
-            validator : Y.Lang.isObject
-        },
-        /**
-         * The name of this branch.
-         * @attribute name
-         * @type String
-         */
-        name : {
-            value : '',
-            validator : Y.Lang.isString,
-            setter : function(val) {
-                return val.replace(/\n/g, '<br />');
-            }
-        },
-        /**
-         * The title to use for this branch.
-         * @attribute title
-         * @type String
-         */
-        title : {
-            value : '',
-            validator : Y.Lang.isString
-        },
-        /**
-         * The ID of this branch.
-         * The ID and Type should always form a unique pair.
-         * @attribute id
-         * @type String
-         */
-        id : {
-            value : '',
-            validator : Y.Lang.isString,
-            getter : function(val) {
-                if (val === '') {
-                    val = 'expandable_branch_'+M.block_navigation.expandablebranchcount;
-                    M.block_navigation.expandablebranchcount++;
-                }
-                return val;
-            }
-        },
-        /**
-         * The key used to identify this branch easily if there is one.
-         * @attribute key
-         * @type String
-         */
-        key : {
-            value : null
-        },
-        /**
-         * The type of this branch.
-         * @attribute type
-         * @type Number
-         */
-        type : {
-            value : null,
-            setter : function(value) {
-                return parseInt(value, 10);
-            }
-        },
-        /**
-         * The link to use for this branch.
-         * @attribute link
-         * @type String
-         */
-        link : {
-            value : false
-        },
-        /**
-         * The Icon to add when displaying this branch.
-         * @attribute icon
-         * @type Object
-         */
-        icon : {
-            value : false,
-            validator : Y.Lang.isObject
-        },
-        /**
-         * True if this branch is expandable.
-         * @attribute expandable
-         * @type Boolean
-         */
-        expandable : {
-            value : false,
-            validator : Y.Lang.isBool
-        },
-        /**
-         * True if this branch is hidden and should be displayed greyed out.
-         * @attribute hidden
-         * @type Boolean
-         */
-        hidden : {
-            value : false,
-            validator : Y.Lang.isBool
-        },
-        /**
-         * True if this branch has any children.
-         * @attribute haschildren
-         * @type Boolean
-         */
-        haschildren : {
-            value : false,
-            validator : Y.Lang.isBool
-        },
-        /**
-         * An array of other branches that appear as children of this branch.
-         * @attribute children
-         * @type Array
-         */
-        children : {
-            value : [],
-            validator : Y.Lang.isArray
-        }
-    }
-});
diff --git a/blocks/navigation/yui/src/navigation/meta/navigation.json b/blocks/navigation/yui/src/navigation/meta/navigation.json
deleted file mode 100644 (file)
index c544b51..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "moodle-block_navigation-navigation": {
-    "requires": [
-        "base",
-        "io-base",
-        "node",
-        "event-synthetic",
-        "event-delegate",
-        "json-parse"
-    ]
-  }
-}
diff --git a/blocks/settings/amd/build/settingsblock.min.js b/blocks/settings/amd/build/settingsblock.min.js
new file mode 100644 (file)
index 0000000..91aa6a5
Binary files /dev/null and b/blocks/settings/amd/build/settingsblock.min.js differ
diff --git a/blocks/settings/amd/src/settingsblock.js b/blocks/settings/amd/src/settingsblock.js
new file mode 100644 (file)
index 0000000..965aec5
--- /dev/null
@@ -0,0 +1,35 @@
+// 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/>.
+
+/**
+ * Load the settings block tree javscript
+ *
+ * @module     block_navigation/navblock
+ * @package    core
+ * @copyright  2015 John Okely <john@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/tree'], function($, Tree) {
+    return {
+        init: function(siteAdminNodeId) {
+            var adminTree = new Tree(".block_settings .block_tree");
+            if (siteAdminNodeId) {
+                var siteAdminNode = adminTree.treeRoot.find('#' + siteAdminNodeId);
+                var siteAdminLink = siteAdminNode.children('a').first();
+                siteAdminLink.replaceWith('<span tabindex="0">' + siteAdminLink.html() + '</span>');
+            }
+        }
+    };
+});
index 14c486b..beaa87f 100644 (file)
@@ -90,13 +90,19 @@ class block_settings extends block_base {
     }
 
     function get_required_javascript() {
+        global $PAGE;
+        $adminnodeid = null;
+        $adminnode = $PAGE->settingsnav->find('siteadministration', navigation_node::TYPE_SITE_ADMIN);
+        if (!empty($adminnode)) {
+            $adminnodeid = $adminnode->id;
+        }
         parent::get_required_javascript();
         $arguments = array(
             'id' => $this->instance->id,
             'instance' => $this->instance->id,
             'candock' => $this->instance_can_be_docked()
         );
-        $this->page->requires->yui_module('moodle-block_navigation-navigation', 'M.block_navigation.init_add_tree', array($arguments));
+        $this->page->requires->js_call_amd('block_settings/settingsblock', 'init', array($adminnodeid));
     }
 
     /**
index 4de8b83..d5572e8 100644 (file)
@@ -32,14 +32,26 @@ class block_settings_renderer extends plugin_renderer_base {
                 $count++;
             }
         }
-        $content = $this->navigation_node($navigation, array('class'=>'block_tree list'));
+        $navigationattrs = array(
+            'class' => 'block_tree list',
+            'role' => 'tree',
+            'data-ajax-loader' => 'block_navigation/site_admin_loader');
+        $content = $this->navigation_node($navigation, $navigationattrs);
         if (isset($navigation->id) && !is_numeric($navigation->id) && !empty($content)) {
             $content = $this->output->box($content, 'block_tree_box', $navigation->id);
         }
         return $content;
     }
 
-    protected function navigation_node(navigation_node $node, $attrs=array()) {
+    /**
+     * Build the navigation node.
+     *
+     * @param navigation_node $node the navigation node object.
+     * @param array $attrs list of attributes.
+     * @param int $depth the depth, default to 1.
+     * @return string the navigation node code.
+     */
+    protected function navigation_node(navigation_node $node, $attrs=array(), $depth = 1) {
         $items = $node->children;
 
         // exit if empty, we don't want an empty ul element
@@ -49,7 +61,9 @@ class block_settings_renderer extends plugin_renderer_base {
 
         // array of nested li elements
         $lis = array();
+        $number = 0;
         foreach ($items as $item) {
+            $number++;
             if (!$item->display) {
                 continue;
             }
@@ -65,19 +79,27 @@ class block_settings_renderer extends plugin_renderer_base {
             // this applies to the li item which contains all child lists too
             $liclasses = array($item->get_css_type());
             $liexpandable = array();
-            if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count()==0  && $item->nodetype==navigation_node::NODETYPE_BRANCH)) {
-                $liclasses[] = 'collapsed';
-            }
             if ($isbranch) {
                 $liclasses[] = 'contains_branch';
-                $liexpandable = array('aria-expanded' => in_array('collapsed', $liclasses) ? "false" : "true");
+                if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count() == 0
+                        && $item->nodetype == navigation_node::NODETYPE_BRANCH)) {
+                    $liexpandable = array('aria-expanded' => 'false');
+                } else {
+                    $liexpandable = array('aria-expanded' => 'true');
+                }
+                if ($item->requiresajaxloading) {
+                    $liexpandable['data-requires-ajax'] = 'true';
+                    $liexpandable['data-loaded'] = 'false';
+                }
+
             } else if ($hasicon) {
                 $liclasses[] = 'item_with_icon';
             }
             if ($item->isactive === true) {
                 $liclasses[] = 'current_branch';
             }
-            $liattr = array('class' => join(' ',$liclasses)) + $liexpandable;
+            $nodetextid = 'label_' . $depth . '_' . $number;
+            $liattr = array('class' => join(' ', $liclasses), 'tabindex' => '-1', 'role' => 'treeitem') + $liexpandable;
             // class attribute on the div item which only contains the item content
             $divclasses = array('tree_item');
             if ($isbranch) {
@@ -92,15 +114,19 @@ class block_settings_renderer extends plugin_renderer_base {
             if (!empty($item->id)) {
                 $divattr['id'] = $item->id;
             }
-            $content = html_writer::tag('p', $content, $divattr) . $this->navigation_node($item);
+            $content = html_writer::tag('p', $content, $divattr) . $this->navigation_node($item, array(), $depth + 1);
             if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) {
                 $content = html_writer::empty_tag('hr') . $content;
             }
+            $liattr['aria-labelledby'] = $nodetextid;
             $content = html_writer::tag('li', $content, $liattr);
             $lis[] = $content;
         }
 
         if (count($lis)) {
+            if (empty($attrs['role'])) {
+                $attrs['role'] = 'group';
+            }
             return html_writer::tag('ul', implode("\n", $lis), $attrs);
         } else {
             return '';
index bc057b5..6cb127c 100644 (file)
 .block_settings .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
 .block_settings .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
+.block_settings .block_tree .loading .tree_item.branch {background-image: url('[[pix:i/loading_small]]');}
 .block_settings .block_tree .active_tree_node {font-weight:bold;}
 .jsenabled .block_settings .block_tree .tree_item.branch {cursor:pointer;}
-.jsenabled .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 10%;background-repeat: no-repeat;}
-.jsenabled .block_settings .block_tree .collapsed ul {display: none;}
-.jsenabled .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
+.jsenabled .block_settings .block_tree .emptybranch .tree_item,
+.jsenabled .block_settings .block_tree [aria-expanded="false"].emptybranch .tree_item.branch {
+    background-image: url([[pix:t/collapsed_empty]]);background-position: 0 10%;background-repeat: no-repeat;
+}
+.jsenabled .block_settings .block_tree [aria-expanded="false"] ul {display: none;}
+.jsenabled .block_settings .block_tree [aria-expanded="false"] .tree_item.branch {background-image: url([[pix:t/collapsed]]);}
+.jsenabled .block_settings .block_tree [aria-expanded="false"].loading .tree_item.branch {background-image: url('[[pix:i/loading_small]]');}
 
 /** Internet explorer specific rules **/
 .ie6 .block_settings .block_tree .tree_item {width:100%;}
@@ -31,5 +36,8 @@
 .dir-rtl .block_settings .block_tree li.item_with_icon > p img { right: 0; left: auto;}
 .jsenabled .block_settings .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
 
-.jsenabled.dir-rtl .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;}
-.jsenabled.dir-rtl .block_settings .block_tree .collapsed .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
+.jsenabled.dir-rtl .block_settings .block_tree .emptybranch .tree_item,
+.jsenabled.dir-rtl .block_settings .block_tree [aria-expanded="false"].emptybranch .tree_item.branch {
+    background-image: url([[pix:t/collapsed_empty_rtl]]);background-position: center right;
+}
+.jsenabled.dir-rtl .block_settings .block_tree [aria-expanded="false"] .tree_item.branch {background-image: url([[pix:t/collapsed_rtl]]);}
index 31ced58..e483abe 100644 (file)
@@ -70,8 +70,8 @@ class block_tags extends block_base {
             $this->config->numberoftags = 80;
         }
 
-        if (empty($this->config->tagtype)) {
-            $this->config->tagtype = '';
+        if (empty($this->config->showstandard)) {
+            $this->config->showstandard = core_tag_tag::BOTH_STANDARD_AND_NOT;
         }
 
         if (empty($this->config->ctx)) {
@@ -102,7 +102,7 @@ class block_tags extends block_base {
         // Get a list of tags.
 
         $tagcloud = core_tag_collection::get_tag_cloud($this->config->tagcoll,
-                $this->config->tagtype,
+                $this->config->showstandard == core_tag_tag::STANDARD_ONLY,
                 $this->config->numberoftags,
                 'name', '', $this->page->context->id, $this->config->ctx, $this->config->rec);
         $this->content->text = $OUTPUT->render_from_template('core_tag/tagcloud', $tagcloud->export_for_template($OUTPUT));
index 89742c5..4ea8209 100644 (file)
@@ -48,10 +48,10 @@ class block_tags_edit_form extends block_edit_form {
         $mform->setDefault('config_numberoftags', 80);
 
         $defaults = array(
-            'official' => get_string('officialonly', 'block_tags'),
-            '' => get_string('anytype', 'block_tags'));
-        $mform->addElement('select', 'config_tagtype', get_string('defaultdisplay', 'block_tags'), $defaults);
-        $mform->setDefault('config_tagtype', '');
+            core_tag_tag::STANDARD_ONLY => get_string('standardonly', 'block_tags'),
+            core_tag_tag::BOTH_STANDARD_AND_NOT => get_string('anytype', 'block_tags'));
+        $mform->addElement('select', 'config_showstandard', get_string('defaultdisplay', 'block_tags'), $defaults);
+        $mform->setDefault('config_showstandard', core_tag_tag::BOTH_STANDARD_AND_NOT);
 
         $defaults = array(0 => context_system::instance()->get_context_name());
         $parentcontext = context::instance_by_id($this->block->instance->parentcontextid);
index 02dd7a1..8f1a1b7 100644 (file)
@@ -26,11 +26,11 @@ $string['anycollection'] = 'Any';
 $string['anytype'] = 'All';
 $string['configtitle'] = 'Block title';
 $string['disabledtags'] = 'Tags are disabled';
-$string['defaultdisplay'] = 'Tag type to display';
-$string['officialonly'] = 'Only official';
+$string['defaultdisplay'] = 'Display tags';
 $string['pluginname'] = 'Tags';
 $string['recursivecontext'] = 'Include child contexts';
 $string['recursivecontext_help'] = 'If unchecked, tags of items in the context specified above will be displayed excluding underlying contexts, for example, you can search on course level only without searching inside course activities';
+$string['standardonly'] = 'Only standard';
 $string['tagcollection'] = 'Tag collection';
 $string['tagcollection_help'] = 'Select tag collection to display tags from. If you choose "Any" '
         . 'the tags from all collections except for those marked with * will be displayed';
index 7a25490..36e1bc0 100644 (file)
@@ -13,8 +13,8 @@ Feature: Block tags displaying tag cloud
       | fullname  | shortname |
       | Course 1  | c1        |
     And the following "tags" exist:
-      | name         | tagtype  |
-      | Neverusedtag | official |
+      | name         | isstandard  |
+      | Neverusedtag | 1           |
     And the following "course enrolments" exist:
       | user     | course | role           |
       | teacher1 | c1     | editingteacher |
index 95e1705..bf2e8b0 100644 (file)
@@ -48,11 +48,11 @@ Feature: Add and configure blocks throughout the site
     # The first block matching the pattern should be top-left block
     And I should see "Comments" in the "//*[@id='region-pre' or @id='block-region-side-pre']/descendant::div[contains(concat(' ', normalize-space(@class), ' '), ' block ')]" "xpath_element"
 
-  Scenario: Blocks on the dashboard page cannot have roles assigned to them
+  Scenario: Blocks on the dashboard page can have roles assigned to them
     Given I log in as "manager1"
     And I click on "Dashboard" "link" in the "Navigation" "block"
     When I press "Customise this page"
-    Then I should not see "Assign roles in Navigation block"
+    Then I should see "Assign roles in Navigation block"
 
   Scenario: Blocks on courses can have roles assigned to them
     Given I log in as "teacher1"
index 34305e1..cdda9f3 100644 (file)
@@ -1,6 +1,12 @@
 This files describes API changes in /blocks/* - activity modules,
 information provided here is intended especially for developers.
 
+=== 3.1 ===
+
+* The collapsed class was removed from the navigation block to make it compatible with aria.
+* New aria attributes were added on the navigation block [aria-expanded="false"].
+* The tree JS handling were moved from YUI to AMD module (Jquery).
+
 === 2.9 ===
 
 * The obsolete method preferred_width() was removed (it was not doing anything)
index 6595848..89d9acb 100644 (file)
@@ -95,13 +95,15 @@ if (!empty($blogs)) {
         $editicon = $OUTPUT->action_icon($editurl, new pix_icon('t/edit', get_string('editexternalblog', 'blog')));
 
         $deletelink = new moodle_url('/blog/external_blogs.php', array('delete' => $blog->id, 'sesskey' => sesskey()));
-        $deleteicon = $OUTPUT->action_icon($deletelink, new pix_icon('t/delete', get_string('deleteexternalblog', 'blog')));
+        $action = new confirm_action(get_string('externalblogdeleteconfirm', 'blog'));
+        $deleteicon = $OUTPUT->action_icon($deletelink, new pix_icon('t/delete', get_string('deleteexternalblog', 'blog')),
+                                           $action);
 
         $table->data[] = new html_table_row(array($blog->name,
                                                   $blog->url,
                                                   userdate($blog->timefetched),
                                                   $validicon,
-                                                  $editicon . '&nbsp'. $deleteicon));
+                                                  $editicon . $deleteicon));
     }
     echo html_writer::table($table);
 }
index 81eac30..80c3be5 100644 (file)
@@ -66,7 +66,7 @@ class core_blog_lib_testcase extends advanced_testcase {
 
         // Create default tag.
         $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
-            'rawname' => 'Testtagname', 'tagtype' => 'official'));
+            'rawname' => 'Testtagname', 'isstandard' => 1));
 
         // Create default post.
         $post = new stdClass();
index e43d324..99ad750 100644 (file)
@@ -65,11 +65,13 @@ class completion_criteria_grade extends completion_criteria {
         $mform->addElement('text', 'criteria_grade_value', get_string('graderequired', 'completion'));
         $mform->disabledIf('criteria_grade_value', 'criteria_grade');
         $mform->setType('criteria_grade_value', PARAM_RAW); // Uses unformat_float.
-        $mform->setDefault('criteria_grade_value', format_float($data));
+        // Grades are stored in Moodle with 5 decimal points, make sure we do not accidentally round them
+        // when setting the form value.
+        $mform->setDefault('criteria_grade_value', format_float($data, 5));
 
         if ($this->id) {
             $mform->setDefault('criteria_grade', 1);
-            $mform->setDefault('criteria_grade_value', format_float($this->gradepass));
+            $mform->setDefault('criteria_grade_value', format_float($this->gradepass, 5));
         }
     }
 
@@ -141,7 +143,10 @@ class completion_criteria_grade extends completion_criteria {
      * @return string
      */
     public function get_title_detailed() {
-        $graderequired = round($this->gradepass, 2).'%';
+        global $CFG;
+        require_once($CFG->libdir . '/gradelib.php');
+        $decimalpoints = grade_get_setting($this->course, 'decimalpoints', $CFG->grade_decimalpoints);
+        $graderequired = format_float($this->gradepass, $decimalpoints);
         return get_string('gradexrequired', 'completion', $graderequired);
     }
 
@@ -161,11 +166,15 @@ class completion_criteria_grade extends completion_criteria {
      * @return string
      */
     public function get_status($completion) {
+        global $CFG;
+        require_once($CFG->libdir . '/gradelib.php');
+        $decimalpoints = grade_get_setting($this->course, 'decimalpoints', $CFG->grade_decimalpoints);
+
         $grade = $this->get_grade($completion);
         $graderequired = $this->get_title_detailed();
 
         if ($grade) {
-            $grade = round($grade, 2).'%';
+            $grade = format_float($grade, $decimalpoints);
         } else {
             $grade = get_string('nograde');
         }
@@ -235,15 +244,19 @@ class completion_criteria_grade extends completion_criteria {
      *     type, criteria, requirement, status
      */
     public function get_details($completion) {
+        global $CFG;
+        require_once($CFG->libdir . '/gradelib.php');
+        $decimalpoints = grade_get_setting($this->course, 'decimalpoints', $CFG->grade_decimalpoints);
+
         $details = array();
         $details['type'] = get_string('coursegrade', 'completion');
         $details['criteria'] = get_string('graderequired', 'completion');
-        $details['requirement'] = round($this->gradepass, 2).'%';
+        $details['requirement'] = format_float($this->gradepass, $decimalpoints);
         $details['status'] = '';
 
-        $grade = round($this->get_grade($completion), 2);
+        $grade = format_float($this->get_grade($completion), $decimalpoints);
         if ($grade) {
-            $details['status'] = $grade.'%';
+            $details['status'] = $grade;
         }
 
         return $details;
index 3762290..f1b4215 100644 (file)
@@ -88,22 +88,10 @@ class behat_completion extends behat_base {
      * @Given /^I go to the current course activity completion report$/
      */
     public function go_to_the_current_course_activity_completion_report() {
+        $completionnode = get_string('pluginname', 'report_progress');
+        $reportsnode = get_string('courseadministration') . ' > ' . get_string('reports');
 
-        $steps = array();
-
-        // Expand reports node if we can't see the link.
-        try {
-            $this->find('xpath', "//div[@id='settingsnav']" .
-                "/descendant::li" .
-                "/descendant::li[not(contains(concat(' ', normalize-space(@class), ' '), ' collapsed '))]" .
-                "/descendant::p[contains(., '" . get_string('pluginname', 'report_progress') . "')]");
-        } catch (ElementNotFoundException $e) {
-            $steps[] = new Given('I expand "' . get_string('reports') . '" node');
-        }
-
-        $steps[] = new Given('I follow "' . get_string('pluginname', 'report_progress') . '"');
-
-        return $steps;
+        return new Given('I navigate to "' . $completionnode . '" node in "' . $reportsnode . '"');
     }
 
     /**
index beb9ac3..04b51d7 100644 (file)
@@ -589,6 +589,10 @@ $CFG->admin = 'admin';
 // Divert all outgoing emails to this address to test and debug emailing features
 // $CFG->divertallemailsto = 'root@localhost.local'; // NOT FOR PRODUCTION SERVERS!
 //
+// Except for certain email addresses you want to let through for testing. Accepts
+// a comma separated list of regexes.
+// $CFG->divertallemailsexcept = 'tester@dev.com, fred(\+.*)?@example.com'; // NOT FOR PRODUCTION SERVERS!
+//
 // Uncomment if you want to allow empty comments when modifying install.xml files.
 // $CFG->xmldbdisablecommentchecking = true;    // NOT FOR PRODUCTION SERVERS!
 //
index 89588dc..b878ca1 100644 (file)
@@ -453,6 +453,11 @@ function can_update_moduleinfo($cm) {
 function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
     global $DB, $CFG;
 
+    $data = new stdClass();
+    if ($mform) {
+        $data = $mform->get_data();
+    }
+
     // Attempt to include module library before we make any changes to DB.
     include_modulelib($moduleinfo->modulename);
 
@@ -523,9 +528,44 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
         $moduleinfo->introformat = $moduleinfo->introeditor['format'];
         unset($moduleinfo->introeditor);
     }
+    // Get the a copy of the grade_item before it is modified incase we need to scale the grades.
+    $oldgradeitem = null;
+    $newgradeitem = null;
+    if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
+        // Fetch the grade item before it is updated.
+        $oldgradeitem = grade_item::fetch(array('itemtype' => 'mod',
+                                                'itemmodule' => $moduleinfo->modulename,
+                                                'iteminstance' => $moduleinfo->instance,
+                                                'itemnumber' => 0,
+                                                'courseid' => $moduleinfo->course));
+    }
+
     $updateinstancefunction = $moduleinfo->modulename."_update_instance";
     if (!$updateinstancefunction($moduleinfo, $mform)) {
-        print_error('cannotupdatemod', '', course_get_url($course, $cw->section), $moduleinfo->modulename);
+        print_error('cannotupdatemod', '', course_get_url($course, $cm->section), $moduleinfo->modulename);
+    }
+
+    // This needs to happen AFTER the grademin/grademax have already been updated.
+    if (!empty($data->grade_rescalegrades) && $data->grade_rescalegrades == 'yes') {
+        // Get the grade_item after the update call the activity to scale the grades.
+        $newgradeitem = grade_item::fetch(array('itemtype' => 'mod',
+                                                'itemmodule' => $moduleinfo->modulename,
+                                                'iteminstance' => $moduleinfo->instance,
+                                                'itemnumber' => 0,
+                                                'courseid' => $moduleinfo->course));
+        if ($newgradeitem && $oldgradeitem->gradetype == GRADE_TYPE_VALUE && $newgradeitem->gradetype == GRADE_TYPE_VALUE) {
+            $params = array(
+                $course,
+                $cm,
+                $oldgradeitem->grademin,
+                $oldgradeitem->grademax,
+                $newgradeitem->grademin,
+                $newgradeitem->grademax
+            );
+            if (!component_callback('mod_' . $moduleinfo->modulename, 'rescale_activity_grades', $params)) {
+                print_error('cannotreprocessgrades', '', course_get_url($course, $cm->section), $moduleinfo->modulename);
+            }
+        }
     }
 
     // Make sure visibility is set correctly (in particular in calendar).
index 690dda8..1f493d0 100644 (file)
@@ -109,9 +109,9 @@ abstract class moodleform_mod extends moodleform {
         $this->_features->defaultcompletion = plugin_supports('mod', $this->_modname, FEATURE_MODEDIT_DEFAULT_COMPLETION, true);
         $this->_features->rating            = plugin_supports('mod', $this->_modname, FEATURE_RATE, false);
         $this->_features->showdescription   = plugin_supports('mod', $this->_modname, FEATURE_SHOW_DESCRIPTION, false);
-
         $this->_features->gradecat          = ($this->_features->outcomes or $this->_features->hasgrades);
         $this->_features->advancedgrading   = plugin_supports('mod', $this->_modname, FEATURE_ADVANCED_GRADING, false);
+        $this->_features->canrescale = (component_callback_exists('mod_' . $this->_modname, 'rescale_activity_grades') !== false);
     }
 
     /**
@@ -405,7 +405,9 @@ abstract class moodleform_mod extends moodleform {
 
             $permission=CAP_ALLOW;
             $rolenamestring = null;
+            $isupdate = false;
             if (!empty($this->_cm)) {
+                $isupdate = true;
                 $context = context_module::instance($this->_cm->id);
 
                 $rolenames = get_role_names_with_caps_in_context($context, array('moodle/rating:rate', 'mod/'.$this->_cm->modname.':rate'));
@@ -420,7 +422,25 @@ abstract class moodleform_mod extends moodleform {
             $mform->setDefault('assessed', 0);
             $mform->addHelpButton('assessed', 'aggregatetype', 'rating');
 
-            $mform->addElement('modgrade', 'scale', get_string('scale'), false);
+            $gradeoptions = array('isupdate' => $isupdate,
+                                  'currentgrade' => false,
+                                  'hasgrades' => false,
+                                  'canrescale' => $this->_features->canrescale,
+                                  'useratings' => $this->_features->rating);
+            if ($isupdate) {
+                $gradeitem = grade_item::fetch(array('itemtype' => 'mod',
+                                                     'itemmodule' => $this->_cm->modname,
+                                                     'iteminstance' => $this->_cm->instance,
+                                                     'itemnumber' => 0,
+                                                     'courseid' => $COURSE->id));
+                if ($gradeitem) {
+                    $gradeoptions['currentgrade'] = $gradeitem->grademax;
+                    $gradeoptions['currentgradetype'] = $gradeitem->gradetype;
+                    $gradeoptions['currentscaleid'] = $gradeitem->scaleid;
+                    $gradeoptions['hasgrades'] = $gradeitem->has_grades();
+                }
+            }
+            $mform->addElement('modgrade', 'scale', get_string('scale'), $gradeoptions);
             $mform->disabledIf('scale', 'assessed', 'eq', 0);
             $mform->addHelpButton('scale', 'modgrade', 'grades');
             $mform->setDefault('scale', $CFG->gradepointdefault);
@@ -641,6 +661,12 @@ abstract class moodleform_mod extends moodleform {
     public function standard_grading_coursemodule_elements() {
         global $COURSE, $CFG;
         $mform =& $this->_form;
+        $isupdate = !empty($this->_cm);
+        $gradeoptions = array('isupdate' => $isupdate,
+                              'currentgrade' => false,
+                              'hasgrades' => false,
+                              'canrescale' => $this->_features->canrescale,
+                              'useratings' => $this->_features->rating);
 
         if ($this->_features->hasgrades) {
 
@@ -650,7 +676,21 @@ abstract class moodleform_mod extends moodleform {
 
             //if supports grades and grades arent being handled via ratings
             if (!$this->_features->rating) {
-                $mform->addElement('modgrade', 'grade', get_string('grade'));
+
+                if ($isupdate) {
+                    $gradeitem = grade_item::fetch(array('itemtype' => 'mod',
+                                                         'itemmodule' => $this->_cm->modname,
+                                                         'iteminstance' => $this->_cm->instance,
+                                                         'itemnumber' => 0,
+                                                         'courseid' => $COURSE->id));
+                    if ($gradeitem) {
+                        $gradeoptions['currentgrade'] = $gradeitem->grademax;
+                        $gradeoptions['currentgradetype'] = $gradeitem->gradetype;
+                        $gradeoptions['currentscaleid'] = $gradeitem->scaleid;
+                        $gradeoptions['hasgrades'] = $gradeitem->has_grades();
+                    }
+                }
+                $mform->addElement('modgrade', 'grade', get_string('grade'), $gradeoptions);
                 $mform->addHelpButton('grade', 'modgrade', 'grades');
                 $mform->setDefault('grade', $CFG->gradepointdefault);
             }
index ffe7470..e0ba5c1 100644 (file)
@@ -215,6 +215,9 @@ class behat_course extends behat_base {
             throw new DriverException('Section edit menu not available when Javascript is disabled');
         }
 
+        // Wait for section to be available, before clicking on the menu.
+        $this->i_wait_until_section_is_available($sectionnumber);
+
         // If it is already opened we do nothing.
         $xpath = $this->section_exists($sectionnumber);
         $xpath .= "/descendant::div[contains(@class, 'section-actions')]/descendant::a[contains(@class, 'textmenu')]";
@@ -300,6 +303,11 @@ class behat_course extends behat_base {
      */
     public function i_show_section($sectionnumber) {
         $showlink = $this->show_section_icon_exists($sectionnumber);
+
+        // Ensure section edit menu is open before interacting with it.
+        if ($this->running_javascript()) {
+            $this->i_open_section_edit_menu($sectionnumber);
+        }
         $showlink->click();
 
         if ($this->running_javascript()) {
@@ -316,6 +324,11 @@ class behat_course extends behat_base {
      */
     public function i_hide_section($sectionnumber) {
         $hidelink = $this->hide_section_icon_exists($sectionnumber);
+
+        // Ensure section edit menu is open before interacting with it.
+        if ($this->running_javascript()) {
+            $this->i_open_section_edit_menu($sectionnumber);
+        }
         $hidelink->click();
 
         if ($this->running_javascript()) {
@@ -964,11 +977,6 @@ class behat_course extends behat_base {
         // We need to know the course format as the text strings depends on them.
         $courseformat = $this->get_course_format();
 
-        // If javascript is on, link is inside a menu.
-        if ($this->running_javascript()) {
-            $this->i_open_section_edit_menu($sectionnumber);
-        }
-
         // Checking the show button alt text and show icon.
         $showtext = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('showfromothers', $courseformat));
         $linkxpath = $xpath . "/descendant::a[@title=$showtext]";
@@ -996,11 +1004,6 @@ class behat_course extends behat_base {
         // We need to know the course format as the text strings depends on them.
         $courseformat = $this->get_course_format();
 
-        // If javascript is on, link is inside a menu.
-        if ($this->running_javascript()) {
-            $this->i_open_section_edit_menu($sectionnumber);
-        }
-
         // Checking the hide button alt text and hide icon.
         $hidetext = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('hidefromothers', $courseformat));
         $linkxpath = $xpath . "/descendant::a[@title=$hidetext]";
index f76f93a..b330e80 100644 (file)
@@ -15,8 +15,8 @@ Feature: Tagging courses
       | Course 1  | c1        |
       | Course 2  | c2        |
     And the following "tags" exist:
-      | name         | tagtype  |
-      | Neverusedtag | official |
+      | name         | isstandard  |
+      | Neverusedtag | 1           |
     And the following "course enrolments" exist:
       | user     | course | role           |
       | teacher1 | c1     | editingteacher |
index ae1a9e4..8fa81d6 100644 (file)
@@ -33,6 +33,13 @@ class enrol_ldap_plugin extends enrol_plugin {
     protected $enroltype = 'enrol_ldap';
     protected $errorlogtag = '[ENROL LDAP] ';
 
+    /**
+     * The object class to use when finding users.
+     *
+     * @var string $userobjectclass
+     */
+    protected $userobjectclass;
+
     /**
      * Constructor for the plugin. In addition to calling the parent
      * constructor, we define and 'fix' some settings depending on the
@@ -59,8 +66,13 @@ class enrol_ldap_plugin extends enrol_plugin {
         unset($ldap_usertypes);
 
         $default = ldap_getdefaults();
-        // Remove the objectclass default, as the values specified there are for
-        // users, and we are dealing with groups here.
+
+        // The objectclass in the defaults is for a user.
+        // This will be required later, but enrol_ldap uses 'objectclass' for its group objectclass.
+        // Save the normalised user objectclass for later.
+        $this->userobjectclass = ldap_normalise_objectclass($default['objectclass'][$this->get_config('user_type')]);
+
+        // Remove the objectclass default, as the values specified there are for users, and we are dealing with groups here.
         unset($default['objectclass']);
 
         // Use defaults if values not given. Dont use this->get_config()
@@ -72,31 +84,19 @@ class enrol_ldap_plugin extends enrol_plugin {
             }
         }
 
+        // Normalise the objectclass used for groups.
         if (empty($this->config->objectclass)) {
-            // Can't send empty filter. Fix it for now and future occasions
-            $this->set_config('objectclass', '(objectClass=*)');
-        } else if (stripos($this->config->objectclass, 'objectClass=') === 0) {
-            // Value is 'objectClass=some-string-here', so just add ()
-            // around the value (filter _must_ have them).
-            // Fix it for now and future occasions
-            $this->set_config('objectclass', '('.$this->config->objectclass.')');
-        } else if (stripos($this->config->objectclass, '(') !== 0) {
-            // Value is 'some-string-not-starting-with-left-parentheses',
-            // which is assumed to be the objectClass matching value.
-            // So build a valid filter with it.
-            $this->set_config('objectclass', '(objectClass='.$this->config->objectclass.')');
+            // No objectclass set yet - set a default class.
+            $this->config->objectclass = ldap_normalise_objectclass(null, '*');
+            $this->set_config('objectclass', $this->config->objectclass);
         } else {
-            // There is an additional possible value
-            // '(some-string-here)', that can be used to specify any
-            // valid filter string, to select subsets of users based
-            // on any criteria. For example, we could select the users
-            // whose objectClass is 'user' and have the
-            // 'enabledMoodleUser' attribute, with something like:
-            //
-            //   (&(objectClass=user)(enabledMoodleUser=1))
-            //
-            // In this particular case we don't need to do anything,
-            // so leave $this->config->objectclass as is.
+            $objectclass = ldap_normalise_objectclass($this->config->objectclass);
+            if ($objectclass !== $this->config->objectclass) {
+                // The objectclass was changed during normalisation.
+                // Save it in config, and update the local copy of config.
+                $this->set_config('objectclass', $objectclass);
+                $this->config->objectclass = $objectclass;
+            }
         }
     }
 
@@ -490,7 +490,7 @@ class enrol_ldap_plugin extends enrol_plugin {
                                 // as the idnumber does not match their dn and we get dn's from membership.
                                 $memberidnumbers = array();
                                 foreach ($ldapmembers as $ldapmember) {
-                                    $result = ldap_read($this->ldapconnection, $ldapmember, '(objectClass=*)',
+                                    $result = ldap_read($this->ldapconnection, $ldapmember, $this->userobjectclass,
                                                         array($this->config->idnumber_attribute));
                                     $entry = ldap_first_entry($this->ldapconnection, $result);
                                     $values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute);
@@ -838,10 +838,9 @@ class enrol_ldap_plugin extends enrol_plugin {
         require_once($CFG->libdir.'/ldaplib.php');
 
         $ldap_contexts = explode(';', $this->get_config('user_contexts'));
-        $ldap_defaults = ldap_getdefaults();
 
         return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts,
-                                '(objectClass='.$ldap_defaults['objectclass'][$this->get_config('user_type')].')',
+                                $this->userobjectclass,
                                 $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub'));
     }
 
index be471bd..9ac6277 100644 (file)
@@ -469,4 +469,64 @@ class enrol_ldap_testcase extends advanced_testcase {
             }
         }
     }
+
+    /**
+     * Test that normalisation of the use objectclass is completed successfully.
+     *
+     * @dataProvider objectclass_fetch_provider
+     * @param string $usertype The supported user type
+     * @param string $expected The expected filter value
+     */
+    public function test_objectclass_fetch($usertype, $expected) {
+        $this->resetAfterTest();
+        // Set the user type - this must be performed before the plugin is instantiated.
+        set_config('user_type', $usertype, 'enrol_ldap');
+
+        // Fetch the plugin.
+        $instance = enrol_get_plugin('ldap');
+
+        // Use reflection to sneak a look at the plugin.
+        $rc = new ReflectionClass('enrol_ldap_plugin');
+        $rcp = $rc->getProperty('userobjectclass');
+        $rcp->setAccessible(true);
+
+        // Fetch the current userobjectclass value.
+        $value = $rcp->getValue($instance);
+        $this->assertEquals($expected, $value);
+    }
+
+    /**
+     * Data provider for the test_objectclass_fetch testcase.
+     *
+     * @return array of testcases.
+     */
+    public function objectclass_fetch_provider() {
+        return array(
+            // This is the list of values from ldap_getdefaults() normalised.
+            'edir' => array(
+                'edir',
+                '(objectClass=user)'
+            ),
+            'rfc2307' => array(
+                'rfc2307',
+                '(objectClass=posixaccount)'
+            ),
+            'rfc2307bis' => array(
+                'rfc2307bis',
+                '(objectClass=posixaccount)'
+            ),
+            'samba' => array(
+                'samba',
+                '(objectClass=sambasamaccount)'
+            ),
+            'ad' => array(
+                'ad',
+                '(samaccounttype=805306368)'
+            ),
+            'default' => array(
+                'default',
+                '(objectClass=*)'
+            ),
+        );
+    }
 }
index ecffc3c..58f6b4c 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js differ
index 46b748d..c2fdc14 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js differ
index ecffc3c..58f6b4c 100644 (file)
Binary files a/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js and b/filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js differ
index 3689b4a..2d0471e 100644 (file)
@@ -73,7 +73,6 @@ Y.extend(AUTOLINKER, Y.Base, {
             alertpanelid,
             definition,
             position;
-        var self = this;
         try {
             data = Y.JSON.parse(content);
             if (data.success){
@@ -89,7 +88,7 @@ Y.extend(AUTOLINKER, Y.Base, {
 
                     // Register alertpanel for stacking.
                     alertpanelid = '#moodle-dialogue-' + alertpanel.get('COUNT');
-                    alertpanel.on('complete', this._deletealertpanel(self.alertpanels, alertpanelid));
+                    alertpanel.on('complete', this._deletealertpanel, this, alertpanelid);
 
                     // We already have some windows opened, so set the right position...
                     if (!Y.Object.isEmpty(this.alertpanels)){
@@ -118,8 +117,8 @@ Y.extend(AUTOLINKER, Y.Base, {
         });
         return lastPosition;
     },
-    _deletealertpanel : function(alertpanels, alertpanelid) {
-        delete alertpanels[alertpanelid];
+    _deletealertpanel : function(ev, alertpanelid) {
+        delete this.alertpanels[alertpanelid];
     }
 }, {
     NAME : AUTOLINKERNAME,
index 75d3901..7ee5cbe 100644 (file)
@@ -156,6 +156,8 @@ if ($mform->is_cancelled()) {
     }
 
     $grade_item = new grade_item(array('id'=>$id, 'courseid'=>$courseid));
+    $oldmin = $grade_item->grademin;
+    $oldmax = $grade_item->grademax;
     grade_item::set_properties($grade_item, $data);
     $grade_item->outcomeid = null;
 
@@ -175,6 +177,12 @@ if ($mform->is_cancelled()) {
 
     } else {
         $grade_item->update();
+
+        if (!empty($data->rescalegrades) && $data->rescalegrades == 'yes') {
+            $newmin = $grade_item->grademin;
+            $newmax = $grade_item->grademax;
+            $grade_item->rescale_grades_keep_percentage($oldmin, $oldmax, $newmin, $newmax, 'gradebook');
+        }
     }
 
     // update hiding flag
index f8a08c6..572faa3 100644 (file)
@@ -51,6 +51,23 @@ class edit_item_form extends moodleform {
         $mform->addHelpButton('idnumber', 'idnumbermod');
         $mform->setType('idnumber', PARAM_RAW);
 
+        if (!empty($item->id)) {
+            $gradeitem = new grade_item(array('id' => $item->id, 'courseid' => $item->courseid));
+            // If grades exist set a message so the user knows why they can not alter the grade type or scale.
+            // We could never change the grade type for external items, so only need to show this for manual grade items.
+            if ($gradeitem->has_grades() && !$gradeitem->is_external_item()) {
+                // Set a message so the user knows why they can not alter the grade type or scale.
+                if ($gradeitem->gradetype == GRADE_TYPE_SCALE) {
+                    $gradesexistmsg = get_string('modgradecantchangegradetyporscalemsg', 'grades');
+                } else {
+                    $gradesexistmsg = get_string('modgradecantchangegradetypemsg', 'grades');
+                }
+
+                $gradesexisthtml = '<div class=\'alert\'>' . $gradesexistmsg . '</div>';
+                $mform->addElement('static', 'gradesexistmsg', '', $gradesexisthtml);
+            }
+        }
+
         // Manual grade items cannot have grade type GRADE_TYPE_NONE.
         $options = array(GRADE_TYPE_VALUE => get_string('typevalue', 'grades'),
                          GRADE_TYPE_SCALE => get_string('typescale', 'grades'),
@@ -85,6 +102,14 @@ class edit_item_form extends moodleform {
         $mform->addHelpButton('scaleid', 'typescale', 'grades');
         $mform->disabledIf('scaleid', 'gradetype', 'noteq', GRADE_TYPE_SCALE);
 
+        $choices = array();
+        $choices[''] = get_string('choose');
+        $choices['no'] = get_string('no');
+        $choices['yes'] = get_string('yes');
+        $mform->addElement('select', 'rescalegrades', get_string('modgraderescalegrades', 'grades'), $choices);
+        $mform->addHelpButton('rescalegrades', 'modgraderescalegrades', 'grades');
+        $mform->disabledIf('rescalegrades', 'gradetype', 'noteq', GRADE_TYPE_VALUE);
+
         $mform->addElement('text', 'grademax', get_string('grademax', 'grades'));
         $mform->addHelpButton('grademax', 'grademax', 'grades');
         $mform->disabledIf('grademax', 'gradetype', 'noteq', GRADE_TYPE_VALUE);
@@ -269,7 +294,39 @@ class edit_item_form extends moodleform {
                         // the idnumber of grade itemnumber 0 is synced with course_modules
                         $mform->hardFreeze('idnumber');
                     }
-                    //$mform->removeElement('calculation');
+
+                    // For external items we can not change the grade type, even if no grades exist, so if it is set to
+                    // scale, then remove the grademax and grademin fields from the form - no point displaying them.
+                    if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
+                        $mform->removeElement('grademax');
+                        if ($mform->elementExists('grademin')) {
+                            $mform->removeElement('grademin');
+                        }
+                    } else { // Not using scale, so remove it.
+                        $mform->removeElement('scaleid');
+                    }
+
+                    // Always remove the rescale grades element if it's an external item.
+                    $mform->removeElement('rescalegrades');
+                } else if ($grade_item->has_grades()) {
+                    // Can't change the grade type or the scale if there are grades.
+                    $mform->hardFreeze('gradetype, scaleid');
+
+                    // If we are using scales then remove the unnecessary rescale and grade fields.
+                    if ($grade_item->gradetype == GRADE_TYPE_SCALE) {
+                        $mform->removeElement('rescalegrades');
+                        $mform->removeElement('grademax');
+                        if ($mform->elementExists('grademin')) {
+                            $mform->removeElement('grademin');
+                        }
+                    } else { // Remove the scale field.
+                        $mform->removeElement('scaleid');
+                        // Set the maximum grade to disabled unless a grade is chosen.
+                        $mform->disabledIf('grademax', 'rescalegrades', 'eq', '');
+                    }
+                } else {
+                    // Remove the rescale element if there are no grades.
+                    $mform->removeElement('rescalegrades');
                 }
             }
 
@@ -342,6 +399,7 @@ class edit_item_form extends moodleform {
             // all new items are manual, children of course category
             $mform->removeElement('plusfactor');
             $mform->removeElement('multfactor');
+            $mform->removeElement('rescalegrades');
         }
 
         // no parent header for course category
@@ -353,12 +411,15 @@ class edit_item_form extends moodleform {
 /// perform extra validation before submission
     function validation($data, $files) {
         global $COURSE;
+        $grade_item = false;
+        if ($data['id']) {
+            $grade_item = new grade_item(array('id' => $data['id'], 'courseid' => $data['courseid']));
+        }
 
         $errors = parent::validation($data, $files);
 
         if (array_key_exists('idnumber', $data)) {
-            if ($data['id']) {
-                $grade_item = new grade_item(array('id'=>$data['id'], 'courseid'=>$data['courseid']));
+            if ($grade_item) {
                 if ($grade_item->itemtype == 'mod') {
                     $cm = get_coursemodule_from_instance($grade_item->itemmodule, $grade_item->iteminstance, $grade_item->courseid);
                 } else {
@@ -386,6 +447,31 @@ class edit_item_form extends moodleform {
             }
         }
 
+        // We do not want the user to be able to change the grade type or scale for this item if grades exist.
+        if ($grade_item && $grade_item->has_grades()) {
+            // Check that grade type is set - should never not be set unless form has been modified.
+            if (!isset($data['gradetype'])) {
+                $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades');
+            } else if ($data['gradetype'] !== $grade_item->gradetype) { // Check if we are changing the grade type.
+                $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades');
+            } else if ($data['gradetype'] == GRADE_TYPE_SCALE) {
+                // Check if we are changing the scale - can't do this when grades exist.
+                if (isset($data['scaleid']) && ($data['scaleid'] !== $grade_item->scaleid)) {
+                    $errors['scaleid'] = get_string('modgradecantchangescale', 'grades');
+                }
+            }
+        }
+        if ($grade_item) {
+            if ($grade_item->gradetype == GRADE_TYPE_VALUE) {
+                if (grade_floats_different($data['grademin'], $grade_item->grademin) ||
+                    grade_floats_different($data['grademax'], $grade_item->grademax)) {
+                    if ($grade_item->has_grades() && empty($data['rescalegrades'])) {
+                        $errors['rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades');
+                    }
+                }
+            }
+        }
+
         return $errors;
     }
 
index 09f1b04..c51e281 100644 (file)
@@ -425,6 +425,7 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Manual item 2":
       | Extra credit  | 0   |
       | Maximum grade | 200 |
+      | Rescale existing grades | No |
     And I give the grade "21.00" to the user "Student 1" for the grade item "Manual item 2"
     And I press "Save changes"
     And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
@@ -435,6 +436,7 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Manual item 2":
       | Extra credit  | 0   |
       | Maximum grade | 100 |
+      | Rescale existing grades | No |
     And I give the grade "21.00" to the user "Student 1" for the grade item "Manual item 2"
     And I press "Save changes"
     And I give the grade "20.00" to the user "Student 1" for the grade item "Manual item 2"
index 145a39e..aa07eab 100644 (file)
@@ -145,6 +145,7 @@ Feature: Calculated grade items can be used in the gradebook
       | Course total | -                 | 112.50 | 0–150 | 75.00 %    | -                            |
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
+      | Rescale existing grades | No |
       | Maximum grade | 40 |
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
index cd8d791..87eeb8f 100644 (file)
@@ -146,6 +146,7 @@ Feature: Gradebook calculations for calculated grade items before the fix 201506
       | Course total | -                 | 112.50 | 0–200 | 56.25 %    | -                            |
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "calc item":
+      | Rescale existing grades | No |
       | Maximum grade | 40 |
     And I follow "Grader report"
     And I give the grade "65.00" to the user "Student 2" for the grade item "grade item 1"
diff --git a/grade/tests/behat/grade_grade_minmax_change.feature b/grade/tests/behat/grade_grade_minmax_change.feature
new file mode 100644 (file)
index 0000000..edcc64a
--- /dev/null
@@ -0,0 +1,76 @@
+@core @core_grades
+Feature: We can change the maximum and minimum number of points for manual items with existing grades
+  In order to verify existing grades are modified as expected
+  As an teacher
+  I need to modify a grade item with exiting grades
+  I need to ensure existing grades are modified in an expected manner
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "users" exist:
+      | username | firstname | lastname | email | idnumber |
+      | teacher1 | Teacher | 1 | teacher1@example.com | t1 |
+      | student1 | Student | 1 | student1@example.com | s1 |
+      | student2 | Student | 2 | student2@example.com | s2 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+    And I log in as "teacher1"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I navigate to "Grades" node in "Course administration"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I press "Add grade item"
+    And I set the following fields to these values:
+      | Item name | Manual item 1 |
+      | Minimum grade | 0 |
+      | Maximum grade | 100 |
+    And I press "Save changes"
+    And I navigate to "Course grade settings" node in "Grade administration > Setup"
+    And I set the field "Show weightings" to "Show"
+    And I set the field "Show contribution to course total" to "Show"
+    And I press "Save changes"
+
+  Scenario: Change maximum number of points on a graded item.
+    And I follow "Course 1"
+    And I navigate to "Grades" node in "Course administration"
+    And I turn editing mode on
+    And I give the grade "10.00" to the user "Student 1" for the grade item "Manual item 1"
+    And I give the grade "8.00" to the user "Student 2" for the grade item "Manual item 1"
+    And I press "Save changes"
+    When I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I click on "Edit" "link" in the "Manual item 1" "table_row"
+    And I click on "Edit settings" "link" in the "Manual item 1" "table_row"
+    And I set the following fields to these values:
+      | Maximum grade | 10 |
+      | Rescale existing grades | No |
+    And I press "Save changes"
+    And I follow "User report"
+    And I select "Student 1" from the "Select all or one user" singleselect
+    Then the following should exist in the "user-grade" table:
+      | Grade item    | Calculated weight | Grade  | Contribution to course total |
+      | Manual item 1 | 100.00 %          | 10.00  | 100.00 %                     |
+    And I select "Student 2" from the "Select all or one user" singleselect
+    And the following should exist in the "user-grade" table:
+      | Grade item    | Calculated weight | Grade  | Contribution to course total |
+      | Manual item 1 | 100.00 %          | 8.00   | 80.00 %                      |
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I click on "Edit" "link" in the "Manual item 1" "table_row"
+    And I click on "Edit settings" "link" in the "Manual item 1" "table_row"
+    And I set the following fields to these values:
+      | Maximum grade | 20 |
+      | Rescale existing grades | Yes |
+    And I press "Save changes"
+    And I follow "User report"
+    And I select "Student 1" from the "Select all or one user" singleselect
+    And the following should exist in the "user-grade" table:
+      | Grade item    | Calculated weight | Grade  | Contribution to course total |
+      | Manual item 1 | 100.00 %          | 20.00  | 100.00 %                     |
+    And I select "Student 2" from the "Select all or one user" singleselect
+    And the following should exist in the "user-grade" table:
+      | Grade item    | Calculated weight | Grade  | Contribution to course total |
+      | Manual item 1 | 100.00 %          | 16.00   | 80.00 %                     |
diff --git a/grade/tests/behat/grade_item_validation.feature b/grade/tests/behat/grade_item_validation.feature
new file mode 100644 (file)
index 0000000..541922f
--- /dev/null
@@ -0,0 +1,93 @@
+@core_grades
+Feature: Editing a grade item
+  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
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | category | groupmode |
+      | Course 1 | C1 | 0 | 1 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "admin"
+    And I navigate to "Scales" node in "Site administration > Grades"
+    And I press "Add a new scale"
+    And I set the following fields to these values:
+      | Name  | ABCDEF |
+      | Scale | F,E,D,C,B,A |
+    And I press "Save changes"
+    And I press "Add a new scale"
+    And I set the following fields to these values:
+      | Name  | Letter scale |
+      | Scale | Disappointing, Good, Very good, Excellent |
+    And I press "Save changes"
+    And I am on site homepage
+    And I follow "Course 1"
+    And I navigate to "Gradebook setup" node in "Course administration"
+    And I press "Add grade item"
+    And I set the following fields to these values:
+      | Item name | MI 1 |
+    And I press "Save changes"
+
+  Scenario: Being able to change the grade type, scale and maximum grade for a manual grade item when there are no grades
+    Given I click on "Edit" "link" in the "MI 1" "table_row"
+    When I click on "Edit settings" "link" in the "MI 1" "table_row"
+    Then I should not see "Some grades have already been awarded, so the grade type"
+    And I set the field "Grade type" to "Scale"
+    And I press "Save changes"
+    And I should see "Scale must be selected"
+    And I set the field "Scale" to "ABCDEF"
+    And I press "Save changes"
+    And I should not see "You cannot change the type, as grades already exist for this item"
+    And I click on "Edit" "link" in the "MI 1" "table_row"
+    And I click on "Edit settings" "link" in the "MI 1" "table_row"
+    And I should not see "Some grades have already been awarded, so the grade type"
+    And I set the field "Scale" to "Letter scale"
+    And I press "Save changes"
+    And I should not see "You cannot change the scale, as grades already exist for this item"
+
+  Scenario: Attempting to change a manual item's grade type when grades already exist
+    Given I navigate to "Grader report" node in "Grade administration"
+    And I turn editing mode on
+    And I give the grade "20.00" to the user "Student 1" for the grade item "MI 1"
+    And I press "Save changes"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I click on "Edit" "link" in the "MI 1" "table_row"
+    When I click on "Edit settings" "link" in the "MI 1" "table_row"
+    Then I should see "Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades."
+    And "//div[contains(concat(' ', normalize-space(@class), ' '), 'fstatic') and contains(text(), 'Value')]" "xpath_element" should exist
+
+  Scenario: Attempting to change a manual item's scale when grades already exist
+    Given I click on "Edit" "link" in the "MI 1" "table_row"
+    And I click on "Edit settings" "link" in the "MI 1" "table_row"
+    And I set the field "Grade type" to "Scale"
+    And I set the field "Scale" to "ABCDEF"
+    And I press "Save changes"
+    And I navigate to "Grader report" node in "Grade administration"
+    And I turn editing mode on
+    And I give the grade "C" to the user "Student 1" for the grade item "MI 1"
+    And I press "Save changes"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I click on "Edit" "link" in the "MI 1" "table_row"
+    When I click on "Edit settings" "link" in the "MI 1" "table_row"
+    Then I should see "Some grades have already been awarded, so the grade type and scale cannot be changed."
+    And "//div[contains(concat(' ', normalize-space(@class), ' '), 'fstatic') and contains(text(), 'ABCDEF')]" "xpath_element" should exist
+
+  Scenario: Attempting to change a manual item's maximum grade when no rescaling option has been chosen
+    Given I navigate to "Grader report" node in "Grade administration"
+    And I turn editing mode on
+    And I give the grade "20.00" to the user "Student 1" for the grade item "MI 1"
+    And I press "Save changes"
+    And I navigate to "Gradebook setup" node in "Grade administration > Setup"
+    And I click on "Edit" "link" in the "MI 1" "table_row"
+    And I click on "Edit settings" "link" in the "MI 1" "table_row"
+    And I set the field "Maximum grade" to "50"
+    When I press "Save changes"
+    Then I should see "You must choose whether to rescale existing grades or not."
index cb1aa15..971461a 100644 (file)
@@ -100,11 +100,13 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | Course total | -                 | 60.00  | 0–300 | 20.00 %    | -                            |
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 1":
-      | Maximum grade          | 50.00 |
-      | Minimum grade          | 5.00 |
+      | Maximum grade           | 50.00 |
+      | Minimum grade           | 5.00  |
+      | Rescale existing grades | No    |
     And I set the following settings for grade item "MI 3":
-      | Maximum grade          | 50.00 |
-      | Minimum grade          | 5.00 |
+      | Maximum grade           | 50.00 |
+      | Minimum grade           | 5.00  |
+      | Rescale existing grades | No    |
     And I follow "User report"
     And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
@@ -129,6 +131,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I navigate to "Gradebook setup" node in "Grade administration > Setup"
     And I set the following settings for grade item "MI 5":
       | Maximum grade          | 200.00 |
+      | Rescale existing grades | No    |
     And I follow "User report"
     And I select "Student 1" from the "Select all or one user" singleselect
     And the following should exist in the "user-grade" table:
index 5662264..1070302 100644 (file)
@@ -29,13 +29,13 @@ Feature: We can change the grading type and maximum grade point values
     And I expand all fieldsets
     And I set the field "grade[modgrade_type]" to "Point"
     Then the "Scale" "select" should be disabled
-    And the "Maximum points" "field" should be enabled
+    And the "Maximum grade" "field" should be enabled
     And I set the field "grade[modgrade_type]" to "Scale"
-    And the "Maximum points" "field" should be disabled
+    And the "Maximum grade" "field" should be disabled
     Then the "Scale" "select" should be enabled
     And I set the field "grade[modgrade_type]" to "None"
     Then the "Scale" "select" should be disabled
-    And the "Maximum points" "field" should be disabled
+    And the "Maximum grade" "field" should be disabled
     And I press "Save and return to course"
 
   @javascript
@@ -61,7 +61,7 @@ Feature: We can change the grading type and maximum grade point values
     And I press "Save and display"
     And I follow "Edit settings"
     Then the field "grade[modgrade_scale]" matches value "Separate and Connected ways of knowing"
-    And the "Maximum points" "field" should be disabled
+    And the "Maximum grade" "field" should be disabled
     And I press "Save and return to course"
 
   @javascript
@@ -73,7 +73,7 @@ Feature: We can change the grading type and maximum grade point values
     And I press "Save and display"
     And I follow "Edit settings"
     And the "Scale" "select" should be disabled
-    And the "Maximum points" "field" should be disabled
+    And the "Maximum grade" "field" should be disabled
     And I press "Save and return to course"
 
   @javascript
@@ -84,7 +84,7 @@ Feature: We can change the grading type and maximum grade point values
     And I set the field "grade[modgrade_type]" to "Point"
     And I set the field "grade[modgrade_point]" to "20000"
     And I press "Save and display"
-    Then I should see "Invalid Grade Value. This must be an integer between 1 and 900"
+    Then I should see "Invalid grade value. This must be an integer between 1 and 900"
     And I press "Cancel"
 
   @javascript
@@ -104,5 +104,5 @@ Feature: We can change the grading type and maximum grade point values
     And I follow "Test Assignment 1"
     And I follow "Edit settings"
     And I press "Save and display"
-    Then I should see "Invalid Grade Value. This must be an integer between 1 and 100"
+    Then I should see "Invalid grade value. This must be an integer between 1 and 100"
     And I press "Cancel"
index 2fec297..2c74e52 100644 (file)
@@ -37,7 +37,7 @@ Feature: Control the aggregation of the scales
     And I log out
 
   @javascript
-  Scenario Outline: Scales can be exluded from aggregation
+  Scenario Outline: Scales can be excluded from aggregation
     Given I log in as "teacher1"
     And I follow "Course 1"
     And I navigate to "Grades" node in "Course administration"
index 6389484..802c874 100644 (file)
@@ -31,6 +31,6 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'Език';
-$string['next'] = 'СледваÑ\89а';
-$string['previous'] = 'Ð\9fÑ\80едиÑ\88на';
+$string['next'] = 'Ð\9eÑ\89е';
+$string['previous'] = 'Ð\9eбÑ\80аÑ\82но';
 $string['reload'] = 'Презареждане';
diff --git a/install/lang/dk_kursus/langconfig.php b/install/lang/dk_kursus/langconfig.php
new file mode 100644 (file)
index 0000000..e22a9ae
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['parentlanguage'] = 'dk';
+$string['thislanguage'] = 'Dansk (kursus)';
index 6a2e548..f7d304a 100644 (file)
@@ -35,8 +35,8 @@ $string['cannotcreatetempdir'] = 'לא ניתן ליצור סיפרייה זמנ
 $string['cannotdownloadcomponents'] = 'לא ניתן להוריד רכיבים.';
 $string['cannotdownloadzipfile'] = 'לא ניתן להוריד קובץ ZIP.';
 $string['cannotfindcomponent'] = 'הרכיב לא נמצא.';
-$string['cannotsavemd5file'] = 'לא ניתן לשמור קובץ md5.';
-$string['cannotsavezipfile'] = 'לא ניתן לשמור קובץ ZIP.';
+$string['cannotsavemd5file'] = 'לא ניתן לשמור קובץ md5';
+$string['cannotsavezipfile'] = 'לא ניתן לשמור קובץ ZIP';
 $string['cannotunzipfile'] = 'לא ניתן לפתוח את קובץ ה-ZIP.';
 $string['componentisuptodate'] = 'הרכיב מעודכן.';
 $string['downloadedfilecheckfailed'] = 'נכשלה בדיקת הקובץ המורד.';
index 6b87350..8dfd3a2 100644 (file)
@@ -104,7 +104,7 @@ $string['welcomep50'] = 'השימוש בכל היישומים בחבילה זו
 <a href="http://www.opensource.org/docs/definition_plain.html">קוד פתוח</a>
 והיא מופצת תחת רשיון
 <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a>';
-$string['welcomep60'] = '×\94×¢×\9e×\95×\93×\99×\9d ×\94×\91×\90×\99×\9d ×\99×\95×\91×\99×\9c×\95 ×\90×\95ת×\9a ×\91צ×\95ר×\94 ×¤×©×\95×\98×\94 ×\93ר×\9a ×\9b×\9e×\94 ×¦×¢×\93×\99×\9d ×\9c×¢×\99צ×\95×\91 ×\94×\92×\93ר×\95ת <strong>Moodle</strong> ×\91×\9e×\97ש×\91×\9a.
ª×\95×\9b×\9c ×\9c×\90שר ×\90ת ×\94×\92×\93ר×\95ת  ×\91ר×\99רת ×\94×\9e×\97×\93×\9c ×\90×\95, ×\91×\90פשר×\95ת×\9a, לשנותם לפי צרכיך.';
+$string['welcomep60'] = '×\94×¢×\9e×\95×\93×\99×\9d ×\94×\91×\90×\99×\9d ×\99×\95×\91×\99×\9c×\95 ×\90×\95ת×\9a ×\91צ×\95ר×\94 ×¤×©×\95×\98×\94 ×\93ר×\9a ×\9b×\9e×\94 ×¦×¢×\93×\99×\9d ×\9cק×\91×\99עת ×\94×\92×\93ר×\95ת <strong>Moodle</strong> ×\91שרת.
 ×\99ת×\9f ×\9c×\90שר ×\94×\92×\93ר×\95ת ×\91ררת־×\94×\9e×\97×\93×\9c ×\90×\95 לשנותם לפי צרכיך.';
 $string['welcomep70'] = 'הקש על לחצן ה"המשך" למטה כדי להמשיך עם הגדרת ה-<strong>Moodle</strong>';
 $string['wwwroot'] = 'כתובת האתר';
index 4431fff..b2085da 100644 (file)
@@ -42,7 +42,7 @@ $string['cannotsavemd5file'] = 'Enregistrament del fichièr md5 impossible';
 $string['cannotsavezipfile'] = 'Enregistrament del fichièr ZIP impossible';
 $string['cannotunzipfile'] = 'Descompression del fichièr ZIP impossibla';
 $string['componentisuptodate'] = 'Lo component es a jorn';
-$string['dmlexceptiononinstall'] = '<p>Una error de banca de donadas s\'es produsida [{$a->errorcode}].<br />{$a->debuginfo}</p>';
+$string['dmlexceptiononinstall'] = '<p>Una error de banca de donadas s\'es producha [{$a->errorcode}].<br />{$a->debuginfo}</p>';
 $string['downloadedfilecheckfailed'] = 'La verificacion del fichièr telecargat a fracassat';
 $string['invalidmd5'] = 'Lo còde de contraròtle md5 es pas valid';
 $string['missingrequiredfield'] = 'Un camp obligatòri es pas completat';
diff --git a/install/lang/or/langconfig.php b/install/lang/or/langconfig.php
new file mode 100644 (file)
index 0000000..15cbb4d
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thisdirection'] = 'ltr';
+$string['thislanguage'] = 'ଓଡ଼ିଆ';
index ec01ae5..286a072 100644 (file)
@@ -45,6 +45,9 @@ $string['environmenthead'] = 'Se verifică mediul...';
 $string['installation'] = 'Instalare';
 $string['paths'] = 'Căi';
 $string['pathshead'] = 'Confirmare căi';
+$string['pathssubdirroot'] = '<p>Calea completă către directorul care conține codul Moodle .</p>';
+$string['pathsunsecuredataroot'] = 'Locația dataroot nu este sigură';
+$string['pathswrongadmindir'] = 'Directorul admin nu există';
 $string['phpextension'] = 'extensie PHP {$a}';
 $string['phpversion'] = 'Versiune PHP';
 $string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
index ea86f2c..df349d1 100644 (file)
@@ -31,5 +31,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['language'] = 'ภาษาที่ใช้ในเว็บ';
+$string['moodlelogo'] = 'โลโก้ Moodle';
 $string['next'] = 'ต่อไป';
 $string['previous'] = 'หน้าก่อน';
+$string['reload'] = 'โหลดใหม่';
index 366e26b..4cb19ce 100644 (file)
@@ -21,3 +21,9 @@ withselectedtags,core_tag
 tag:create,core_role
 categoriesanditems,core_grades
 taggedwith,core_tag
+officialtag,core_tag
+otags,core_tag
+othertags,core_tag
+tagtype,core_tag
+manageofficialtags,core_tag
+settypeofficial,core_tag
index a5248d1..d34cda2 100644 (file)
@@ -123,6 +123,7 @@ $string['cannotreadfile'] = 'Cannot read file ({$a})';
 $string['cannotreadtmpfile'] = 'Error reading temporary file';
 $string['cannotreaduploadfile'] = 'Could not read uploaded file';
 $string['cannotremovefrommeta'] = 'Could not remove the selected course from this meta course!';
+$string['cannotreprocessgrades'] = 'Could not reprocess grades for this activity {$a}';
 $string['cannotresetguestpwd'] = 'You cannot reset the guest password';
 $string['cannotresetmail'] = 'Error resetting password and mailing you';
 $string['cannotresetthisrole'] = 'Cannot reset this role';
index 6bbb927..4089b5e 100644 (file)
@@ -472,11 +472,23 @@ $string['minimum_show_help'] = 'Minimum grade is used in calculating grades and
 $string['missingitemtypeoreid'] = 'Array key (itemtype or eid) missing from 2nd param of grade_edit_tree_column_select::get_item_cell($item, $params)';
 $string['missingscale'] = 'Scale must be selected';
 $string['mode'] = 'Mode';
-$string['modgradeerrorbadpoint'] = 'Invalid Grade Value. This must be an integer between 1 and {$a}';
-$string['modgradeerrorbadscale'] = 'Invalid scale selected. Please make sure you select a scale from the selections below.';
 $string['modgrade'] = 'Grade';
 $string['modgrade_help'] = 'Select the type of grading used for this activity. If "scale" is chosen, you can then choose the scale from the "scale" dropdown. If using "point" grading, you can then enter the maximum grade available for this activity.';
-$string['modgrademaxgrade'] = 'Maximum points';
+$string['modgradecantchangegradetype'] = 'You cannot change the type, as grades already exist for this item.';
+$string['modgradecantchangegradetypemsg'] = 'Some grades have already been awarded, so the grade type cannot be changed. If you wish to change the maximum grade, you must first choose whether or not to rescale existing grades.';
+$string['modgradecantchangegradetyporscalemsg'] = 'Some grades have already been awarded, so the grade type and scale cannot be changed.';
+$string['modgradecantchangescale'] = 'You cannot change the scale, as grades already exist for this item.';
+$string['modgradecantchangeratingmaxgrade'] = 'You cannot change the maximum grade when grades already exist for an activity with ratings.';
+$string['modgradedonotmodify'] = 'Do not modify existing grades';
+$string['modgradeerrorbadpoint'] = 'Invalid grade value. This must be an integer between 1 and {$a}';
+$string['modgradeerrorbadscale'] = 'Invalid scale selected. Please make sure you select a scale from the selections below.';
+$string['modgrademaxgrade'] = 'Maximum grade';
+$string['modgraderescalegrades'] = 'Rescale existing grades';
+$string['modgraderescalegrades_help'] = 'When changing the maximum grades on a gradebook item you need to specify whether or not this will cause existing percentage grades to change as well.
+
+If this is set to \'Yes\', any existing grades will be rescaled so that the percentage grade remains the same.
+
+For example, if this option is set to \'Yes\', changing the maximum grade on an item from 10 to 20 would cause a grade of 6/10 (60%) to be rescaled to 12/20 (60%). With this option set to \'No\', the grade would change from 6/10 (60%) to 6/20 (30%), requiring manual adjustment of the grade items to ensure correct scores.';
 $string['modgradetype'] = 'Type';
 $string['modgradetypenone'] = 'None';
 $string['modgradetypepoint'] = 'Point';
@@ -491,6 +503,7 @@ $string['mygrades'] = 'User menu grades link';
 $string['mygrades_desc'] = 'This setting allows for the option of linking to an external gradebook from the user menu.';
 $string['mypreferences'] = 'My preferences';
 $string['myreportpreferences'] = 'My report preferences';
+$string['mustchooserescaleyesorno'] = 'You must choose whether to rescale existing grades or not.';
 $string['navmethod'] = 'Navigation method';
 $string['neverdeletehistory'] = 'Never delete history';
 $string['newcategory'] = 'New category';
index f429e4c..e7eb45e 100644 (file)
@@ -86,8 +86,6 @@ $string['blog:associatemodule'] = 'This capability is deprecated and does nothin
 $string['blog:create'] = 'Create new blog entries';
 $string['blog:manageentries'] = 'Edit and manage entries';
 $string['blog:manageexternal'] = 'Edit and manage external blogs';
-$string['blog:manageofficialtags'] = 'Manage official tags';
-$string['blog:managepersonaltags'] = 'Manage personal tags';
 $string['blog:search'] = 'Search blog entries';
 $string['blog:view'] = 'View blog entries';
 $string['blog:viewdrafts'] = 'View draft blog entries';
index e20a603..5d576fd 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['added'] = 'Official tag(s) added';
-$string['addotags'] = 'Add official tags';
+$string['added'] = 'Standard tag(s) added';
+$string['addotags'] = 'Add standard tags';
 $string['addtagcoll'] = 'Add tag collection';
 $string['addtagtomyinterests'] = 'Add "{$a}" to my interests';
 $string['alltagpages'] = 'All tag pages';
 $string['backtoallitems'] = 'Back to all items tagged with "{$a}"';
+$string['changeshowstandard'] = 'Change showing standard tags in area {$a}';
 $string['changessaved'] = 'Changes saved';
 $string['changetagcoll'] = 'Change tag collection of area {$a}';
 $string['collnameexplained'] = 'Leave the field empty to use the default value: {$a}';
@@ -69,7 +70,7 @@ $string['id'] = 'id';
 $string['inalltagcoll'] = 'Everywhere';
 $string['itemstaggedwith'] = '{$a->tagarea} tagged with "{$a->tag}"';
 $string['lesstags'] = 'less...';
-$string['manageofficialtags'] = 'Manage official tags';
+$string['managestandardtags'] = 'Manage standard tags';
 $string['managetags'] = 'Manage tags';
 $string['managetagcolls'] = 'Manage tag collections';
 $string['moretags'] = 'more...';
@@ -80,9 +81,6 @@ $string['nextpage'] = 'More';
 $string['notagsfound'] = 'No tags matching "{$a}" found';
 $string['noresultsfor'] = 'No results for "{$a}"';
 $string['nothingtoupdate'] = 'Nothing to update';
-$string['officialtag'] = 'Official';
-$string['otags'] = 'Official tags';
-$string['othertags'] = 'Other tags';
 $string['owner'] = 'Owner';
 $string['prevpage'] = 'Back';
 $string['ptags'] = 'User defined tags (Comma separated)';
@@ -94,6 +92,8 @@ $string['resetflag'] = 'Reset flag';
 $string['responsiblewillbenotified'] = 'The person responsible will be notified';
 $string['rssdesc'] = 'This RSS feed was automatically generated by Moodle and contains user generated tags for courses.';
 $string['rsstitle'] = 'Course tags RSS feed for user: {$a}';
+$string['showstandard'] = 'Standard tags usage';
+$string['showstandard_help'] = 'This controls how area handles standard tags. When user edits the item the standard tags may be suggested or not, it is also possible to force area to only use standard tags and not allow user to type new ones.';
 $string['search'] = 'Search';
 $string['searchable'] = 'Searchable';
 $string['searchable_help'] = 'Tags in this tag collection can be searched for on "Search tags" page. If unchecked, tags can still be accessed by clicking on them or via different search interfaces.';
@@ -103,9 +103,13 @@ $string['seeallblogs'] = 'See all blog entries tagged with "{$a}"';
 $string['select'] = 'Select';
 $string['selectcoll'] = 'Select tag collection';
 $string['selecttag'] = 'Select tag {$a}';
-$string['settypedefault'] = 'Remove from official tags';
-$string['settypeofficial'] = 'Make official';
+$string['settypedefault'] = 'Remove from standard tags';
+$string['settypestandard'] = 'Make standard';
 $string['showingfirsttags'] = 'Showing {$a} most popular tags';
+$string['standardforce'] = 'Force';
+$string['standardhide'] = 'Don\'t suggest';
+$string['standardsuggest'] = 'Suggest';
+$string['standardtag'] = 'Standard';
 $string['suredeletecoll'] = 'Are you sure you want to delete tag collection "{$a}"?';
 $string['tag'] = 'Tag';
 $string['tagarea_blog_external'] = 'External blog posts';
@@ -116,11 +120,11 @@ $string['tagareaenabled'] = 'Enabled';
 $string['tagareaname'] = 'Name';
 $string['tagareas'] = 'Tag areas';
 $string['tagcollection'] = 'Tag collection';
+$string['tagcollection_help'] = 'Tag collections are sets of tags for different areas. For example, a collection of standard tags can be used to tag courses, with user interests and blog post tags kept in a separate collection. When a user clicks on a tag, the tag page displays only items with that tag in the same collection. Tags can be automatically added to a collection according to the area tagged or can be added manually as standard tags.';
 $string['tagcollections'] = 'Tag collections';
 $string['tagdescription'] = 'Tag description';
 $string['tags'] = 'Tags';
 $string['tagsaredisabled'] = 'Tags are disabled';
-$string['tagtype'] = 'Tag type';
 $string['thingstaggedwith'] = '"{$a->name}" is used {$a->count} times';
 $string['thingtaggedwith'] = '"{$a->name}" is used once';
 $string['timemodified'] = 'Modified';
@@ -143,4 +147,10 @@ $string['withselectedtags'] = 'With selected tags...';
 
 // Deprecated since 3.1 .
 
+$string['manageofficialtags'] = 'Manage official tags';
+$string['officialtag'] = 'Official';
+$string['otags'] = 'Official tags';
+$string['othertags'] = 'Other tags';
+$string['settypeofficial'] = 'Make official';
 $string['taggedwith'] = 'tagged with "{$a}"';
+$string['tagtype'] = 'Tag type';
diff --git a/lib/amd/build/fragment.min.js b/lib/amd/build/fragment.min.js
new file mode 100644 (file)
index 0000000..89a6952
Binary files /dev/null and b/lib/amd/build/fragment.min.js differ
index 7a4328f..e570801 100644 (file)
Binary files a/lib/amd/build/tag.min.js and b/lib/amd/build/tag.min.js differ
diff --git a/lib/amd/build/tree.min.js b/lib/amd/build/tree.min.js
new file mode 100644 (file)
index 0000000..837d4dc
Binary files /dev/null and b/lib/amd/build/tree.min.js differ
diff --git a/lib/amd/src/fragment.js b/lib/amd/src/fragment.js
new file mode 100644 (file)
index 0000000..92d7e6d
--- /dev/null
@@ -0,0 +1,118 @@
+// 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/>.
+
+/**
+ * A way to call HTML fragments to be inserted as required via JavaScript.
+ *
+ * @module     core/fragment
+ * @class      fragment
+ * @package    core
+ * @copyright  2016 Adrian Greeve <adrian@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since      3.1
+ */
+define(['jquery', 'core/ajax', 'core/notification'], function($, ajax, notification) {
+
+    /**
+     * Loads an HTML fragment through a callback.
+     *
+     * @method loadFragment
+     * @param {string} component Component where callback is located.
+     * @param {string} callback Callback function name.
+     * @param {integer} contextid Context ID of the fragment.
+     * @param {object} params Parameters for the callback.
+     * @return {Promise} JQuery promise object resolved when the fragment has been loaded.
+     */
+    var loadFragment = function(component, callback, contextid, params) {
+        // Change params into required webservice format.
+        var formattedparams = [];
+        for (var index in params) {
+            formattedparams.push({name: index, value: params[index]});
+        }
+
+        // Ajax stuff.
+        var deferred = $.Deferred();
+
+        var promises = ajax.call([{
+            methodname: 'core_get_fragment',
+            args:{
+                component: component,
+                callback: callback,
+                contextid: contextid,
+                args: formattedparams
+            }
+        }], false);
+
+        promises[0].done(function(data) {
+            deferred.resolve(data);
+        }).fail(function(ex) {
+            deferred.reject(ex);
+        });
+        return deferred.promise();
+    };
+
+    /**
+     * Removes and cleans children of a node. This includes event handlers and listeners that may be
+     * attached to the nodes for both jquery and yui.
+     *
+     * @method recursiveCleanup
+     * @param {object} DOM node to be cleaned.
+     * @return {void}
+     */
+    var recursiveCleanup = function(node) {
+        node.children().each(function(index, el) {
+            var child = $(el);
+            recursiveCleanup(child);
+        });
+        var yuinode = new Y.Node(node);
+        node.empty();
+        node.remove();
+        yuinode.detachAll();
+        if (yuinode.get('childNodes')) {
+            yuinode.empty();
+        }
+        yuinode.remove(true);
+    };
+
+    return /** @alias module:core/fragment */{
+        /**
+         * Appends HTML and JavaScript fragments to specified nodes.
+         * Callbacks called by this AMD module are responsible for doing the appropriate security checks
+         * to access the information that is returned. This only does minimal validation on the context.
+         *
+         * @method fragmentAppend
+         * @param {string} component Component where callback is located.
+         * @param {string} callback Callback function name.
+         * @param {integer} contextid Context ID of the fragment.
+         * @param {object} params Parameters for the callback.
+         * @param {string} htmlnodeidentifier The 'class' or 'id' to attach the HTML.
+         * @param {string} javascriptnodeidentifier The 'class' or 'id' to attach the JavaScript.
+         * @return {void}
+         */
+        fragmentAppend: function(component, callback, contextid, params, htmlnodeidentifier, javascriptnodeidentifier) {
+            $.when(loadFragment(component, callback, contextid, params)).then(function(data) {
+                // Clean up previous code if found first.
+                recursiveCleanup($('#fragment-html'));
+                recursiveCleanup($('#fragment-scripts'));
+                // Attach new HTML and JavaScript.
+                $(htmlnodeidentifier).append('<div id="fragment-html">' + data.html + '</div>');
+                $(javascriptnodeidentifier).append('<div id="fragment-scripts">' + data.javascript + '</div>');
+
+            }).fail(function(ex) {
+                notification.exception(ex);
+            });
+        }
+    };
+});
index 01ef814..1a0a94e 100644 (file)
@@ -72,16 +72,16 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
             };
 
             // Click handler for changing tag type.
-            $('.tag-management-table').delegate('.tagtype', 'click', function(e) {
+            $('.tag-management-table').delegate('.tagisstandard', 'click', function(e) {
                 e.preventDefault();
                 var target = $( this ),
                     tagid = target.attr('data-id'),
                     currentvalue = target.attr('data-value'),
-                    official = (currentvalue === "1") ? 0 : 1;
+                    isstandard = (currentvalue === "1") ? 0 : 1;
 
                 var promises = ajax.call([{
                     methodname: 'core_tag_update_tags',
-                    args: { tags : [ { id : tagid , official : official } ] }
+                    args: { tags : [ { id : tagid , isstandard : isstandard } ] }
                 }, {
                     methodname: 'core_tag_get_tags',
                     args: { tags : [ { id : tagid } ] }
@@ -90,11 +90,11 @@ define(['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str'
                 $.when.apply($, promises)
                     .done( function(updateresult, data) {
                         if (updateresult.warnings[0] === undefined && data.tags[0] !== undefined) {
-                            templates.render('core_tag/tagtype', data.tags[0]).done(function(html) {
+                            templates.render('core_tag/tagisstandard', data.tags[0]).done(function(html) {
                                 update_modified(target);
                                 var parent = target.parent();
                                 target.replaceWith(html);
-                                parent.find('.tagtype').get(0).focus();
+                                parent.find('.tagisstandard').get(0).focus();
                             });
                         }
                     });
diff --git a/lib/amd/src/tree.js b/lib/amd/src/tree.js
new file mode 100644 (file)
index 0000000..195258f
--- /dev/null
@@ -0,0 +1,496 @@
+// 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/>.
+
+/**
+ * Implement an accessible aria tree widget, from a nested unordered list.
+ * Based on http://oaa-accessibility.org/example/41/.
+ *
+ * @module     tool_lp/tree
+ * @package    core
+ * @copyright  2015 Damyon Wiese <damyon@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery'], function($) {
+    // Private variables and functions.
+    var SELECTORS = {
+        ITEM: '[role=treeitem]',
+        GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][data-requires-ajax=true]',
+        CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem]' +
+                '[data-requires-ajax=true][aria-expanded=false]',
+        FIRST_ITEM: '[role=treeitem]:first',
+        VISIBLE_ITEM: '[role=treeitem]:visible',
+        UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'
+    };
+
+    /**
+     * Constructor.
+     *
+     * @param {String} selector
+     * @param {function} selectCallback Called when the active node is changed.
+     */
+    var Tree = function(selector, selectCallback) {
+        this.treeRoot = $(selector);
+
+        this.treeRoot.data('activeItem', null);
+        this.selectCallback = selectCallback;
+        this.keys = {
+            tab:      9,
+            enter:    13,
+            space:    32,
+            pageup:   33,
+            pagedown: 34,
+            end:      35,
+            home:     36,
+            left:     37,
+            up:       38,
+            right:    39,
+            down:     40,
+            asterisk: 106
+        };
+
+        // Apply the standard default initialisation for all nodes, starting with the tree root.
+        this.initialiseNodes(this.treeRoot);
+        // Make the first item the active item for the tree so that it is added to the tab order.
+        this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));
+        // Create the cache of the visible items.
+        this.refreshVisibleItemsCache();
+        // Create the event handlers for the tree.
+        this.bindEventHandlers();
+    };
+
+    /**
+     * Find all visible tree items and save a cache of them on the tree object.
+     *
+     * @method refreshVisibleItemsCache
+     */
+    Tree.prototype.refreshVisibleItemsCache = function() {
+        this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));
+    };
+
+    /**
+     * Get all visible tree items.
+     *
+     * @method getVisibleItems
+     */
+    Tree.prototype.getVisibleItems = function() {
+        return this.treeRoot.data('visibleItems');
+    };
+
+    /**
+     * Mark the given item as active within the tree and fire the callback for when the active item is set.
+     *
+     * @method setActiveItem
+     * @param {object} item jquery object representing an item on the tree.
+     */
+    Tree.prototype.setActiveItem = function(item) {
+        var currentActive = this.treeRoot.data('activeItem');
+        if (item === currentActive) {
+            return;
+        }
+
+        // Remove previous active from tab order.
+        if (currentActive) {
+            currentActive.attr('tabindex', '-1');
+            currentActive.attr('aria-selected', 'false');
+        }
+        item.attr('tabindex', '0');
+        item.attr('aria-selected', 'true');
+
+        // Set the new active item.
+        this.treeRoot.data('activeItem', item);
+
+        if (typeof this.selectCallback === 'function') {
+            this.selectCallback(item);
+        }
+    };
+
+    /**
+     * Determines if the given item is a group item (contains child tree items) in the tree.
+     *
+     * @method isGroupItem
+     * @param {object} item jquery object representing an item on the tree.
+     * @returns {bool}
+     */
+    Tree.prototype.isGroupItem = function(item) {
+        return item.is(SELECTORS.GROUP);
+    };
+
+    /**
+     * Determines if the given group item (contains child tree items) is collapsed.
+     *
+     * @method isGroupCollapsed
+     * @param {object} item jquery object representing a group item on the tree.
+     * @returns {bool}
+     */
+    Tree.prototype.isGroupCollapsed = function(item) {
+        return item.attr('aria-expanded') === 'false';
+    };
+
+    /**
+     * Determines if the given group item (contains child tree items) can be collapsed.
+     *
+     * @method isGroupCollapsible
+     * @param {object} item jquery object representing a group item on the tree.
+     * @returns {bool}
+     */
+    Tree.prototype.isGroupCollapsible = function(item) {
+        return item.attr('data-collapsible') !== 'false';
+    };
+
+    /**
+     * Performs the tree initialisation for all child items from the given node,
+     * such as removing everything from the tab order and setting aria selected
+     * on items.
+     *
+     * @method initialiseNodes
+     * @param {object} node jquery object representing a node.
+     */
+    Tree.prototype.initialiseNodes = function(node) {
+        this.removeAllFromTabOrder(node);
+        this.setAriaSelectedFalseOnItems(node);
+
+        // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.
+        var thisTree = this;
+        node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {
+            var unloadedNode = $(this);
+            // Collapse and then expand to trigger the ajax loading.
+            thisTree.collapseGroup(unloadedNode);
+            thisTree.expandGroup(unloadedNode);
+        });
+    };
+
+    /**
+     * Removes all child DOM elements of the given node from the tab order.
+     *
+     * @method removeAllFromTabOrder
+     * @param {object} node jquery object representing a node.
+     */
+    Tree.prototype.removeAllFromTabOrder = function(node) {
+        node.find('*').attr('tabindex', '-1');
+    };
+
+    /**
+     * Find all child tree items from the given node and set the aria selected attribute to false.
+     *
+     * @method setAriaSelectedFalseOnItems
+     * @param {object} node jquery object representing a node.
+     */
+    Tree.prototype.setAriaSelectedFalseOnItems = function(node) {
+        node.find(SELECTORS.ITEM).attr('aria-selected', 'false');
+    };
+
+    /**
+     * Expand all group nodes within the tree.
+     *
+     * @method expandAllGroups
+     */
+    Tree.prototype.expandAllGroups = function() {
+        this.expandAllChildGroups(this.treeRoot);
+    };
+
+    /**
+     * Find all child group nodes from the given node and expand them.
+     *
+     * @method expandAllChildGroups
+     * @param {object} node jquery object representing a node.
+     */
+    Tree.prototype.expandAllChildGroups = function(node) {
+        var thisTree = this;
+
+        node.find(SELECTORS.CLOSED_GROUP).each(function() {
+            var childNode = $(this);
+            thisTree.expandGroup(childNode).done(function() {
+                thisTree.expandAllChildGroups(childNode);
+            });
+        });
+    };
+
+    /**
+     * Expand a collapsed group.
+     *
+     * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).
+     *
+     * @method expandGroup
+     * @param {Object} item is the jquery id of the parent item of the group.
+     * @return {Object} a promise that is resolved when the group has been expanded.
+     */
+    Tree.prototype.expandGroup = function(item) {
+        var promise = $.Deferred();
+        // Ignore nodes that are explicitly maked as not expandable or are already expanded.
+        if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) {
+            // If this node requires ajax load and we haven't already loaded it.
+            if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') {
+                item.attr('data-loaded', false);
+                // Get the closes ajax loading module specificed in the tree.
+                var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader');
+                var thisTree = this;
+                // Flag this node as loading.
+                item.addClass('loading');
+                // Require the ajax module (must be AMD) and try to load the items.
+                require([moduleName], function(loader) {
+                    // All ajax module must implement a "load" method.
+                    loader.load(item).done(function() {
+                        item.attr('data-loaded', true);
+
+                        // Set defaults on the newly constructed part of the tree.
+                        thisTree.initialiseNodes(item);
+                        thisTree.finishExpandingGroup(item);
+                        // Make sure no child elements of the item we just loaded are tabbable.
+                        item.removeClass('loading');
+                        promise.resolve();
+                    });
+                });
+            } else {
+                this.finishExpandingGroup(item);
+                promise.resolve();
+            }
+        } else {
+            promise.resolve();
+        }
+        return promise;
+    };
+
+    /**
+     * Perform the necessary DOM changes to display a group item.
+     *
+     * @method finishExpandingGroup
+     * @param {Object} item is the jquery id of the parent item of the group.
+     */
+    Tree.prototype.finishExpandingGroup = function(item) {
+        // Find the first child node.
+        var group = item.children(SELECTORS.GROUP);
+
+        // Expand the group.
+        group.show().attr('aria-hidden', 'false');
+
+        item.attr('aria-expanded', 'true');
+
+        // Update the list of visible items.
+        this.refreshVisibleItemsCache();
+    };
+
+    /**
+     * Collapse an expanded group.
+     *
+     * @method collapseGroup
+     * @param {Object} item is the jquery id of the parent item of the group.
+     */
+    Tree.prototype.collapseGroup = function(item) {
+        // If the item is not collapsible or already collapsed then do nothing.
+        if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) {
+            return;
+        }
+
+        // Get and collapse the group.
+        var group = item.children(SELECTORS.GROUP);
+        group.hide().attr('aria-hidden', 'true');
+        item.attr('aria-expanded', 'false');
+
+        // Update the list of visible items.
+        this.refreshVisibleItemsCache();
+    };
+
+    /**
+     * Expand or collapse a group.
+     *
+     * @method toggleGroup
+     * @param {Object} item is the jquery id of the parent item of the group.
+     */
+    Tree.prototype.toggleGroup = function(item) {
+        if (item.attr('aria-expanded') === 'true') {
+            this.collapseGroup(item);
+        } else {
+            this.expandGroup(item);
+        }
+    };
+
+    /**
+     * Handle a key down event - ie navigate the tree.
+     *
+     * @method handleKeyDown
+     * @param {Object} item is the jquery id of the parent item of the group.
+     * @param {Event} e The event.
+     */
+    Tree.prototype.handleKeyDown = function(item, e) {
+        var currentIndex = this.getVisibleItems().index(item);
+
+        if ((e.altKey || e.ctrlKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {
+            // Do nothing.
+            return true;
+        }
+
+        switch (e.keyCode) {
+            case this.keys.home: {
+                // Jump to first item in tree.
+                this.getVisibleItems().first().focus();
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.end: {
+                // Jump to last visible item.
+                this.getVisibleItems().last().focus();
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.enter: {
+                var links = item.children().not(SELECTORS.GROUP).children('a');
+                if (links.length) {
+                    window.location.href = links.first().attr('href');
+                } else if (this.isGroupItem(item)) {
+                    this.toggleGroup(item, true);
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.space: {
+                if (this.isGroupItem(item)) {
+                    this.toggleGroup(item, true);
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.left: {
+                var focusParent = function(tree) {
+                    // Get the immediate visible parent group item that contains this element.
+                    var parentGroup = tree.getVisibleItems().filter(SELECTORS.GROUP).has(item).last();
+                    parentGroup.focus();
+                };
+
+                // If this is a goup item then collapse it and focus the parent group
+                // in accordance with the aria spec.
+                if (this.isGroupItem(item)) {
+                    if (this.isGroupCollapsed(item)) {
+                        focusParent(this);
+                    } else {
+                        this.collapseGroup(item);
+                    }
+                } else {
+                    focusParent(this);
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.right: {
+                // If this is a group item then expand it and focus the first child item
+                // in accordance with the aria spec.
+                if (this.isGroupItem(item)) {
+                    if (this.isGroupCollapsed(item)) {
+                        this.expandGroup(item);
+                    } else {
+                        // Move to the first item in the child group.
+                        item.find(SELECTORS.ITEM).first().focus();
+                    }
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.up: {
+
+                if (currentIndex > 0) {
+                    var prev = this.getVisibleItems().eq(currentIndex - 1);
+
+                    prev.focus();
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.down: {
+
+                if (currentIndex < this.getVisibleItems().length - 1) {
+                    var next = this.getVisibleItems().eq(currentIndex + 1);
+
+                    next.focus();
+                }
+
+                e.stopPropagation();
+                return false;
+            }
+            case this.keys.asterisk: {
+                // Expand all groups.
+                this.expandAllGroups();
+                e.stopPropagation();
+                return false;
+            }
+        }
+        return true;
+    };
+
+    /**
+     * Handle a click (select).
+     *
+     * @method handleClick
+     * @param {Object} item The jquery id of the parent item of the group.
+     * @param {Event} e The event.
+     */
+    Tree.prototype.handleClick = function(item, e) {
+
+        if (e.altKey || e.ctrlKey || e.shiftKey) {
+            // Do nothing.
+            return true;
+        }
+
+        // Update the active item.
+        item.focus();
+
+        // If the item is a group node.
+        if (this.isGroupItem(item)) {
+            this.toggleGroup(item);
+        }
+
+        e.stopPropagation();
+        return true;
+    };
+
+    /**
+     * Handle a focus event.
+     *
+     * @method handleFocus
+     * @param {Object} item The jquery id of the parent item of the group.
+     * @param {Event} e The event.
+     */
+    Tree.prototype.handleFocus = function(item, e) {
+
+        this.setActiveItem(item);
+
+        e.stopPropagation();
+        return true;
+    };
+
+    /**
+     * Bind the event listeners we require.
+     *
+     * @method bindEventHandlers
+     */
+    Tree.prototype.bindEventHandlers = function() {
+        var thisObj = this;
+
+        // Bind event handlers to the tree items. Use event delegates to allow
+        // for dynamically loaded parts of the tree.
+        this.treeRoot.on({
+            click: function(e) { return thisObj.handleClick($(this), e); },
+            keydown: function(e) { return thisObj.handleKeyDown($(this), e); },
+            focus: function(e) { return thisObj.handleFocus($(this), e); },
+        }, SELECTORS.ITEM);
+    };
+
+    return /** @alias module:tool_lp/tree */ Tree;
+});
index c2dac9a..efe954b 100644 (file)
@@ -74,7 +74,7 @@ class behat_base extends Behat\MinkExtension\Context\RawMinkContext {
     /**
      * The JS code to check that the page is ready.
      */
-    const PAGE_READY_JS = '(M && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
+    const PAGE_READY_JS = '(typeof M !== "undefined" && M.util && M.util.pending_js && !Boolean(M.util.pending_js.length)) && (document.readyState === "complete")';
 
     /**
      * Locates url, based on provided path.
index b045eb2..595cf3b 100644 (file)
@@ -1093,38 +1093,36 @@ class block_manager {
         }
 
         // Display either "Assign roles" or "Permissions" or "Change permissions" icon (whichever first is available).
-        if ($this->page->pagetype != 'my-index') {
-            $rolesurl = null;
-
-            if (get_assignable_roles($block->context, ROLENAME_SHORT)) {
-                $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id));
-                $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
-                $icon = 'i/assignroles';
-            } else if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {
-                $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id));
-                $str = get_string('permissions', 'role');
-                $icon = 'i/permissions';
-            } else if (has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {
-                $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id));
-                $str = get_string('checkpermissions', 'role');
-                $icon = 'i/checkpermissions';
-            }
+        $rolesurl = null;
+
+        if (get_assignable_roles($block->context, ROLENAME_SHORT)) {
+            $rolesurl = new moodle_url('/admin/roles/assign.php', array('contextid' => $block->context->id));
+            $str = new lang_string('assignrolesinblock', 'block', $blocktitle);
+            $icon = 'i/assignroles';
+        } else if (has_capability('moodle/role:review', $block->context) or get_overridable_roles($block->context)) {
+            $rolesurl = new moodle_url('/admin/roles/permissions.php', array('contextid' => $block->context->id));
+            $str = get_string('permissions', 'role');
+            $icon = 'i/permissions';
+        } else if (has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override', 'moodle/role:assign'), $block->context)) {
+            $rolesurl = new moodle_url('/admin/roles/check.php', array('contextid' => $block->context->id));
+            $str = get_string('checkpermissions', 'role');
+            $icon = 'i/checkpermissions';
+        }
+
+        if ($rolesurl) {
+            // TODO: please note it is sloppy to pass urls through page parameters!!
+            //      it is shortened because some web servers (e.g. IIS by default) give
+            //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
+            $return = $this->page->url->out(false);
+            $return = str_replace($CFG->wwwroot . '/', '', $return);
+            $rolesurl->param('returnurl', $return);
 
-            if ($rolesurl) {
-                //TODO: please note it is sloppy to pass urls through page parameters!!
-                //      it is shortened because some web servers (e.g. IIS by default) give
-                //      a 'security' error if you try to pass a full URL as a GET parameter in another URL.
-                $return = $this->page->url->out(false);
-                $return = str_replace($CFG->wwwroot . '/', '', $return);
-                $rolesurl->param('returnurl', $return);
-
-                $controls[] = new action_menu_link_secondary(
-                    $rolesurl,
-                    new pix_icon($icon, $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                    $str,
-                    array('class' => 'editing_roles')
-                );
-            }
+            $controls[] = new action_menu_link_secondary(
+                $rolesurl,
+                new pix_icon($icon, $str, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                $str,
+                array('class' => 'editing_roles')
+            );
         }
 
         if ($this->user_can_delete_block($block)) {
index 1444eff..1059ec5 100644 (file)
@@ -23,6 +23,8 @@
  */
 namespace core\plugininfo;
 
+use admin_settingpage;
+
 defined('MOODLE_INTERNAL') || die();
 
 /**
@@ -62,4 +64,46 @@ class availability extends base {
     public function is_uninstall_allowed() {
         return true;
     }
+
+    /**
+     * Get the name for the settings section.
+     *
+     * @return string
+     */
+    public function get_settings_section_name() {
+        return 'availabilitysetting' . $this->name;
+    }
+
+    /**
+     * Load the global settings for a particular availability plugin (if there are any)
+     *
+     * @param \part_of_admin_tree $adminroot
+     * @param string $parentnodename
+     * @param bool $hassiteconfig
+     */
+    public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
+        global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
+        $ADMIN = $adminroot; // May be used in settings.php.
+        $plugininfo = $this; // Also can be used inside settings.php
+        $availability = $this; // Also to be used inside settings.php.
+
+        if (!$this->is_installed_and_upgraded()) {
+            return;
+        }
+
+        if (!$hassiteconfig) {
+            return;
+        }
+
+        $section = $this->get_settings_section_name();
+
+        $settings = null;
+        if (file_exists($this->full_path('settings.php'))) {
+            $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false);
+            include($this->full_path('settings.php')); // This may also set $settings to null.
+        }
+        if ($settings) {
+            $ADMIN->add($parentnodename, $settings);
+        }
+    }
 }
index 0fb84b8..dda16f7 100644 (file)
@@ -59,6 +59,10 @@ class delete_incomplete_users_task extends scheduled_task {
                 if (isguestuser($user) or is_siteadmin($user)) {
                     continue;
                 }
+                if ($user->lastname !== '' and $user->firstname !== '' and $user->email !== '') {
+                    // This can happen on MySQL - see MDL-52831.
+                    continue;
+                }
                 delete_user($user);
                 mtrace(" Deleted not fully setup user $user->username ($user->id)");
             }
index 3c9e420..c8cda52 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20160111" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20160202" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
         <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="itemtype" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="enabled" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
+        <FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="1" SEQUENCE="false"/>
         <FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="callback" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="callbackfile" TYPE="char" LENGTH="100" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="showstandard" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
         <FIELD NAME="tagcollid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="name" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="rawname" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="The raw, unnormalised name for the tag as entered by users"/>
-        <FIELD NAME="tagtype" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
+        <FIELD NAME="isstandard" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether this tag is standard"/>
         <FIELD NAME="description" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="descriptionformat" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="flag" TYPE="int" LENGTH="4" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="a tag can be 'flagged' as inappropriate"/>
       </KEYS>
       <INDEXES>
         <INDEX NAME="tagcollname" UNIQUE="true" FIELDS="tagcollid, name"/>
-        <INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, tagtype"/>
+        <INDEX NAME="tagcolltype" UNIQUE="false" FIELDS="tagcollid, isstandard"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="tag_correlation" COMMENT="The rationale for the 'tag_correlation' table is performance.   It works as a cache for a potentially heavy load query done at the 'tag_instance' table.   So, the 'tag_correlation' table stores redundant information derived from the 'tag_instance' table">
index bc3230f..f422c5d 100644 (file)
@@ -1051,6 +1051,15 @@ $functions = array(
         'ajax'        => true,
     ),
 
+    'core_get_fragment' => array(
+        'classname'   => 'core_external',
+        'methodname'  => 'get_fragment',
+        'classpath'   => 'lib/external/externallib.php',
+        'description' => 'Return a fragment for inclusion, such as a JavaScript page.',
+        'type'        => 'read',
+        'ajax'        => true,
+    ),
+
 
     // === Calendar related functions ===
 
index 2923bb5..074b685 100644 (file)
@@ -28,6 +28,8 @@
  *     any other tag areas to this collection nor move this tag area elsewhere
  *   - searchable (only if collection is specified) - wether the tag collection
  *     should be searchable on /tag/search.php
+ *   - showstandard - default value for the "Standard tags" attribute of the area,
+ *     this is only respected when new tag area is added and ignored during upgrade
  *   - customurl (only if collection is specified) - custom url to use instead of
  *     /tag/search.php to display information about one tag
  *   - callback - name of the function that returns items tagged with this tag,
@@ -56,6 +58,7 @@ $tagareas = array(
         'component' => 'core',
         'callback' => 'user_get_tagged_users',
         'callbackfile' => '/user/lib.php',
+        'showstandard' => core_tag_tag::HIDE_STANDARD,
     ),
     array(
         'itemtype' => 'course', // Courses.
index 3975de3..7e6ca5c 100644 (file)
@@ -4849,5 +4849,72 @@ function xmldb_main_upgrade($oldversion) {
 
         upgrade_main_savepoint(true, 2016011901.00);
     }
+
+    if ($oldversion < 2016020200.00) {
+
+        // Define field isstandard to be added to tag.
+        $table = new xmldb_table('tag');
+        $field = new xmldb_field('isstandard', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'rawname');
+
+        // Conditionally launch add field isstandard.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Define index tagcolltype (not unique) to be dropped form tag.
+        $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'tagtype'));
+
+        // Conditionally launch drop index tagcolltype.
+        if ($dbman->index_exists($table, $index)) {
+            $dbman->drop_index($table, $index);
+        }
+
+        // Define index tagcolltype (not unique) to be added to tag.
+        $index = new xmldb_index('tagcolltype', XMLDB_INDEX_NOTUNIQUE, array('tagcollid', 'isstandard'));
+
+        // Conditionally launch add index tagcolltype.
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Define field tagtype to be dropped from tag.
+        $field = new xmldb_field('tagtype');
+
+        // Conditionally launch drop field tagtype and update isstandard.
+        if ($dbman->field_exists($table, $field)) {
+            $DB->execute("UPDATE {tag} SET isstandard=(CASE WHEN (tagtype = ?) THEN 1 ELSE 0 END)", array('official'));
+            $dbman->drop_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2016020200.00);
+    }
+
+    if ($oldversion < 2016020201.00) {
+
+        // Define field showstandard to be added to tag_area.
+        $table = new xmldb_table('tag_area');
+        $field = new xmldb_field('showstandard', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'callbackfile');
+
+        // Conditionally launch add field showstandard.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // By default set user area to hide standard tags. 2 = core_tag_tag::HIDE_STANDARD (can not use constant here).
+        $DB->execute("UPDATE {tag_area} SET showstandard = ? WHERE itemtype = ? AND component = ?",
+            array(2, 'user', 'core'));
+
+        // Changing precision of field enabled on table tag_area to (1).
+        $table = new xmldb_table('tag_area');
+        $field = new xmldb_field('enabled', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'itemtype');
+
+        // Launch change of precision for field enabled.
+        $dbman->change_field_precision($table, $field);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2016020201.00);
+    }
+
     return true;
 }
index 21c56f5..6222822 100644 (file)
@@ -2363,7 +2363,7 @@ function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $unus
 
     // get tags from the db ordered by highest count first
     $params = array();
-    $sql = "SELECT id as tkey, name, id, tagtype, rawname, f.timemodified, flag, count
+    $sql = "SELECT id as tkey, name, id, isstandard, rawname, f.timemodified, flag, count
               FROM {tag} t,
                  (SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
                     FROM {tag_instance}
@@ -2388,8 +2388,8 @@ function coursetag_get_tags($courseid, $userid=0, $tagtype='', $numtags=0, $unus
     $sql .= "   GROUP BY tagid) f
              WHERE t.id = f.tagid ";
     if ($tagtype != '') {
-        $sql .= "AND tagtype = :tagtype ";
-        $params['tagtype'] = $tagtype;
+        $sql .= "AND isstandard = :isstandard ";
+        $params['isstandard'] = ($tagtype === 'official') ? 1 : 0;
     }
     $sql .= "ORDER BY count DESC, name ASC";
 
@@ -2430,7 +2430,7 @@ function coursetag_get_all_tags($unused='', $numtags=0) {
     global $CFG, $DB;
 
     // note that this selects all tags except for courses that are not visible
-    $sql = "SELECT id, name, tagtype, rawname, f.timemodified, flag, count
+    $sql = "SELECT id, name, isstandard, rawname, f.timemodified, flag, count
         FROM {tag} t,
         (SELECT tagid, MAX(timemodified) as timemodified, COUNT(id) as count
             FROM {tag_instance} WHERE tagid NOT IN
@@ -2628,7 +2628,7 @@ function coursetag_delete_course_tags($courseid, $showfeedback=false) {
 function tag_type_set($tagid, $type) {
     debugging('Function tag_type_set() is deprecated and can be replaced with use core_tag_tag::get($tagid)->update().', DEBUG_DEVELOPER);
     if ($tag = core_tag_tag::get($tagid, '*')) {
-        return $tag->update(array('tagtype' => $type));
+        return $tag->update(array('isstandard' => ($type === 'official') ? 1 : 0));
     }
     return false;
 }
@@ -2666,8 +2666,9 @@ function tag_description_set($tagid, $description, $descriptionformat) {
 function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
     debugging('Method tag_get_tags() is deprecated and replaced with core_tag_tag::get_item_tags(). ' .
         'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
-    $official = ($type === 'official' ? true : (!empty($type) ? false : null));
-    $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $official, $userid);
+    $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+        (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
+    $tags = core_tag_tag::get_item_tags(null, $record_type, $record_id, $standardonly, $userid);
     $rv = array();
     foreach ($tags as $id => $t) {
         $rv[$id] = $t->to_object();
@@ -2688,8 +2689,9 @@ function tag_get_tags($record_type, $record_id, $type=null, $userid=0) {
 function tag_get_tags_array($record_type, $record_id, $type=null) {
     debugging('Method tag_get_tags_array() is deprecated and replaced with core_tag_tag::get_item_tags_array(). ' .
         'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
-    $official = ($type === 'official' ? true : (!empty($type) ? false : null));
-    return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official);
+    $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+        (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
+    return core_tag_tag::get_item_tags_array('', $record_type, $record_id, $standardonly);
 }
 
 /**
@@ -2710,11 +2712,12 @@ function tag_get_tags_csv($record_type, $record_id, $html=null, $type=null) {
     debugging('Method tag_get_tags_csv() is deprecated. Instead you should use either ' .
             'core_tag_tag::get_item_tags_array() or $OUTPUT->tag_list(core_tag_tag::get_item_tags()). ' .
         'Component is now required when retrieving tag instances.', DEBUG_DEVELOPER);
-    $official = ($type === 'official' ? true : (!empty($type) ? false : null));
+    $standardonly = ($type === 'official' ? core_tag_tag::STANDARD_ONLY :
+        (!empty($type) ? core_tag_tag::NOT_STANDARD_ONLY : core_tag_tag::BOTH_STANDARD_AND_NOT));
     if ($html != TAG_RETURN_TEXT) {
-        return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $official), '');
+        return $OUTPUT->tag_list(core_tag_tag::get_item_tags('', $record_type, $record_id, $standardonly), '');
     } else {
-        return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $official, 0, false));
+        return join(', ', core_tag_tag::get_item_tags_array('', $record_type, $record_id, $standardonly, 0, false));
     }
 }
 
@@ -2856,7 +2859,8 @@ function tag_add($tags, $type="default") {
     if (!is_array($tags)) {
         $tags = array($tags);
     }
-    $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags, $type === 'official');
+    $objects = core_tag_tag::create_if_missing(core_tag_collection::get_default(), $tags,
+            $type === 'official');
 
     // New function returns the tags in different format, for BC we keep the format that this function used to have.
     $rv = array();
@@ -3007,7 +3011,7 @@ function tag_print_cloud($tagset=null, $nr_of_tags=150, $return=false, $sort='')
     if (is_null($tagset)) {
         // No tag set received, so fetch tags from database.
         // Always add query by tagcollid even when it's not known to make use of the table index.
-        $tagcloud = core_tag_collection::get_tag_cloud(0, '', $nr_of_tags, $sort);
+        $tagcloud = core_tag_collection::get_tag_cloud(0, false, $nr_of_tags, $sort);
     } else {
         $tagsincloud = $tagset;
 
index e089c1c..09ab53f 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 f68b5f1..b6ff702 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 1a8ea70..bbc68c7 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 12086a3..e90b30a 100644 (file)
@@ -64,6 +64,16 @@ EditorSelection.prototype = {
      */
     _focusFromClick: false,
 
+    /**
+     * Whether if the last gesturemovestart event target was contained in this editor or not.
+     *
+     * @property _gesturestartededitor
+     * @type Boolean
+     * @default false
+     * @private
+     */
+    _gesturestartededitor: false,
+
     /**
      * Set up the watchers for selection save and restoration.
      *
@@ -94,13 +104,21 @@ EditorSelection.prototype = {
                 Y.soon(Y.bind(this._hasSelectionChanged, this, e));
             }, this);
 
-        // To capture both mouseup and touchend events, we need to track the gesturemoveend event in standAlone mode. Without
-        // standAlone, it will only fire if we listened to a gesturemovestart too.
-        this.editor.on('gesturemoveend', function(e) {
-                Y.soon(Y.bind(this._hasSelectionChanged, this, e));
-            }, {
-                standAlone: true
-            }, this);
+        Y.one(document.body).on('gesturemovestart', function(e) {
+            if (this._wrapper.contains(e.target._node)) {
+                this._gesturestartededitor = true;
+            } else {
+                this._gesturestartededitor = false;
+            }
+        }, null, this);
+
+        Y.one(document.body).on('gesturemoveend', function(e) {
+            if (!this._gesturestartededitor) {
+                // Ignore the event if movestart target was not contained in the editor.
+                return;
+            }
+            Y.soon(Y.bind(this._hasSelectionChanged, this, e));
+        }, null, this);
 
         return this;
     },
index b75f173..ff38252 100644 (file)
@@ -163,6 +163,8 @@ class MoodleExcelWorksheet {
         $name = strtr(trim($name, "'"), '[]*/\?:', '       ');
         // Shorten the title if necessary.
         $name = core_text::substr($name, 0, 31);
+        // After the substr, we might now have a single quote on the end.
+        $name = trim($name, "'");
 
         if ($name === '') {
             // Name is required!
index a0526c5..6114f99 100644 (file)
@@ -260,4 +260,90 @@ class core_external extends external_api {
                 'string' => new external_value(PARAM_RAW, 'translated string'))
             ));
     }
+
+    /**
+     * Returns description of get_fragment parameters
+     *
+     * @return external_function_parameters
+     * @since Moodle 3.1
+     */
+    public static function get_fragment_parameters() {
+        return new external_function_parameters(
+            array(
+                'component' => new external_value(PARAM_COMPONENT, 'Component for the callback e.g. mod_assign'),
+                'callback' => new external_value(PARAM_ALPHANUMEXT, 'Name of the callback to execute'),
+                'contextid' => new external_value(PARAM_INT, 'Context ID that the fragment is from'),
+                'args' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'name' => new external_value(PARAM_ALPHANUMEXT, 'param name'),
+                            'value' => new external_value(PARAM_RAW, 'param value')
+                        )
+                    ), 'args for the callback are optional', VALUE_OPTIONAL
+                )
+            )
+        );
+    }
+
+    /**
+     * Get a HTML fragment for inserting into something. Initial use is for inserting mforms into
+     * a page using AJAX.
+     * This web service is designed to be called only via AJAX and not directly.
+     * Callbacks that are called by this web service are responsible for doing the appropriate security checks
+     * to access the information returned. This only does minimal validation on the context.
+     *
+     * @param string $component Name of the component.
+     * @param string $callback Function callback name.
+     * @param int $contextid Context ID this fragment is in.
+     * @param array $args optional arguments for the callback.
+     * @return array HTML and JavaScript fragments for insertion into stuff.
+     * @since Moodle 3.1
+     */
+    public static function get_fragment($component, $callback, $contextid, $args = null) {
+        global $OUTPUT, $PAGE;
+
+        $params = self::validate_parameters(self::get_fragment_parameters(),
+                array(
+                    'component' => $component,
+                    'callback' => $callback,
+                    'contextid' => $contextid,
+                    'args' => $args
+                )
+        );
+
+        // Reformat arguments into something less unwieldy.
+        $arguments = array();
+        foreach ($params['args'] as $paramargument) {
+            $arguments[$paramargument['name']] = $paramargument['value'];
+        }
+
+        $context = context::instance_by_id($contextid);
+        self::validate_context($context);
+
+        // Hack alert: Forcing bootstrap_renderer to initiate moodle page.
+        $OUTPUT->header();
+
+        // Overwriting page_requirements_manager with the fragment one so only JS included from
+        // this point is returned to the user.
+        $PAGE->start_collecting_javascript_requirements();
+        $data = component_callback($params['component'], 'output_fragment_' . $params['callback'], $arguments);
+        $jsfooter = $PAGE->requires->get_end_code();
+        $output = array('html' => $data, 'javascript' => $jsfooter);
+        return $output;
+    }
+
+    /**
+     * Returns description of get_fragment() result value
+     *
+     * @return array
+     * @since Moodle 3.1
+     */
+    public static function get_fragment_returns() {
+        return new external_single_structure(
+            array(
+                'html' => new external_value(PARAM_RAW, 'HTML fragment.'),
+                'javascript' => new external_value(PARAM_RAW, 'JavaScript fragment')
+            )
+        );
+    }
 }
index 4da13f2..4b1e7e8 100644 (file)
@@ -4506,6 +4506,14 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
                 send_file_not_found();
             }
 
+            if ($context->get_course_context(false)) {
+                // If block is in course context, then check if user has capability to access course.
+                require_course_login($course);
+            } else if ($CFG->forcelogin) {
+                // If user is logged out, bp record will not be visible, even if the user would have access if logged in.
+                require_login();
+            }
+
             $bprecord = $DB->get_record('block_positions', array('contextid' => $context->id, 'blockinstanceid' => $context->instanceid));
             // User can't access file, if block is hidden or doesn't have block:view capability
             if (($bprecord && !$bprecord->visible) || !has_capability('moodle/block:view', $context)) {
index ff679d4..0d1c3cc 100644 (file)
@@ -61,7 +61,7 @@ class MoodleQuickForm_advcheckbox extends HTML_QuickForm_advcheckbox{
             $values = array(0, 1);
         }
 
-        if (!is_null($attributes['group'])) {
+       &nb