--- /dev/null
+{
+ 'env': {
+ 'browser': true,
+ 'amd': true
+ },
+ 'globals': {
+ 'M': true,
+ 'Y': true
+ },
+ 'rules': {
+ // See http://eslint.org/docs/rules/ for all rules and explanations of all
+ // rules. Commented out rules with 'DEFINE POLICY' are rules Dan P has flagged
+ // for discussion and possible enable soon.
+ // === Possible Errors ===
+ // DEFINE POLICY: 'comma-dangle': ['off', 'always'],
+ 'no-cond-assign': 'error',
+ 'no-console': 'error',
+ 'no-constant-condition': 'error',
+ 'no-control-regex': 'error',
+ 'no-debugger': 'error',
+ 'no-dupe-args': 'error',
+ 'no-dupe-keys': 'error',
+ 'no-duplicate-case': 'error',
+ // Disabled for YUI rollups, enabled by grunt for AMD: 'no-empty': 'error',
+ 'no-empty-character-class': 'error',
+ 'no-ex-assign': 'error',
+ 'no-extra-boolean-cast': 'error',
+ 'no-extra-parens': 'off',
+ 'no-extra-semi': 'error',
+ 'no-func-assign': 'error',
+ 'no-inner-declarations': 'error',
+ 'no-invalid-regexp': 'error',
+ 'no-irregular-whitespace': 'error',
+ 'no-negated-in-lhs': 'error',
+ 'no-obj-calls': 'error',
+ 'no-prototype-builtins': 'off',
+ 'no-regex-spaces': 'error',
+ 'no-sparse-arrays': 'error',
+ 'no-unexpected-multiline': 'error',
+ 'no-unreachable': 'warn',
+ 'no-unsafe-finally': 'error',
+ 'use-isnan': 'error',
+ 'valid-jsdoc': ['warn', { 'requireReturn': false }],
+ 'valid-typeof': 'error',
+
+ // === Best Practices ===
+ // (these mostly match our jshint config)
+ 'curly': 'error',
+ 'dot-notation': 'warn',
+ 'no-alert': 'warn',
+ 'no-caller': 'error',
+ 'no-case-declarations': 'error',
+ 'no-empty-pattern': 'error',
+ 'no-empty-function': 'warn',
+ //DEFINE POLICY: 'no-eq-null': 'warn',
+ 'no-eval': 'error',
+ //DEFINE POLICY: 'no-extra-bind': 'warn',
+ 'no-fallthrough': 'error',
+ //DEFINE POLICY: 'no-implicit-globals': 'warn',
+ 'no-implied-eval': 'error',
+ 'no-invalid-this': 'error',
+ 'no-iterator': 'error',
+ 'no-labels': 'error',
+ 'no-loop-func': 'error',
+ 'no-multi-spaces': 'warn',
+ 'no-multi-str': 'error',
+ 'no-native-reassign': 'warn',
+ 'no-new-func': 'error',
+ 'no-new-wrappers': 'error',
+ // DEFINE POLICY: no-octal: "error"
+ // DEFINE POLICY: no-octal-escape: "error"
+ 'no-proto': 'error',
+ 'no-redeclare': 'warn',
+ 'no-return-assign': 'error',
+ 'no-script-url': 'error',
+ 'no-self-assign': 'error',
+ 'no-self-compare': 'error',
+ 'no-unmodified-loop-condition': 'error',
+ // Disabled for YUI rollups, enabled by grunt for AMD: 'no-unused-expressions': 'error',
+ 'no-unused-labels': 'error',
+ //DEFINE POLICY: 'no-useless-call': 'error',
+ 'no-useless-escape': 'warn',
+ //DEFINE POLICY: 'no-with': 'error',
+ 'wrap-iife': ['error', 'any'],
+
+ // === Variables ===
+ 'no-delete-var': 'error',
+ // Disabled for YUI rollups, enabled by grunt for AMD: 'no-undef': 'off',
+ //DEFINE POLICY: 'no-undef-init': 'error',
+ // Disabled for YUI rollups, enabled by grunt for AMD: 'no-unused-vars': 'error',
+
+ // === Stylistic Issues ===
+ 'array-bracket-spacing': 'warn',
+ 'block-spacing': 'warn',
+ 'brace-style': ['warn', '1tbs'],
+ 'camelcase': 'warn',
+ 'comma-spacing': ['warn', { 'before': false, 'after': true }],
+ 'comma-style': ['warn', 'last'],
+ 'computed-property-spacing': 'error',
+ 'consistent-this': 'off',
+ 'eol-last': 'off',
+ 'func-names': 'off',
+ 'func-style': 'off',
+ // indent currently not doing well with our wrapping style, so disabled.
+ 'indent': ['off', 4, { 'SwitchCase': 1 }],
+ 'key-spacing': ['warn', { 'beforeColon': false, 'afterColon': true, 'mode': minimum }],
+ 'keyword-spacing': 'warn',
+ 'linebreak-style': ['error', 'unix'],
+ 'lines-around-comment': 'off',
+ 'max-len': ['error', 132],
+ 'max-lines': 'off',
+ // DEFINE POLICY: turn on some of these max values?
+ 'max-depth': 'off',
+ 'max-nested-callbacks': 'off',
+ 'max-params': 'off',
+ 'max-statements': 'off',
+ 'max-statements-per-line': 'off',
+ 'new-cap': 'warn',
+ 'new-parens': 'warn',
+ 'newline-after-var': 'off',
+ 'newline-before-return': 'off',
+ // REVIST POLICY: 'newline-per-chained-call': 'warn',
+ 'no-array-constructor': 'off',
+ 'no-bitwise': 'error',
+ 'no-continue': 'off',
+ 'no-inline-comments': 'off',
+ 'no-lonely-if': 'off',
+ 'no-mixed-operators': 'off',
+ 'no-mixed-spaces-and-tabs': 'error',
+ 'no-multiple-empty-lines': 'warn',
+ 'no-negated-condition': 'off',
+ 'no-nested-ternary': 'warn',
+ 'no-new-object': 'off',
+ 'no-plusplus': 'off',
+ 'no-spaced-func': 'warn',
+ 'no-ternary': 'off',
+ 'no-trailing-spaces': 'error',
+ 'no-underscore-dangle': 'off',
+ // DEFINE POLICY: 'no-unneeded-ternary': 'off',
+ 'no-whitespace-before-property': 'warn',
+ // DEFINE POLICY: 'object-curly-newline': 'off,
+ // DEFINE POLICY: 'object-curly-spacing': 'off',
+ // DEFINE POLICY: 'object-property-newline': 'off',
+ 'one-var': 'off',
+ // DEFINE POLICY: 'one-var-declaration-per-line': 'off',
+ 'operator-assignment': 'off',
+ 'operator-linebreak': 'off',
+ 'padded-blocks': 'off',
+ // DEFINE POLICY: 'quote-props': 'off',
+ 'quotes': 'off',
+ 'require-jsdoc': 'warn',
+ 'semi': 'error',
+ 'semi-spacing': ['warn', {'before': false, 'after': true}],
+ 'sort-vars': 'off',
+ 'space-before-blocks': 'warn',
+ 'space-before-function-paren': ['warn', 'never'],
+ 'space-in-parens': 'warn',
+ 'space-infix-ops': 'warn',
+ 'space-unary-ops': 'warn',
+ 'spaced-comment': 'warn',
+ 'unicode-bom': 'error',
+ 'wrap-regex': 'off',
+ }
+}
/lib/yuilib/*/*/*-coverage.js
atlassian-ide-plugin.xml
/node_modules/
+.eslintignore
// 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 */
+/* eslint-env node */
/**
* @copyright 2014 Andrew Nicols
module.exports = function(grunt) {
var path = require('path'),
tasks = {},
- cwd = process.env.PWD || process.cwd();
+ cwd = process.env.PWD || process.cwd(),
+ async = require('async'),
+ DOMParser = require('xmldom').DOMParser,
+ xpath = require('xpath');
// Windows users can't run grunt in a subdirectory, so allow them to set
// the root by passing --root=path/to/dir.
var root = grunt.option('root');
if (grunt.file.exists(__dirname, root)) {
cwd = path.join(__dirname, root);
- grunt.log.ok('Setting root to '+cwd);
+ grunt.log.ok('Setting root to ' + cwd);
} else {
- grunt.fail.fatal('Setting root to '+root+' failed - path does not exist');
+ grunt.fail.fatal('Setting root to ' + root + ' failed - path does not exist');
}
}
* @param {String} srcPath the matched src path
* @return {String} The rewritten destination path.
*/
- var uglify_rename = function (destPath, srcPath) {
+ var uglifyRename = function(destPath, srcPath) {
destPath = srcPath.replace('src', 'build');
destPath = destPath.replace('.js', '.min.js');
destPath = path.resolve(cwd, destPath);
return destPath;
};
+ /**
+ * Find thirdpartylibs.xml and generate an array of paths contained within
+ * them (used to generate ignore files and so on).
+ *
+ * @return {array} The list of thirdparty paths.
+ */
+ var getThirdPartyPathsFromXML = function() {
+ var thirdpartyfiles = grunt.file.expand('*/**/thirdpartylibs.xml');
+ var libs = ['node_modules/', 'vendor/'];
+
+ thirdpartyfiles.forEach(function(file) {
+ var dirname = path.dirname(file);
+
+ var doc = new DOMParser().parseFromString(grunt.file.read(file));
+ var nodes = xpath.select("/libraries/library/location/text()", doc);
+
+ nodes.forEach(function(node) {
+ var lib = path.join(dirname, node.toString());
+ if (grunt.file.isDir(lib)) {
+ // Ensure trailing slash on dirs.
+ lib = lib.replace(/\/?$/, '/');
+ }
+
+ // Look for duplicate paths before adding to array.
+ if (libs.indexOf(lib) === -1) {
+ libs.push(lib);
+ }
+ });
+ });
+ return libs;
+ };
+
+ // An array of paths to third party directories.
+ var thirdPartyPaths = getThirdPartyPathsFromXML();
+
+ /**
+ * Determine if the file is a Moodle file, or its listed in
+ * the thirdpartylibs.xml file paths as a third party file.
+ *
+ * @param {string} file The file path to determine if thirdparty
+ * @return {bool} false If thid party file.
+ */
+ var isMoodleFile = function(file) {
+ if (grunt.file.isMatch(thirdPartyPaths, file)) {
+ return false;
+ }
+ return true;
+ };
+
// Project configuration.
grunt.initConfig({
jshint: {
options: {jshintrc: '.jshintrc'},
amd: { src: amdSrc }
},
+ eslint: {
+ // Even though warnings dont stop the build we don't display warnings by default because
+ // at this moment we've got too many core warnings.
+ options: { quiet: !grunt.option('show-lint-warnings') },
+ // Check AMD files. We add some stricter rules which we can't apply to the default configuration due
+ // to YUI rollups.
+ amd: {
+ src: amdSrc,
+ filter: isMoodleFile,
+ options: {
+ rules: {'no-undef': 'error', 'no-unused-vars': 'error', 'no-empty': 'error', 'no-unused-expressions': 'error'}
+ }
+ },
+ // Check YUI module source files.
+ yui: {
+ src: ['**/yui/src/**/*.js', '!*/**/yui/src/*/meta/*.js'],
+ filter: isMoodleFile
+ }
+ },
uglify: {
amd: {
files: [{
expand: true,
src: amdSrc,
- rename: uglify_rename
+ rename: uglifyRename
}]
}
},
},
yui: {
files: ['**/yui/src/**/*.js'],
- tasks: ['shifter']
+ tasks: ['yui']
},
},
shifter: {
}
});
+ /**
+ * Generate ignore files (utilising thirdpartylibs.xml data)
+ */
+ tasks.ignorefiles = function() {
+ // Generate .eslintignore.
+ var eslintIgnores = ['# Generated by "grunt ignorefiles"', '*/**/yui/src/*/meta/', '*/**/build/'].concat(thirdPartyPaths);
+ grunt.file.write('.eslintignore', eslintIgnores.join('\n'));
+ };
+
/**
* 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.
* so be careful to to call done().
*/
tasks.shifter = function() {
- var async = require('async'),
- done = this.async(),
+ var done = this.async(),
options = grunt.config('shifter.options');
// Run the shifter processes one at a time to avoid confusing output.
- async.eachSeries(options.paths, function (src, filedone) {
+ async.eachSeries(options.paths, function(src, filedone) {
var args = [];
- args.push( path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
+ args.push(path.normalize(__dirname + '/node_modules/shifter/bin/shifter'));
// Always ignore the node_modules directory.
args.push('--excludes', 'node_modules');
cmd: "node",
args: args,
opts: {cwd: src, stdio: 'inherit', env: process.env}
- }, function (error, result, code) {
+ }, function(error, result, code) {
if (code) {
grunt.fail.fatal('Shifter failed with code: ' + code);
} else {
tasks.startup = function() {
// Are we in a YUI directory?
if (path.basename(path.resolve(cwd, '../../')) == 'yui') {
- grunt.task.run('shifter');
+ grunt.task.run('yui');
// Are we in an AMD directory?
} else if (inAMD) {
grunt.task.run('amd');
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('uglify.amd.files', [{ expand: true, src: files, rename: uglifyRename }]);
grunt.config('shifter.options.paths', files);
changedFiles = Object.create(null);
}, 200);
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-contrib-watch');
+ grunt.loadNpmTasks('grunt-eslint');
// Register JS tasks.
grunt.registerTask('shifter', 'Run Shifter against the current directory', tasks.shifter);
- grunt.registerTask('amd', ['jshint', 'uglify']);
- grunt.registerTask('js', ['amd', 'shifter']);
+ grunt.registerTask('ignorefiles', 'Generate ignore files for linters', tasks.ignorefiles);
+ grunt.registerTask('yui', ['eslint:yui', 'shifter']);
+ grunt.registerTask('amd', ['eslint:amd', 'jshint', 'uglify']);
+ grunt.registerTask('js', ['amd', 'yui']);
// Register CSS taks.
grunt.registerTask('css', ['less:bootstrapbase']);
$formcourseformats[$courseformat] = new lang_string('pluginname', "format_$courseformat");
}
$temp->add(new admin_setting_configselect('moodlecourse/format', new lang_string('format'), new lang_string('coursehelpformat'),
- 'weeks',$formcourseformats));
+ 'topics', $formcourseformats));
$temp->add(new admin_setting_configtext('moodlecourse/maxsections', new lang_string('maxnumberweeks'),
new lang_string('maxnumberweeks_desc'), 52));
$temp->add(new admin_settings_num_course_sections('moodlecourse/numsections', new lang_string('numberweeks'),
- new lang_string('coursehelpnumberweeks'), 10));
+ new lang_string('coursehelpnumberweeks'), 4));
$choices = array();
$choices['0'] = new lang_string('hiddensectionscollapsed');
try {
config = JSON.parse(self._competency.ruleconfig);
} catch (e) {
+ // eslint-disable-line no-empty
}
}
navigation_node::TYPE_SETTING,
null,
null,
- new pix_icon('competency', '', 'tool_lp'));
+ new pix_icon('i/competencies', ''));
if (isset($settingsnode)) {
$navigation->add_node($settingsnode);
}
navigation_node::TYPE_SETTING,
null,
null,
- new pix_icon('competency', '', 'tool_lp'));
+ new pix_icon('i/competencies', ''));
if (isset($settingsnode)) {
$navigation->add_node($settingsnode);
}
navigation_node::TYPE_SETTING,
null,
null,
- new pix_icon('competency', '', 'tool_lp'));
+ new pix_icon('i/competencies', ''));
if (isset($settingsnode)) {
$navigation->add_node($settingsnode);
}
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
-
-<svg
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns:cc="http://creativecommons.org/ns#"
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:svg="http://www.w3.org/2000/svg"
- xmlns="http://www.w3.org/2000/svg"
- xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
- xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="32"
- height="32"
- id="svg2"
- version="1.1"
- inkscape:version="0.48.4 r9939"
- sodipodi:docname="New document 1" preserveAspectRatio="xMinYMid meet">
- <defs
- id="defs4" />
- <sodipodi:namedview
- id="base"
- pagecolor="#ffffff"
- bordercolor="#666666"
- borderopacity="1.0"
- inkscape:pageopacity="0.0"
- inkscape:pageshadow="2"
- inkscape:zoom="5.6"
- inkscape:cx="32.609372"
- inkscape:cy="35.818362"
- inkscape:document-units="px"
- inkscape:current-layer="layer1"
- showgrid="false"
- width="33px"
- inkscape:window-width="1916"
- inkscape:window-height="981"
- inkscape:window-x="0"
- inkscape:window-y="72"
- inkscape:window-maximized="0" />
- <metadata
- id="metadata7">
- <rdf:RDF>
- <cc:Work
- rdf:about="">
- <dc:format>image/svg+xml</dc:format>
- <dc:type
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
- <dc:title></dc:title>
- </cc:Work>
- </rdf:RDF>
- </metadata>
- <g
- inkscape:label="Layer 1"
- inkscape:groupmode="layer"
- id="layer1"
- transform="translate(0,-1020.3622)">
- <g
- id="g3050"
- transform="matrix(0.92121849,0,0,0.92121849,-35.507391,91.120599)">
- <path
- style="fill:#fafafa"
- d="m 38.892857,1026.2908 0,-17 17,0 17,0 0,17 0,17 -17,0 -17,0 0,-17 z"
- id="path3056"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#989898"
- d="m 40.954049,1037.3439 c -3.465234,-3.0054 -1.687876,-4.0531 6.875853,-4.0531 6.919599,0 7.985143,0.2502 9.263277,2.1752 0.947236,1.4266 2.021632,1.9536 3.121972,1.5314 1.37559,-0.5279 1.677706,-2.0754 1.677706,-8.5934 0,-4.3723 0.273145,-8.6614 0.606989,-9.5314 0.509481,-1.3277 -0.303204,-1.5818 -5.059017,-1.5818 -3.522693,0 -5.432219,-0.3783 -5.047972,-1 1.028329,-1.6639 14.737848,-1.2221 17.277494,0.5567 3.593622,2.5171 2.823056,4.4433 -1.777494,4.4433 l -4,0 0,7.345 c 0,9.8743 -0.892111,10.655 -12.175731,10.655 -7.005509,0 -8.916904,-0.3458 -10.763077,-1.9469 z m 3.938808,-10.6589 c 0,-2.9538 0.324365,-3.3942 2.5,-3.3942 2.153991,0 2.499902,0.4498 2.499294,3.25 -6e-4,2.7611 -0.376693,3.2717 -2.5,3.3942 -2.203157,0.1271 -2.499294,-0.2579 -2.499294,-3.25 z m 7.428571,-1.3942 c 0,-1.6931 0.666667,-2 4.344732,-2 5.000845,0 4.927087,1.9191 -0.08232,2.1419 -3.011559,0.1339 -2.966069,0.1822 0.809017,0.8581 l 4,0.7162 -4.535714,0.1419 c -3.91635,0.1225 -4.535715,-0.1312 -4.535715,-1.8581 z m -6.761904,-3.6667 c -1.167788,-1.1678 -0.718106,-4.0457 0.833333,-5.3333 1.846181,-1.5322 4.5,0.2461 4.5,3.0155 0,2.2918 -3.735778,3.9154 -5.333333,2.3178 z m 6.833333,-0.3333 c 0.339919,-0.55 2.139919,-1 4,-1 1.860081,0 3.660081,0.45 4,1 0.377944,0.6115 -1.175955,1 -4,1 -2.824045,0 -4.377944,-0.3885 -4,-1 z"
- id="path3054"
- inkscape:connector-curvature="0" />
- <path
- style="fill:#5e5e5e"
- d="m 40.892857,1036.2908 c -1.027328,-1.9196 -0.76049,-2 6.636076,-2 5.863885,0 8.157677,0.4084 9.593719,1.708 1.429812,1.2939 2.297278,1.4506 3.578741,0.6463 1.318167,-0.8273 1.801811,-3.0713 2.191464,-10.1678 l 0.5,-9.1062 -5.5,-0.351 c -4.40263,-0.2811 -3.893849,-0.3889 2.55,-0.5402 5.16365,-0.1213 8.480263,0.2412 9.25,1.0109 2.011735,2.0117 1.364232,2.8 -2.3,2.8 l -3.5,0 0,7.0657 c 0,10.7479 -0.204518,10.9343 -12,10.9343 -8.906177,0 -10.039956,-0.2061 -11,-2 z m 5.078947,-9.0357 c -0.06465,-2.8954 0.283272,-3.5357 1.921053,-3.5357 1.608782,0 2,0.6666 2,3.4081 0,2.8111 -0.245749,3.2042 -1.402754,2.244 -1.077175,-0.894 -1.523051,-0.8644 -1.921053,0.1276 -0.285064,0.7104 -0.553825,-0.2994 -0.597246,-2.244 z m 8.682712,-0.6716 c 1.243913,-0.2392 3.043913,-0.2301 4,0.02 0.956088,0.2503 -0.06166,0.446 -2.261659,0.4349 -2.2,-0.011 -2.982253,-0.2159 -1.738341,-0.4551 z m 0,-3 c 1.243913,-0.2392 3.043913,-0.2301 4,0.02 0.956088,0.2503 -0.06166,0.446 -2.261659,0.4349 -2.2,-0.011 -2.982253,-0.2159 -1.738341,-0.4551 z m 0,-2 c 1.243913,-0.2392 3.043913,-0.2301 4,0.02 0.956088,0.2503 -0.06166,0.446 -2.261659,0.4349 -2.2,-0.011 -2.982253,-0.2159 -1.738341,-0.4551 z m -9.205886,-1.2025 c -0.810187,-1.3109 0.447721,-4.0902 1.851216,-4.0902 1.828446,0 3.499178,2.081 2.907877,3.6219 -0.592966,1.5452 -3.892838,1.87 -4.759093,0.4683 z"
- id="path3052"
- inkscape:connector-curvature="0" />
- </g>
- </g>
-</svg>
--- /dev/null
+This files describes changes in /admin/tool/lp/* - plugins,
+information provided here is intended especially for developers.
+
+=== 3.2 ===
+
+* The icon 'competency.png/svg' has been removed, please use i/competencies instead.
+
+=== 3.1.1 ===
+
+* The plugin icon 'competency.png/svg' will be removed in the future use i/competencies instead.
case 3: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_delete.php');
case 4: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_display.php');
case 5: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_download.php');
- //case 6: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_enrol.php'); //TODO: MDL-24064
case 7: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_forcepasswordchange.php');
case 8: redirect($CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk_cohortadd.php');
}
+++ /dev/null
-<?php
-/**
-* script for bulk user multi enrol operations
-*/
-
-die('this needs to be rewritten to use new enrol framework, sorry'); //TODO: MDL-24064
-
-require_once('../../config.php');
-require_once($CFG->libdir.'/adminlib.php');
-$processed = optional_param('processed', '', PARAM_BOOL);
-$sort = optional_param('sort', 'fullname', PARAM_ALPHA); //Sort by full name
-$dir = optional_param('dir', 'asc', PARAM_ALPHA); //Order to sort (ASC)
-
-require_login();
-admin_externalpage_setup('userbulk');
-require_capability('moodle/role:assign', context_system::instance()); //TODO: use some enrol cap
-$return = $CFG->wwwroot.'/'.$CFG->admin.'/user/user_bulk.php';
-//If no users selected then return to user_bulk.php
-if (empty($SESSION->bulk_users)) {
- redirect($return);
-}
-$users = $SESSION->bulk_users; //Get users to display
-$usertotal = get_users(false); //Total number of users registered
-$usercount = count($users); //number of users
-
-echo $OUTPUT->header();
-
-//take user info
-foreach ($users as $key => $id) {
- $user = $DB->get_record('user', array('id'=>$id));
- $user->fullname = fullname($user, true);
- unset($user->firstname);
- unset($user->lastname);
- $users[$key] = $user;
-}
-
-// Need to sort by date
-function sort_compare($a, $b) {
- global $sort, $dir;
- if($sort == 'lastaccess') {
- $rez = $b->lastaccess - $a->lastaccess;
- } else {
- $rez = strcasecmp(@$a->$sort, @$b->$sort);
- }
- return $dir == 'desc' ? -$rez : $rez;
-}
-usort($users, 'sort_compare');
-
-//Take courses data (id, shortname, and fullname)
-$courses = get_courses_page(1, 'c.sortorder ASC', 'c.id,c.shortname,c.fullname,c.visible', $totalcount);
-$table = new html_table();
-$table->width = "95%";
-$columns = array('fullname');
-foreach ($courses as $v)
-{
- $columns[] = $v->shortname;
-}
-
-//Print columns headers from table
-foreach ($columns as $column) {
- $strtitle = $column;
- if ($sort != $column) {
- $columnicon = '';
- $columndir = 'asc';
- } else {
- $columndir = ($dir == 'asc') ? 'desc' : 'asc';
- $columnicon = ' <img src="'.$OUTPUT->pix_url('t/'.($dir == 'asc' ? 'down' : 'up' )).'" alt="" />';
- }
- $table->head[] = '<a href="user_bulk_enrol.php?sort='.$column.'&dir='.$columndir.'">'.$strtitle.'</a>'.$columnicon;
- $table->align[] = 'left';
-}
-
-// process data submitting
-if(!empty($processed)) {
- //Process data form here
- $total = count($courses) * count($users);
-
- for ( $i = 0; $i < $total; $i++ )
- {
- $param = "selected".$i;
- $info = optional_param($param, '', PARAM_SEQUENCE);
- /**
- * user id: ids[0]
- * course id: ids[1]
- * enrol stat: ids[2]
- */
- $ids = explode(',', $info);
- if(!empty($ids[2])) {
- $context = context_course::instance($ids[1]);
- role_assign(5, $ids[0], $context->id); //TODO: horrible!!
- } else {
- if( empty($ids[1] ) ) {
- continue;
- }
- $context = context_course::instance($ids[1]);
- role_unassign(5, $ids[0], $context->id);
- }
- }
- redirect($return, get_string('changessaved')); //TODO: horrible!!
-}
-
-//Form beginning
-echo '<form id="multienrol" name="multienrol" method="post" action="user_bulk_enrol.php">';
-echo '<input type="hidden" name="processed" value="yes" />';
-$count = 0;
-foreach($users as $user) {
- $temparray = array (
- '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$user->id.'&course='.SITEID.'">'.$user->fullname.'</a>'
- );
- $mycourses = enrol_get_users_courses($user->id, false);
- foreach($courses as $acourse) {
- $state = '';
- if (isset($mycourses[$acourse->id])) {
- $state = 'checked="checked"';
- }
- $temparray[] = '<input type="hidden" name="selected' . $count .
- '" value="' . $user->id . ',' . $acourse->id . ',0" />' .
- '<input type="checkbox" name="selected' . $count .
- '" value="' . $user->id . ',' . $acourse->id . ',1" ' . $state . '/>';
- $count++;
- }
- $table->data[] = $temparray;
-}
-echo $OUTPUT->heading("$usercount / $usertotal ".get_string('users'));
-echo html_writer::table($table);
-echo '<div class="continuebutton">';
-echo '<input type="submit" name="multienrolsubmit" value="save changes" />';
-echo '</div>';
-echo '</form>';
-
-echo $OUTPUT->footer();
if (has_capability('moodle/user:update', $syscontext)) {
$actions[5] = get_string('download', 'admin');
}
- if (has_capability('moodle/role:assign', $syscontext)){
- //TODO: MDL-24064
- //$actions[6] = get_string('enrolmultipleusers', 'admin');
- }
if (has_capability('moodle/user:update', $syscontext)) {
$actions[7] = get_string('forcepasswordchange');
}
$info = new stdClass();
$info->filequestionid = $oldquestionid;
$info->dbquestionid = $newquestionid;
- $info->answer = $data->answertext;
+ $info->answer = s($data->answertext);
throw new restore_step_exception('error_question_answers_missing_in_db', $info);
}
$newitemid = $this->questionanswercache[$data->answertext];
--- /dev/null
+@block @block_course_overview @mod_quiz
+Feature: View the quiz being due
+ In order to know what quizzes are due
+ As a student
+ I can visit my dashboard
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | student1 | Student | 1 | student1@example.com |
+ | student2 | Student | 2 | student2@example.com |
+ | teacher1 | Teacher | 1 | teacher1@example.com |
+ And the following "courses" exist:
+ | fullname | shortname |
+ | Course 1 | C1 |
+ | Course 2 | C2 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | student1 | C1 | student |
+ | student2 | C2 | student |
+ | teacher1 | C1 | editingteacher |
+ | teacher1 | C2 | editingteacher |
+ And the following "activities" exist:
+ | activity | course | idnumber | name | timeclose |
+ | quiz | C1 | Q1A | Quiz 1A No deadline | 0 |
+ | quiz | C1 | Q1B | Quiz 1B Past deadline | 1337 |
+ | quiz | C1 | Q1C | Quiz 1C Future deadline | 9000000000 |
+ | quiz | C1 | Q1D | Quiz 1D Future deadline | 9000000000 |
+ | quiz | C1 | Q1E | Quiz 1E Future deadline | 9000000000 |
+ | quiz | C2 | Q2A | Quiz 2A Future deadline | 9000000000 |
+ And the following "question categories" exist:
+ | contextlevel | reference | name |
+ | Course | C1 | Test questions |
+ And the following "questions" exist:
+ | qtype | name | questiontext | questioncategory |
+ | truefalse | First question | Answer the first question | Test questions |
+ And quiz "Quiz 1A No deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+ And quiz "Quiz 1B Past deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+ And quiz "Quiz 1C Future deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+ And quiz "Quiz 1D Future deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+ And quiz "Quiz 1E Future deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+ And quiz "Quiz 2A Future deadline" contains the following questions:
+ | question | page |
+ | First question | 1 |
+
+ Scenario: View my quizzes that are due
+ Given I log in as "student1"
+ When I am on homepage
+ Then I should see "You have quizzes that are due" in the "Course overview" "block"
+ And I should see "Quiz 1C Future deadline" in the "Course overview" "block"
+ And I should see "Quiz 1D Future deadline" in the "Course overview" "block"
+ And I should see "Quiz 1E Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1A No deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1B Past deadline" in the "Course overview" "block"
+ And I should not see "Quiz 2A Future deadline" in the "Course overview" "block"
+ And I log out
+ And I log in as "student2"
+ And I should see "You have quizzes that are due" in the "Course overview" "block"
+ And I should not see "Quiz 1C Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1D Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1E Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1A No deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1B Past deadline" in the "Course overview" "block"
+ And I should see "Quiz 2A Future deadline" in the "Course overview" "block"
+
+ Scenario: View my quizzes that are due and never finished
+ Given I log in as "student1"
+ And I follow "Course 1"
+ And I follow "Quiz 1D Future deadline"
+ And I press "Attempt quiz now"
+ And I follow "Finish attempt ..."
+ And I press "Submit all and finish"
+ And I follow "Course 1"
+ And I follow "Quiz 1E Future deadline"
+ And I press "Attempt quiz now"
+ When I am on homepage
+ Then I should see "You have quizzes that are due" in the "Course overview" "block"
+ And I should see "Quiz 1C Future deadline" in the "Course overview" "block"
+ And I should see "Quiz 1E Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1A No deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1B Past deadline" in the "Course overview" "block"
+ And I should not see "Quiz 1D Future deadline" in the "Course overview" "block"
+ And I should not see "Quiz 2A Future deadline" in the "Course overview" "block"
+
.block_navigation .block_tree ul {margin-left: 18px;}
.block_navigation .block_tree p.hasicon {text-indent: -21px; padding-left: 21px;}
.block_navigation .block_tree p.hasicon img {width: 16px; height: 16px; margin-top: 3px; margin-right: 5px; vertical-align: top;}
+.block_navigation .block_tree p.hasicon.visibleifjs {display: block;}
.block_navigation .block_tree .tree_item {cursor:pointer; padding-left: 0;margin:3px 0px; background-position: 0 50%; background-repeat: no-repeat;}
.block_navigation .block_tree .tree_item.branch {padding-left: 21px;}
.block_settings .block_tree ul {margin-left: 18px;}
.block_settings .block_tree p.hasicon {text-indent: -21px; padding-left: 21px;}
.block_settings .block_tree p.hasicon img {width: 16px; height: 16px; margin-top: 3px; margin-right: 5px; vertical-align: top;}
+.block_settings .block_tree p.hasicon.visibleifjs {display: block;}
.block_settings .block_tree .tree_item.branch {padding-left: 21px;}
.block_settings .block_tree .tree_item {cursor:pointer; margin:3px 0px; background-position: 0 50%; background-repeat: no-repeat;}
'perpage' => new external_value(PARAM_INT, 'items per page', VALUE_DEFAULT, 0),
'requiredcapabilities' => new external_multiple_structure(
new external_value(PARAM_CAPABILITY, 'Capability string used to filter courses by permission'),
- VALUE_OPTIONAL
+ 'Optional list of required capabilities (used to filter the list)', VALUE_DEFAULT, array()
),
'limittoenrolled' => new external_value(PARAM_BOOL, 'limit to enrolled courses', VALUE_DEFAULT, 0),
)
}
if (!is_object($section)) {
$section = $DB->get_record('course_sections', array('course' => $this->get_courseid(), 'section' => $section),
- 'id,section,sequence');
+ 'id,section,sequence,summary');
}
if (!$section || !$section->section) {
// Not possible to delete 0-section.
return false;
}
- if (!$forcedeleteifnotempty && !empty($section->sequence)) {
+ if (!$forcedeleteifnotempty && (!empty($section->sequence) || !empty($section->summary))) {
return false;
}
// very quick on an empty table).
$DB->delete_records('course_modules_completion', array('coursemoduleid' => $cm->id));
$DB->delete_records('course_completion_criteria', array('moduleinstance' => $cm->id,
+ 'course' => $cm->course,
'criteriatype' => COMPLETION_CRITERIA_TYPE_ACTIVITY));
// Delete all tag instances associated with the instance of this module.
break;
case 'move':
case 'update':
- case 'duplicate':
case 'assignroles':
break;
default:
אנא השתמש באפשרות העזרה.';
$string['cliyesnoprompt'] = 'רשום y (שפרושו כן) או n (שפרושו לא)';
$string['environmentrequireinstall'] = 'נדרש להתקין/לאפשר זאת';
-$string['environmentrequireversion'] = '×\92×\99רסת {$a->needed} × ×\93רשת ×\95×\90ת×\94 ×\9eר×\99×¥ {$a->current}';
+$string['environmentrequireversion'] = '×\92×\99רסת {$a->needed} × ×\93רשת ×\90×\9a ×\94×\92×\99רס×\94 ×\94× ×\95×\9b×\97×\99ת ×\94×\99×\90 {$a->current}';
$string['pathswrongadmindir'] = 'ספריית ה-admin לא קיימת';
$string['phpextension'] = 'הרחבת PHP {$a}';
$string['phpversion'] = 'גירסת PHP';
-$string['phpversionhelp'] = '<p>גרסת PHP חייבת להיות לפחות 4.3.0 או 5.1.0 (בגרסאות 5.0.x קיימות מספר בעיות ידועות) </p>
+$string['phpversionhelp'] = '<p>גרסת PHP חייבת להיות לפחות 4.3.0 או 5.1.0 (בגרסאות 5.0 קיימות מספר בעיות ידועות) </p>
<p> במערכת שלך פועלת כרגע גרסת {$a} </p>
<p> אתה חייב לשדרג את גרסת ה-PHP שלך או לעבור למחשב מארח עם עם גירסת PHP חדשה! <br/>
-(במקרים של גרסת 5.0.x תוכל גם לרדת בגרסה ל- 4.4.x)
+(במקרים של גרסת 5.0 תוכל גם לרדת בגרסה ל- 4.4)
</p>';
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'הינך רואה את עמוד זה מפני שהתקנת והפעלת בהלכה את <strong>{$a->packname} {$a->packversion}</strong>
$string['language'] = 'שפת ממשק';
$string['next'] = 'הבא';
$string['previous'] = 'קודם';
-$string['reload'] = '×\98×¢×\9f מחדש';
+$string['reload'] = '×\98×¢×\99× ×\94 מחדש';
$string['pathserrcreatedataroot'] = 'O programa de instalação não conseguiu criar a pasta de dados <b>{$a->dataroot}</b>.';
$string['pathshead'] = 'Confirmar caminhos';
$string['pathsrodataroot'] = 'A pasta de dados não tem permissões de escrita.';
-$string['pathsroparentdataroot'] = 'A pasta pai <b>{$a->parent}</b> não tem permissões de escrita. O programa de instalação não conseguiu criar a pasta <b>{$a->dataroot}</b>.';
+$string['pathsroparentdataroot'] = 'A pasta ascendente <b>{$a->parent}</b> não tem permissões de escrita. O programa de instalação não conseguiu criar a pasta <b>{$a->dataroot}</b>.';
$string['pathssubadmindir'] = 'Alguns servidores Web utilizam a pasta <strong>admin</strong> em URLs especiais de acesso a funcionalidades especiais, como é o caso de painéis de controlo. Algumas situações podem criar conflitos com a localização normal das páginas de administração do Moodle. Estes problemas podem ser resolvidos renomeando a pasta <strong>admin</strong> na instalação do Moodle e indicando aqui o novo nome a utilizar. Por exemplo:<br /><br /><b>moodleadmin</b><br /><br />Esta ação resolverá os problemas de acesso das hiperligações para as funcionalidades de administração do Moodle.';
$string['pathssubdataroot'] = '<p>Uma diretoria em que o Moodle irá armazenar todo o conteúdo de ficheiros enviados pelos utilizadores.</p>
<p>Esta diretoria deve ser legível e gravável pelo utilizador do servidor web (geralmente \'www-data\', \'nobody\', ou \'apache\').</p>
$string['duplicateusername'] = 'Duplicate username - skipping record';
$string['emailfail'] = 'Emailing failed';
$string['error'] = 'Error occurred';
+$string['error_question_answers_missing_in_db'] = 'Failed to find an answer matching "{$a->answer}" in the question_answers database table. This occurred while restoring the question with id {$a->filequestionid} in the backup file, which has been matched to the existing question with id {$a->dbquestionid} in the database.';
$string['errorprocessingarchive'] = 'Error processing archive file';
$string['errorcleaningdirectory'] = 'Error cleaning directory "{$a}"';
$string['errorcopyingfiles'] = 'Error copying files';
var hashString = function(source) {
// From http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery.
/* jshint bitwise: false */
+ /* eslint no-bitwise: "off" */
var hash = 0, i, chr, len;
if (source.length === 0) {
return hash;
define(function() {
// This module exposes only the global yui instance.
+ /* global Y */
return /** @alias module:core/yui */ Y;
});
ini_set('memcached.sess_locking', '1'); // Locking is required!
// Try to configure lock and expire timeouts - ignored if memcached is before version 2.2.0.
- ini_set('memcached.sess_lock_max_wait', $this->acquiretimeout);
+ if (version_compare($version, '3.0.0-dev') >= 0) {
+ ini_set('memcached.sess_lock_wait_max', $this->acquiretimeout * 1000);
+ } else {
+ ini_set('memcached.sess_lock_max_wait', $this->acquiretimeout);
+ }
+
ini_set('memcached.sess_lock_expire', $this->lockexpire);
}
require_once(dirname(__FILE__) . '/../../../config.php');
require_once($CFG->libdir . '/filestorage/file_storage.php');
-$contextid = required_param('contextid', PARAM_INT);
-$elementid = required_param('elementid', PARAM_ALPHANUMEXT);
-$pagehash = required_param('pagehash', PARAM_ALPHANUMEXT);
-$pageinstance = required_param('pageinstance', PARAM_ALPHANUMEXT);
+// Clean up actions.
+$actions = array_map(function($actionparams) {
+ $action = isset($actionparams['action']) ? $actionparams['action'] : null;
+ $params = [];
+ $keys = [
+ 'action' => PARAM_ALPHA,
+ 'contextid' => PARAM_INT,
+ 'elementid' => PARAM_ALPHANUMEXT,
+ 'pagehash' => PARAM_ALPHANUMEXT,
+ 'pageinstance' => PARAM_ALPHANUMEXT
+ ];
+
+ if ($action == 'save') {
+ $keys['drafttext'] = PARAM_RAW;
+ } else if ($action == 'resume') {
+ $keys['draftid'] = PARAM_INT;
+ }
+
+ foreach ($keys as $key => $type) {
+ // Replicate required_param().
+ if (!isset($actionparams[$key])) {
+ print_error('missingparam', '', '', $key);
+ }
+ $params[$key] = clean_param($actionparams[$key], $type);
+ }
+
+ return $params;
+}, isset($_REQUEST['actions']) ? $_REQUEST['actions'] : []);
+
$now = time();
// This is the oldest time any autosave text will be recovered from.
// This is so that there is a good chance the draft files will still exist (there are many variables so
// this is impossible to guarantee).
$before = $now - 60*60*24*4;
-list($context, $course, $cm) = get_context_info_array($contextid);
+$context = context_system::instance();
$PAGE->set_url('/lib/editor/atto/autosave-ajax.php');
$PAGE->set_context($context);
-require_login($course, false, $cm);
-require_sesskey();
-
+require_login();
if (isguestuser()) {
print_error('accessdenied', 'admin');
}
+require_sesskey();
if (!in_array('atto', explode(',', get_config('core', 'texteditors')))) {
print_error('accessdenied', 'admin');
}
-$action = required_param('action', PARAM_ALPHA);
+$responses = array();
+foreach ($actions as $actionparams) {
+
+ $action = $actionparams['action'];
+ $contextid = $actionparams['contextid'];
+ $elementid = $actionparams['elementid'];
+ $pagehash = $actionparams['pagehash'];
+ $pageinstance = $actionparams['pageinstance'];
+
+ if ($action === 'save') {
+ $drafttext = $actionparams['drafttext'];
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
+
+ $record = $DB->get_record('editor_atto_autosave', $params);
+ if ($record && $record->pageinstance != $pageinstance) {
+ print_error('concurrent access from the same user is not supported');
+ die();
+ }
-$response = array();
+ if (!$record) {
+ $record = new stdClass();
+ $record->elementid = $elementid;
+ $record->userid = $USER->id;
+ $record->pagehash = $pagehash;
+ $record->contextid = $contextid;
+ $record->drafttext = $drafttext;
+ $record->pageinstance = $pageinstance;
+ $record->timemodified = $now;
-if ($action === 'save') {
- $drafttext = required_param('drafttext', PARAM_RAW);
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
+ $DB->insert_record('editor_atto_autosave', $record);
- $record = $DB->get_record('editor_atto_autosave', $params);
- if ($record && $record->pageinstance != $pageinstance) {
- print_error('concurrent access from the same user is not supported');
- die();
- }
+ // No response means no error.
+ $responses[] = null;
+ continue;
+ } else {
+ $record->drafttext = $drafttext;
+ $record->timemodified = time();
+ $DB->update_record('editor_atto_autosave', $record);
- if (!$record) {
- $record = new stdClass();
- $record->elementid = $elementid;
- $record->userid = $USER->id;
- $record->pagehash = $pagehash;
- $record->contextid = $contextid;
- $record->drafttext = $drafttext;
- $record->pageinstance = $pageinstance;
- $record->timemodified = $now;
-
- $DB->insert_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- } else {
- $record->drafttext = $drafttext;
- $record->timemodified = time();
- $DB->update_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- }
-} else if ($action == 'resume') {
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
-
- $newdraftid = required_param('draftid', PARAM_INT);
-
- $record = $DB->get_record('editor_atto_autosave', $params);
-
- if (!$record) {
- $record = new stdClass();
- $record->elementid = $elementid;
- $record->userid = $USER->id;
- $record->pagehash = $pagehash;
- $record->contextid = $contextid;
- $record->pageinstance = $pageinstance;
- $record->pagehash = $pagehash;
- $record->draftid = $newdraftid;
- $record->timemodified = time();
- $record->drafttext = '';
-
- $DB->insert_record('editor_atto_autosave', $record);
-
- // No response means no error.
- die();
- } else {
- // Copy all draft files from the old draft area.
- $usercontext = context_user::instance($USER->id);
- $stale = $record->timemodified < $before;
- require_once($CFG->libdir . '/filelib.php');
-
- $fs = get_file_storage();
- $files = $fs->get_directory_files($usercontext->id, 'user', 'draft', $newdraftid, '/', true, true);
-
- $lastfilemodified = 0;
- foreach ($files as $file) {
- $lastfilemodified = max($lastfilemodified, $file->get_timemodified());
- }
- if ($record->timemodified < $lastfilemodified) {
- $stale = true;
+ // No response means no error.
+ $responses[] = null;
+ continue;
}
- if (!$stale) {
- // This function copies all the files in one draft area, to another area (in this case it's
- // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
- $newdrafttext = file_save_draft_area_files($record->draftid,
- $usercontext->id,
- 'user',
- 'draft',
- $newdraftid,
- array(),
- $record->drafttext);
-
- // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
- $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
- 'draftfile.php',
- $usercontext->id,
- 'user',
- 'draft',
- $newdraftid);
- $record->drafttext = $newdrafttext;
+ } else if ($action == 'resume') {
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
+
+ $newdraftid = $actionparams['draftid'];
+
+ $record = $DB->get_record('editor_atto_autosave', $params);
+ if (!$record) {
+ $record = new stdClass();
+ $record->elementid = $elementid;
+ $record->userid = $USER->id;
+ $record->pagehash = $pagehash;
+ $record->contextid = $contextid;
$record->pageinstance = $pageinstance;
+ $record->pagehash = $pagehash;
$record->draftid = $newdraftid;
$record->timemodified = time();
- $DB->update_record('editor_atto_autosave', $record);
+ $record->drafttext = '';
- // A response means the draft has been restored and here is the auto-saved text.
- $response['result'] = $record->drafttext;
- echo json_encode($response);
- } else {
- $DB->delete_records('editor_atto_autosave', array('id' => $record->id));
+ $DB->insert_record('editor_atto_autosave', $record);
// No response means no error.
+ $responses[] = null;
+ continue;
+
+ } else {
+ // Copy all draft files from the old draft area.
+ $usercontext = context_user::instance($USER->id);
+ $stale = $record->timemodified < $before;
+ require_once($CFG->libdir . '/filelib.php');
+
+ $fs = get_file_storage();
+ $files = $fs->get_directory_files($usercontext->id, 'user', 'draft', $newdraftid, '/', true, true);
+
+ $lastfilemodified = 0;
+ foreach ($files as $file) {
+ $lastfilemodified = max($lastfilemodified, $file->get_timemodified());
+ }
+ if ($record->timemodified < $lastfilemodified) {
+ $stale = true;
+ }
+
+ if (!$stale) {
+ // This function copies all the files in one draft area, to another area (in this case it's
+ // another draft area). It also rewrites the text to @@PLUGINFILE@@ links.
+ $newdrafttext = file_save_draft_area_files($record->draftid,
+ $usercontext->id,
+ 'user',
+ 'draft',
+ $newdraftid,
+ array(),
+ $record->drafttext);
+
+ // Final rewrite to the new draft area (convert the @@PLUGINFILES@@ again).
+ $newdrafttext = file_rewrite_pluginfile_urls($newdrafttext,
+ 'draftfile.php',
+ $usercontext->id,
+ 'user',
+ 'draft',
+ $newdraftid);
+ $record->drafttext = $newdrafttext;
+
+ $record->pageinstance = $pageinstance;
+ $record->draftid = $newdraftid;
+ $record->timemodified = time();
+ $DB->update_record('editor_atto_autosave', $record);
+
+ // A response means the draft has been restored and here is the auto-saved text.
+ $response = ['result' => $record->drafttext];
+ $responses[] = $response;
+
+ } else {
+ $DB->delete_records('editor_atto_autosave', array('id' => $record->id));
+
+ // No response means no error.
+ $responses[] = null;
+ }
+ continue;
}
- die();
+
+ } else if ($action == 'reset') {
+ $params = array('elementid' => $elementid,
+ 'userid' => $USER->id,
+ 'pagehash' => $pagehash,
+ 'contextid' => $contextid);
+
+ $DB->delete_records('editor_atto_autosave', $params);
+ $responses[] = null;
+ continue;
}
-} else if ($action == 'reset') {
- $params = array('elementid' => $elementid,
- 'userid' => $USER->id,
- 'pagehash' => $pagehash,
- 'contextid' => $contextid);
-
- $DB->delete_records('editor_atto_autosave', $params);
- die();
}
-print_error('invalidarguments');
+echo json_encode($responses);
"notify.js",
"textarea.js",
"autosave.js",
+ "autosave-io.js",
"clean.js",
"commands.js",
"toolbar.js",
--- /dev/null
+// 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 autosave function for the Atto editor.
+ *
+ * @module moodle-editor_atto-autosave-io
+ * @submodule autosave-io
+ * @package editor_atto
+ * @copyright 2016 Frédéric Massart
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var EditorAutosaveIoDispatcherInstance = null;
+
+function EditorAutosaveIoDispatcher() {
+ EditorAutosaveIoDispatcher.superclass.constructor.apply(this, arguments);
+ this._submitEvents = {};
+ this._queue = [];
+ this._throttle = null;
+}
+EditorAutosaveIoDispatcher.NAME = 'EditorAutosaveIoDispatcher';
+EditorAutosaveIoDispatcher.ATTRS = {
+
+ /**
+ * The relative path to the ajax script.
+ *
+ * @attribute autosaveAjaxScript
+ * @type String
+ * @default '/lib/editor/atto/autosave-ajax.php'
+ * @readOnly
+ */
+ autosaveAjaxScript: {
+ value: '/lib/editor/atto/autosave-ajax.php',
+ readOnly: true
+ },
+
+ /**
+ * The time buffer for the throttled requested.
+ *
+ * @attribute delay
+ * @type Number
+ * @default 50
+ * @readOnly
+ */
+ delay: {
+ value: 50,
+ readOnly: true
+ }
+
+};
+Y.extend(EditorAutosaveIoDispatcher, Y.Base, {
+
+ /**
+ * Dispatch an IO request.
+ *
+ * This method will put the requests in a queue in order to attempt to bulk them.
+ *
+ * @param {Object} params The parameters of the request.
+ * @param {Object} context The context in which the callbacks are called.
+ * @param {Object} callbacks Object with 'success', 'complete', 'end', 'failure' and 'start' as
+ * optional keys defining the callbacks to call. Success and Complete
+ * functions will receive the response as parameter. Success and Complete
+ * may receive an object containing the error key, use this to confirm
+ * that no errors occured.
+ * @return {Void}
+ */
+ dispatch: function(params, context, callbacks) {
+ if (this._throttle) {
+ this._throttle.cancel();
+ }
+
+ this._throttle = Y.later(this.get('delay'), this, this._processDispatchQueue);
+ this._queue.push([params, context, callbacks]);
+ },
+
+ /**
+ * Dispatches the requests in the queue.
+ *
+ * @return {Void}
+ */
+ _processDispatchQueue: function() {
+ var queue = this._queue,
+ data = {};
+
+ this._queue = [];
+ if (queue.length < 1) {
+ return;
+ }
+
+ Y.Array.each(queue, function(item, index) {
+ data[index] = item[0];
+ });
+
+ Y.io(M.cfg.wwwroot + this.get('autosaveAjaxScript'), {
+ method: 'POST',
+ data: Y.QueryString.stringify({
+ actions: data,
+ sesskey: M.cfg.sesskey
+ }),
+ on: {
+ start: this._makeIoEventCallback('start', queue),
+ complete: this._makeIoEventCallback('complete', queue),
+ failure: this._makeIoEventCallback('failure', queue),
+ end: this._makeIoEventCallback('end', queue),
+ success: this._makeIoEventCallback('success', queue)
+ }
+ });
+ },
+
+ /**
+ * Creates a function that dispatches an IO response to callbacks.
+ *
+ * @param {String} event The type of event.
+ * @param {Array} queue The queue.
+ * @return {Function}
+ */
+ _makeIoEventCallback: function(event, queue) {
+ var noop = function() {};
+ return function() {
+ var response = arguments[1],
+ parsed = {};
+
+ if ((event == 'complete' || event == 'success') && (typeof response !== 'undefined'
+ && typeof response.responseText !== 'undefined' && response.responseText !== '')) {
+
+ // Success and complete events need to parse the response.
+ parsed = JSON.parse(response.responseText) || {};
+ }
+
+ Y.Array.each(queue, function(item, index) {
+ var context = item[1],
+ cb = (item[2] && item[2][event]) || noop,
+ arg;
+
+ if (parsed && parsed.error) {
+ // The response is an error, we send it to everyone.
+ arg = parsed;
+ } else if (parsed) {
+ // The response was parsed, we only communicate the relevant portion of the response.
+ arg = parsed[index];
+ }
+
+ cb.apply(context, [arg]);
+ });
+ };
+ },
+
+ /**
+ * Form submit handler.
+ *
+ * @param {EventFacade} e The event.
+ * @return {Void}
+ */
+ _onSubmit: function(e) {
+ var data = {},
+ id = e.currentTarget.generateID(),
+ params = this._submitEvents[id];
+
+ if (!params || params.ios.length < 1) {
+ return;
+ }
+
+ Y.Array.each(params.ios, function(param, index) {
+ data[index] = param;
+ });
+
+ Y.io(M.cfg.wwwroot + this.get('autosaveAjaxScript'), {
+ method: 'POST',
+ data: Y.QueryString.stringify({
+ actions: data,
+ sesskey: M.cfg.sesskey
+ }),
+ sync: true
+ });
+ },
+
+ /**
+ * Registers a request to be made on form submission.
+ *
+ * @param {Node} node The forum node we will listen to.
+ * @param {Object} params Parameters for the IO request.
+ * @return {Void}
+ */
+ whenSubmit: function(node, params) {
+ if (typeof this._submitEvents[node.generateID()] === 'undefined') {
+ this._submitEvents[node.generateID()] = {
+ event: node.on('submit', this._onSubmit, this),
+ ios: []
+ };
+ }
+ this._submitEvents[node.get('id')].ios.push([params]);
+ }
+
+});
+EditorAutosaveIoDispatcherInstance = new EditorAutosaveIoDispatcher();
+
+
+function EditorAutosaveIo() {}
+EditorAutosaveIo.prototype = {
+
+ /**
+ * Dispatch an IO request.
+ *
+ * This method will put the requests in a queue in order to attempt to bulk them.
+ *
+ * @param {Object} params The parameters of the request.
+ * @param {Object} context The context in which the callbacks are called.
+ * @param {Object} callbacks Object with 'success', 'complete', 'end', 'failure' and 'start' as
+ * optional keys defining the callbacks to call. Success and Complete
+ * functions will receive the response as parameter. Success and Complete
+ * may receive an object containing the error key, use this to confirm
+ * that no errors occured.
+ * @return {Void}
+ */
+ autosaveIo: function(params, context, callbacks) {
+ EditorAutosaveIoDispatcherInstance.dispatch(params, context, callbacks);
+ },
+
+ /**
+ * Registers a request to be made on form submission.
+ *
+ * @param {Node} form The forum node we will listen to.
+ * @param {Object} params Parameters for the IO request.
+ * @return {Void}
+ */
+ autosaveIoOnSubmit: function(form, params) {
+ EditorAutosaveIoDispatcherInstance.whenSubmit(form, params);
+ }
+
+};
+Y.Base.mix(Y.M.editor_atto.Editor, [EditorAutosaveIo]);
pageHash: {
value: '',
writeOnce: true
- },
-
- /**
- * The relative path to the ajax script.
- *
- * @attribute autosaveAjaxScript
- * @type String
- * @default '/lib/editor/atto/autosave-ajax.php'
- * @readOnly
- */
- autosaveAjaxScript: {
- value: '/lib/editor/atto/autosave-ajax.php',
- readOnly: true
}
};
form,
optiontype = null,
options = this.get('filepickeroptions'),
- params,
- url;
+ params;
if (!this.get('autosaveEnabled')) {
// Autosave disabled for this instance.
// First see if there are any saved drafts.
// Make an ajax request.
- url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
params = {
- sesskey: M.cfg.sesskey,
contextid: this.get('contextid'),
action: 'resume',
- drafttext: '',
draftid: draftid,
elementid: this.get('elementid'),
pageinstance: this.autosaveInstance,
pagehash: this.get('pageHash')
};
- Y.io(url, {
- method: 'POST',
- data: params,
- context: this,
- on: {
- success: function(id,o) {
- var response_json;
- if (typeof o.responseText !== "undefined" && o.responseText !== "") {
- response_json = JSON.parse(o.responseText);
+ this.autosaveIo(params, this, {
+ success: function(response) {
+ if (response === null) {
+ // This can happen when there is nothing to resume from.
+ return;
+ } else if (!response) {
+ Y.log('Invalid response received.', 'debug', LOGNAME_AUTOSAVE);
+ return;
+ }
- // Revert untouched editor contents to an empty string.
- // Check for FF and Chrome.
- if (response_json.result === '<p></p>' || response_json.result === '<p><br></p>' ||
- response_json.result === '<br>') {
- response_json.result = '';
- }
+ // Revert untouched editor contents to an empty string.
+ // Check for FF and Chrome.
+ if (response.result === '<p></p>' || response.result === '<p><br></p>' ||
+ response.result === '<br>') {
+ response.result = '';
+ }
- // Check for IE 9 and 10.
- if (response_json.result === '<p> </p>' || response_json.result === '<p><br> </p>') {
- response_json.result = '';
- }
+ // Check for IE 9 and 10.
+ if (response.result === '<p> </p>' || response.result === '<p><br> </p>') {
+ response.result = '';
+ }
- if (response_json.error || typeof response_json.result === 'undefined') {
- Y.log('Error occurred recovering draft text: ' + response_json.error, 'debug', LOGNAME_AUTOSAVE);
- this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
- NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
- } else if (response_json.result !== this.textarea.get('value') &&
- response_json.result !== '') {
- Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
- this.recoverText(response_json.result);
- }
- this._fireSelectionChanged();
- }
- },
- failure: function() {
+ if (response.error || typeof response.result === 'undefined') {
+ Y.log('Error occurred recovering draft text: ' + response.error, 'debug', LOGNAME_AUTOSAVE);
this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
+ } else if (response.result !== this.textarea.get('value') &&
+ response.result !== '') {
+ Y.log('Autosave text found - recover it.', 'debug', LOGNAME_AUTOSAVE);
+ this.recoverText(response.result);
}
+ this._fireSelectionChanged();
+
+ },
+ failure: function() {
+ this.showMessage(M.util.get_string('errortextrecovery', 'editor_atto'),
+ NOTIFY_WARNING, RECOVER_MESSAGE_TIMEOUT);
}
});
// Now setup the timer for periodic saves.
-
var delay = parseInt(this.get('autosaveFrequency'), 10) * 1000;
this.autosaveTimer = Y.later(delay, this, this.saveDraft, false, true);
// Now setup the listener for form submission.
form = this.textarea.ancestor('form');
if (form) {
- form.on('submit', this.resetAutosave, this);
+ this.autosaveIoOnSubmit(form, {
+ action: 'reset',
+ contextid: this.get('contextid'),
+ elementid: this.get('elementid'),
+ pageinstance: this.autosaveInstance,
+ pagehash: this.get('pageHash')
+ });
}
return this;
},
- /**
- * Clear the autosave text because the form was submitted normally.
- *
- * @method resetAutosave
- * @chainable
- */
- resetAutosave: function() {
- // Make an ajax request to reset the autosaved text.
- var url = M.cfg.wwwroot + this.get('autosaveAjaxScript');
- var params = {
- sesskey: M.cfg.sesskey,
- contextid: this.get('contextid'),
- action: 'reset',
- elementid: this.get('elementid'),
- pageinstance: this.autosaveInstance,
- pagehash: this.get('pageHash')
- };
-
- Y.io(url, {
- method: 'POST',
- data: params,
- sync: true
- });
- return this;
- },
-
-
/**
* Recover a previous version of this text and show a message.
*
};
// Reusable error handler - must be passed the correct context.
- var ajaxErrorFunction = function(code, response) {
+ var ajaxErrorFunction = function(response) {
var errorDuration = parseInt(this.get('autosaveFrequency'), 10) * 1000;
- Y.log('Error while autosaving text:' + code, 'warn', LOGNAME_AUTOSAVE);
+ Y.log('Error while autosaving text', 'warn', LOGNAME_AUTOSAVE);
Y.log(response, 'warn', LOGNAME_AUTOSAVE);
this.showMessage(M.util.get_string('autosavefailed', 'editor_atto'), NOTIFY_WARNING, errorDuration);
};
- Y.io(url, {
- method: 'POST',
- data: params,
- context: this,
- on: {
- error: ajaxErrorFunction,
- failure: ajaxErrorFunction,
- success: function(code, response) {
- if (response.responseText !== "") {
- Y.soon(Y.bind(ajaxErrorFunction, this, [code, response]));
- } else {
- // All working.
- this.lastText = newText;
- this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'),
- NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
- }
+ this.autosaveIo(params, this, {
+ failure: ajaxErrorFunction,
+ success: function(response) {
+ if (response && response.error) {
+ Y.soon(Y.bind(ajaxErrorFunction, this, [response]));
+ } else {
+ // All working.
+ this.lastText = newText;
+ this.showMessage(M.util.get_string('autosavesucceeded', 'editor_atto'),
+ NOTIFY_INFO, SUCCESS_MESSAGE_TIMEOUT);
}
}
});
"moodle-core-notification-confirm",
"moodle-editor_atto-rangy",
"handlebars",
- "timers"
+ "timers",
+ "querystring-stringify"
]
},
"moodle-editor_atto-plugin": {
*/
public function onQuickFormEvent($event, $arg, &$caller) {
if ($event === 'createElement') {
+ if (!is_array($arg[2])) {
+ $arg[2] = [];
+ }
$arg[2] += array('itemtype' => '', 'component' => '');
}
return parent::onQuickFormEvent($event, $arg, $caller);
public function standard_top_of_body_html() {
global $CFG;
$output = $this->page->requires->get_top_of_body_code();
- if (!empty($CFG->additionalhtmltopofbody)) {
+ if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmltopofbody)) {
$output .= "\n".$CFG->additionalhtmltopofbody;
}
$output .= $this->maintenance_warning();
// but some of the content won't be known until later, so we return a placeholder
// for now. This will be replaced with the real content in {@link core_renderer::footer()}.
$output = '';
- if (!empty($CFG->additionalhtmlfooter)) {
+ if ($this->page->pagelayout !== 'embedded' && !empty($CFG->additionalhtmlfooter)) {
$output .= "\n".$CFG->additionalhtmlfooter;
}
$output .= $this->unique_end_html_token;
return false;
}
- list ($dir, $filename) = $this->get_faildump_filename($scope, 'png');
- $this->saveScreenshot($filename, $dir);
+ // Some drivers (e.g. chromedriver) may throw an exception while trying to take a screenshot. If this isn't handled,
+ // the behat run dies. We don't want to lose the information about the failure that triggered the screenshot,
+ // so let's log the exception message to a file (to explain why there's no screenshot) and allow the run to continue,
+ // handling the failure as normal.
+ try {
+ list ($dir, $filename) = $this->get_faildump_filename($scope, 'png');
+ $this->saveScreenshot($filename, $dir);
+ } catch (Exception $e) {
+ // Catching all exceptions as we don't know what the driver might throw.
+ list ($dir, $filename) = $this->get_faildump_filename($scope, 'txt');
+ $message = "Could not save screenshot due to an error\n" . $e->getMessage();
+ file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $message);
+ }
}
/**
protected function take_contentdump(AfterStepScope $scope) {
list ($dir, $filename) = $this->get_faildump_filename($scope, 'html');
- $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w');
- fwrite($fh, $this->getSession()->getPage()->getContent());
- fclose($fh);
+ try {
+ // Driver may throw an exception during getContent(), so do it first to avoid getting an empty file.
+ $content = $this->getSession()->getPage()->getContent();
+ } catch (Exception $e) {
+ // Catching all exceptions as we don't know what the driver might throw.
+ $content = "Could not save contentdump due to an error\n" . $e->getMessage();
+ }
+ file_put_contents($dir . DIRECTORY_SEPARATOR . $filename, $content);
}
/**
*/
public function format_text_blanktarget_testcases() {
return [
- 'Simple link' =>
- [
- '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4">Hey, that\'s pretty good!</a>',
- '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
- ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
- ],
- 'Link with rel' =>
- [
- '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow">Hey, that\'s pretty good!</a>',
- '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow noreferrer"' .
- ' target="_blank">Hey, that\'s pretty good!</a></div>'
- ],
- 'Link with rel noreferrer' =>
- [
- '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer">Hey, that\'s pretty good!</a>',
- '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer"' .
- ' target="_blank">Hey, that\'s pretty good!</a></div>'
- ],
- 'Link with target' =>
- [
- '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">Hey, that\'s pretty good!</a>',
- '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">' .
- 'Hey, that\'s pretty good!</a></div>'
- ],
- 'Link with target blank' =>
- [
- '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank">Hey, that\'s pretty good!</a>',
- '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
- ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
- ]
+ 'Simple link' => [
+ '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4">Hey, that\'s pretty good!</a>',
+ '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
+ ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
+ ],
+ 'Link with rel' => [
+ '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow">Hey, that\'s pretty good!</a>',
+ '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="nofollow noreferrer"' .
+ ' target="_blank">Hey, that\'s pretty good!</a></div>'
+ ],
+ 'Link with rel noreferrer' => [
+ '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer">Hey, that\'s pretty good!</a>',
+ '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" rel="noreferrer"' .
+ ' target="_blank">Hey, that\'s pretty good!</a></div>'
+ ],
+ 'Link with target' => [
+ '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">Hey, that\'s pretty good!</a>',
+ '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_self">' .
+ 'Hey, that\'s pretty good!</a></div>'
+ ],
+ 'Link with target blank' => [
+ '<a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank">Hey, that\'s pretty good!</a>',
+ '<div class="text_to_html"><a href="https://www.youtube.com/watch?v=JeimE8Wz6e4" target="_blank"' .
+ ' rel="noreferrer">Hey, that\'s pretty good!</a></div>'
+ ],
+ 'Link with Frank\'s casket inscription' => [
+ '<a href="https://en.wikipedia.org/wiki/Franks_Casket">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻ' .
+ 'ᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a>',
+ '<div class="text_to_html"><a href="https://en.wikipedia.org/wiki/Franks_Casket" target="_blank" ' .
+ 'rel="noreferrer">ᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁᚠᛁᛋᚳ᛫ᚠᛚᚩᛞᚢ᛫ᚪᚻᚩᚠᚩᚾᚠᛖᚱᚷ ᛖᚾ' .
+ 'ᛒᛖᚱᛁᚷ ᚹᚪᚱᚦᚷᚪ᛬ᛋᚱᛁᚳᚷᚱᚩᚱᚾᚦᚫᚱᚻᛖᚩᚾᚷᚱᛖᚢᛏᚷᛁᛋᚹᚩᛗ ᚻᚱᚩᚾᚫᛋᛒᚪᚾ ᛗᚫᚷᛁ</a></div>'
+ ],
+ 'No link' => [
+ 'Some very boring text written with the Latin script',
+ '<div class="text_to_html">Some very boring text written with the Latin script</div>'
+ ],
+ 'No link with Thror\'s map runes' => [
+ 'ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ',
+ '<div class="text_to_html">ᛋᛏᚫᚾᛞ ᛒᚣ ᚦᛖ ᚷᚱᛖᚣ ᛋᛏᚩᚾᛖ ᚻᚹᛁᛚᛖ ᚦᛖ ᚦᚱᚢᛋᚻ ᚾᚩᚳᛋ ᚫᚾᛞ ᚦᛖ ᛋᛖᛏᛏᛁᚾᚷ ᛋᚢᚾ ᚹᛁᚦ ᚦᛖ ᛚᚫᛋᛏ ᛚᛁᚷᚻᛏ ᚩᚠ ᛞᚢᚱᛁᚾᛋ ᛞᚫᚣ ᚹ' .
+ 'ᛁᛚᛚ ᛋᚻᛁᚾᛖ ᚢᛈᚩᚾ ᚦᛖ ᚳᛖᚣᚻᚩᛚᛖ</div>'
+ ]
];
}
}
if ($options['blanktarget']) {
$domdoc = new DOMDocument();
- $domdoc->loadHTML($text);
+ $domdoc->loadHTML('<?xml version="1.0" encoding="UTF-8" ?>' . $text);
foreach ($domdoc->getElementsByTagName('a') as $link) {
if ($link->hasAttribute('target') && strpos($link->getAttribute('target'), '_blank') === false) {
continue;
// $domdoc->loadHTML($text, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); however it seems like the libxml
// version that travis uses doesn't work properly and ends up leaving <html><body>, so I'm forced to use
// this regex to remove those tags.
- $text = trim(preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $domdoc->saveHTML()));
+ $text = trim(preg_replace('~<(?:!DOCTYPE|/?(?:html|body))[^>]*>\s*~i', '', $domdoc->saveHTML($domdoc->documentElement)));
}
return $text;
if ($success && empty($contactlist[$message['touserid']]) && !empty($blocknoncontacts)) {
// The user isn't a contact and they have selected to block non contacts so this message won't be sent.
$success = false;
- $errormessage = get_string('userisblockingyounoncontact', 'message');
+ $errormessage = get_string('userisblockingyounoncontact', 'message',
+ fullname(core_user::get_user($message['touserid'])));
}
//now we can send the message (at least try)
*/
draw : function() {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
- drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
+ drawingcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
node,
position;
'zIndex': 50
});
- drawingregion.append(node);
+ drawingcanvas.append(node);
node.setX(position.x);
node.setY(position.y);
drawable.store_position(node, position.x, position.y);
-
- // Pass throught the event handlers on the div.
- node.on('gesturemovestart', this.editor.edit_start, null, this.editor);
- node.on('gesturemove', this.editor.edit_move, null, this.editor);
- node.on('gesturemoveend', this.editor.edit_end, null, this.editor);
-
drawable.nodes.push(node);
this.drawable = drawable;
this.draw = function(focus) {
var drawable = new M.assignfeedback_editpdf.drawable(this.editor),
node,
- drawingregion = this.editor.get_dialogue_element(SELECTOR.DRAWINGREGION),
+ drawingcanvas = this.editor.get_dialogue_element(SELECTOR.DRAWINGCANVAS),
container,
menu,
position,
color: COMMENTTEXTCOLOUR
});
- drawingregion.append(container);
+ drawingcanvas.append(container);
container.setStyle('position', 'absolute');
container.setX(position.x);
container.setY(position.y);
drawable.store_position(container, position.x, position.y);
drawable.nodes.push(container);
node.set('value', this.rawtext);
- scrollheight = node.get('scrollHeight'),
+ scrollheight = node.get('scrollHeight');
node.setStyles({
'height' : scrollheight + 'px',
'overflow': 'hidden'
drawingcanvas.setStyle('height', page.height + 'px');
// Update page select.
- this.get_dialogue_element(SELECTOR.PAGESELECT).set('value', this.currentpage);
+ this.get_dialogue_element(SELECTOR.PAGESELECT).set('selectedIndex', this.currentpage);
this.resize(); // Internally will call 'redraw', after checking the dialogue size.
},
// If completion option is enabled, evaluate it and return true/false.
if ($assign->get_instance()->completionsubmit) {
- $submission = $assign->get_user_submission($userid, false);
+ if ($assign->get_instance()->teamsubmission) {
+ $submission = $assign->get_group_submission($userid, 0, false);
+ } else {
+ $submission = $assign->get_user_submission($userid, false);
+ }
return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
} else {
// Completion option is not enabled so just return $type.
* @param int $updatetime
* @return void
*/
- public function send_notification($userfrom,
- $userto,
- $messagetype,
- $eventtype,
- $updatetime) {
+ public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
+ global $USER;
+ $userid = core_user::is_real_user($userfrom->id) ? $userfrom->id : $USER->id;
+ $uniqueid = $this->get_uniqueid_for_user($userid);
self::send_assignment_notification($userfrom,
$userto,
$messagetype,
$this->get_module_name(),
$this->get_instance()->name,
$this->is_blind_marking(),
- $this->get_uniqueid_for_user($userfrom->id));
+ $uniqueid);
}
/**
$this->update_submission($submission, $userid, true, $instance->teamsubmission);
$completion = new completion_info($this->get_course());
if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
- $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $userid);
+ $this->update_activity_completion_records($instance->teamsubmission,
+ $instance->requireallteammemberssubmit,
+ $submission,
+ $userid,
+ COMPLETION_COMPLETE,
+ $completion);
}
if (!empty($data->submissionstatement) && $USER->id == $userid) {
}
$completion = new completion_info($this->get_course());
if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
- $completion->update_state($this->get_course_module(), $complete, $USER->id);
+ $this->update_activity_completion_records($instance->teamsubmission,
+ $instance->requireallteammemberssubmit,
+ $submission,
+ $USER->id,
+ $complete,
+ $completion);
}
if (!$instance->submissiondrafts) {
}
return $this->get_course_module()->id . '_' . $id;
}
+
+ /**
+ * Updates and creates the completion records in mdl_course_modules_completion.
+ *
+ * @param int $teamsubmission value of 0 or 1 to indicate whether this is a group activity
+ * @param int $requireallteammemberssubmit value of 0 or 1 to indicate whether all group members must click Submit
+ * @param obj $submission the submission
+ * @param int $userid the user id
+ * @param int $complete
+ * @param obj $completion
+ *
+ * @return null
+ */
+ protected function update_activity_completion_records($teamsubmission,
+ $requireallteammemberssubmit,
+ $submission,
+ $userid,
+ $complete,
+ $completion) {
+
+ if (($teamsubmission && $submission->groupid > 0 && !$requireallteammemberssubmit) ||
+ ($teamsubmission && $submission->groupid > 0 && $requireallteammemberssubmit &&
+ $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
+
+ $members = groups_get_members($submission->groupid);
+
+ foreach ($members as $member) {
+ $completion->update_state($this->get_course_module(), $complete, $member->id);
+ }
+ } else {
+ $completion->update_state($this->get_course_module(), $complete, $userid);
+ }
+
+ return;
+ }
+
}
/**
$this->apply_admin_defaults();
$this->add_action_buttons();
-
- // Add warning popup/noscript tag, if grades are changed by user.
- $hasgrade = false;
- if (!empty($this->_instance)) {
- $hasgrade = $DB->record_exists_select('assign_grades',
- 'assignment = ? AND grade <> -1',
- array($this->_instance));
- }
-
- if ($mform->elementExists('grade') && $hasgrade) {
- $module = array(
- 'name' => 'mod_assign',
- 'fullpath' => '/mod/assign/module.js',
- 'requires' => array('node', 'event'),
- 'strings' => array(array('changegradewarning', 'mod_assign'))
- );
- $PAGE->requires->js_init_call('M.mod_assign.init_grade_change', null, false, $module);
-
- // Add noscript tag in case.
- $noscriptwarning = $mform->createElement('static',
- 'warning',
- null,
- html_writer::tag('noscript',
- get_string('changegradewarning', 'mod_assign')));
- $mform->insertElementBefore($noscriptwarning, 'grade');
- }
}
/**
$this->resetAfterTest(true);
- $this->course = $this->getDataGenerator()->create_course();
+ $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$this->teachers = array();
for ($i = 0; $i < self::DEFAULT_TEACHER_COUNT; $i++) {
array_push($this->teachers, $this->getDataGenerator()->create_user());
return $mform;
}
+
+ public function testable_update_activity_completion_records($teamsubmission,
+ $requireallteammemberssubmit,
+ $submission,
+ $userid,
+ $complete,
+ $completion) {
+ return parent::update_activity_completion_records($teamsubmission,
+ $requireallteammemberssubmit,
+ $submission,
+ $userid,
+ $complete,
+ $completion);
+ }
}
$grade = $assign->get_user_grade($this->students[0]->id, false);
$this->assertEquals('30.0', $grade->grade);
}
+
+ /**
+ * Test updating activity completion when submitting an assessment.
+ */
+ public function test_update_activity_completion_records_solitary_submission() {
+ $assign = $this->create_instance(array('grade' => 100,
+ 'completion' => COMPLETION_TRACKING_AUTOMATIC,
+ 'requireallteammemberssubmit' => 0));
+
+ $cm = $assign->get_course_module();
+
+ $student = $this->students[0];
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+
+ $this->setUser($student);
+
+ // Simulate a submission.
+ $data = new stdClass();
+ $data->onlinetext_editor = array(
+ 'itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Student submission text',
+ 'format' => FORMAT_MOODLE
+ );
+ $completion = new completion_info($this->course);
+
+ $notices = array();
+ $assign->save_submission($data, $notices);
+
+ $submission = $assign->get_user_submission($student->id, true);
+
+ // Check that completion is not met yet.
+ $completiondata = $completion->get_data($cm, false, $student->id);
+ $this->assertEquals(0, $completiondata->completionstate);
+ $assign->testable_update_activity_completion_records(0, 0, $submission,
+ $student->id, COMPLETION_COMPLETE, $completion);
+ // Completion should now be met.
+ $completiondata = $completion->get_data($cm, false, $student->id);
+ $this->assertEquals(1, $completiondata->completionstate);
+ }
+
+ /**
+ * Test updating activity completion when submitting an assessment.
+ */
+ public function test_update_activity_completion_records_team_submission() {
+ $assign = $this->create_instance(array('grade' => 100,
+ 'completion' => COMPLETION_TRACKING_AUTOMATIC,
+ 'teamsubmission' => 1));
+
+ $cm = $assign->get_course_module();
+
+ $student1 = $this->students[0];
+ $student2 = $this->students[1];
+ $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
+
+ // Put both users into a group.
+ $group1 = $this->getDataGenerator()->create_group(array('courseid' => $this->course->id));
+ $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student1->id));
+ $this->getDataGenerator()->create_group_member(array('groupid' => $group1->id, 'userid' => $student2->id));
+
+ $this->setUser($student1);
+
+ // Simulate a submission.
+ $data = new stdClass();
+ $data->onlinetext_editor = array(
+ 'itemid' => file_get_unused_draft_itemid(),
+ 'text' => 'Student submission text',
+ 'format' => FORMAT_MOODLE
+ );
+ $completion = new completion_info($this->course);
+
+ $notices = array();
+ $assign->save_submission($data, $notices);
+
+ $submission = $assign->get_user_submission($student1->id, true);
+ $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+ $submission->groupid = $group1->id;
+
+ // Check that completion is not met yet.
+ $completiondata = $completion->get_data($cm, false, $student1->id);
+ $this->assertEquals(0, $completiondata->completionstate);
+ $completiondata = $completion->get_data($cm, false, $student2->id);
+ $this->assertEquals(0, $completiondata->completionstate);
+ $assign->testable_update_activity_completion_records(1, 0, $submission, $student1->id,
+ COMPLETION_COMPLETE, $completion);
+ // Completion should now be met.
+ $completiondata = $completion->get_data($cm, false, $student1->id);
+ $this->assertEquals(1, $completiondata->completionstate);
+ $completiondata = $completion->get_data($cm, false, $student2->id);
+ $this->assertEquals(1, $completiondata->completionstate);
+ }
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Search area for mod_data activities.
+ *
+ * @package mod_data
+ * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace mod_data\search;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Search area for mod_data activities.
+ *
+ * @package mod_data
+ * @copyright 2016 David Monllao {@link http://www.davidmonllao.com}
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class activity extends \core_search\area\base_activity {
+}
$string['savesuccess'] = 'Saved successfully. Your preset will now be available across the site.';
$string['savetemplate'] = 'Save template';
$string['search'] = 'Search';
+$string['search:activity'] = 'Database - activity information';
$string['selectedrequired'] = 'All selected required';
$string['showall'] = 'Show all entries';
$string['single'] = 'View single';
// String cache
static $str;
+ // This is an extremely hacky way to ensure we only print the 'unread' anchor
+ // the first time we encounter an unread post on a page. Ideally this would
+ // be moved into the caller somehow, and be better testable. But at the time
+ // of dealing with this bug, this static workaround was the most surgical and
+ // it fits together with only printing th unread anchor id once on a given page.
+ static $firstunreadanchorprinted = false;
$modcontext = context_module::instance($cm->id);
$forumpostclass = ' read';
} else {
$forumpostclass = ' unread';
- $output .= html_writer::tag('a', '', array('name'=>'unread'));
+ // If this is the first unread post printed then give it an anchor and id of unread.
+ if (!$firstunreadanchorprinted) {
+ $output .= html_writer::tag('a', '', array('id' => 'unread'));
+ $firstunreadanchorprinted = true;
+ }
}
} else {
// ignore trackign status if not tracked or tracked param missing
array(
"title" => "lti_typename",
"launch_url" => "lti_toolurl",
- "description" => "lti_description"
+ "description" => "lti_description",
+ "icon" => "lti_icon",
+ "secure_icon" => "lti_secureicon"
),
array(
- "icon_url" => "lti_icon",
- "secure_icon_url" => "lti_secureicon"
+ "icon_url" => "lti_extension_icon",
+ "secure_icon_url" => "lti_extension_secureicon"
)
);
// If an activity name exists, unset the cartridge name so we don't override it.
if (isset($type->lti_typename)) {
unset($toolinfo['lti_typename']);
}
+
+ // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
+ if (empty($toolinfo['lti_icon']) && !empty($toolinfo['lti_extension_icon'])) {
+ $toolinfo['lti_icon'] = $toolinfo['lti_extension_icon'];
+ }
+ unset($toolinfo['lti_extension_icon']);
+
+ if (empty($toolinfo['lti_secureicon']) && !empty($toolinfo['lti_extension_secureicon'])) {
+ $toolinfo['lti_secureicon'] = $toolinfo['lti_extension_secureicon'];
+ }
+ unset($toolinfo['lti_extension_secureicon']);
+
foreach ($toolinfo as $property => $value) {
$type->$property = $value;
}
"title" => "name",
"launch_url" => "toolurl",
"secure_launch_url" => "securetoolurl",
- "description" => "intro"
+ "description" => "intro",
+ "icon" => "icon",
+ "secure_icon" => "secureicon"
),
array(
- "icon_url" => "icon",
- "secure_icon_url" => "secureicon"
+ "icon_url" => "extension_icon",
+ "secure_icon_url" => "extension_secureicon"
)
);
// If an activity name exists, unset the cartridge name so we don't override it.
if (isset($lti->name)) {
unset($toolinfo['name']);
}
+
+ // Always prefer cartridge core icons first, then, if none are found, look at the extension icons.
+ if (empty($toolinfo['icon']) && !empty($toolinfo['extension_icon'])) {
+ $toolinfo['icon'] = $toolinfo['extension_icon'];
+ }
+ unset($toolinfo['extension_icon']);
+
+ if (empty($toolinfo['secureicon']) && !empty($toolinfo['extension_secureicon'])) {
+ $toolinfo['secureicon'] = $toolinfo['extension_secureicon'];
+ }
+ unset($toolinfo['extension_secureicon']);
+
foreach ($toolinfo as $property => $value) {
$lti->$property = $value;
}
}
/**
- * @param int $quizid the quiz id.
+ * @param int|array $quizids A quiz ID, or an array of quiz IDs.
* @param int $userid the userid.
* @param string $status 'all', 'finished' or 'unfinished' to control
* @param bool $includepreviews
* @return an array of all the user's attempts at this quiz. Returns an empty
* array if there are none.
*/
-function quiz_get_user_attempts($quizid, $userid, $status = 'finished', $includepreviews = false) {
+function quiz_get_user_attempts($quizids, $userid, $status = 'finished', $includepreviews = false) {
global $DB, $CFG;
// TODO MDL-33071 it is very annoying to have to included all of locallib.php
// just to get the quiz_attempt::FINISHED constants, but I will try to sort
break;
}
+ $quizids = (array) $quizids;
+ list($insql, $inparams) = $DB->get_in_or_equal($quizids, SQL_PARAMS_NAMED);
+ $params += $inparams;
+ $params['userid'] = $userid;
+
$previewclause = '';
if (!$includepreviews) {
$previewclause = ' AND preview = 0';
}
- $params['quizid'] = $quizid;
- $params['userid'] = $userid;
return $DB->get_records_select('quiz_attempts',
- 'quiz = :quizid AND userid = :userid' . $previewclause . $statuscondition,
+ "quiz $insql AND userid = :userid" . $previewclause . $statuscondition,
$params, 'attempt ASC');
}
return;
}
+ // Get the quizzes attempts.
+ $attemptsinfo = [];
+ $quizids = [];
+ foreach ($quizzes as $quiz) {
+ $quizids[] = $quiz->id;
+ $attemptsinfo[$quiz->id] = ['count' => 0, 'hasfinished' => false];
+ }
+ $attempts = quiz_get_user_attempts($quizids, $USER->id);
+ foreach ($attempts as $attempt) {
+ $attemptsinfo[$attempt->quiz]['count']++;
+ $attemptsinfo[$attempt->quiz]['hasfinished'] = true;
+ }
+ unset($attempts);
+
// Fetch some language strings outside the main loop.
$strquiz = get_string('modulename', 'quiz');
$strnoattempts = get_string('noattempts', 'quiz');
$now = time();
foreach ($quizzes as $quiz) {
if ($quiz->timeclose >= $now && $quiz->timeopen < $now) {
- // Give a link to the quiz, and the deadline.
- $str = '<div class="quiz overview">' .
- '<div class="name">' . $strquiz . ': <a ' .
- ($quiz->visible ? '' : ' class="dimmed"') .
- ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' .
- $quiz->coursemodule . '">' .
- $quiz->name . '</a></div>';
- $str .= '<div class="info">' . get_string('quizcloseson', 'quiz',
- userdate($quiz->timeclose)) . '</div>';
+ $str = '';
// Now provide more information depending on the uers's role.
$context = context_module::instance($quiz->coursemodule);
// For teacher-like people, show a summary of the number of student attempts.
// The $quiz objects returned by get_all_instances_in_course have the necessary $cm
// fields set to make the following call work.
- $str .= '<div class="info">' .
- quiz_num_attempt_summary($quiz, $quiz, true) . '</div>';
- } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),
- $context)) { // Student
+ $str .= '<div class="info">' . quiz_num_attempt_summary($quiz, $quiz, true) . '</div>';
+
+ } else if (has_any_capability(array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'), $context)) { // Student
// For student-like people, tell them how many attempts they have made.
- if (isset($USER->id) &&
- ($attempts = quiz_get_user_attempts($quiz->id, $USER->id))) {
- $numattempts = count($attempts);
- $str .= '<div class="info">' .
- get_string('numattemptsmade', 'quiz', $numattempts) . '</div>';
+
+ if (isset($USER->id)) {
+ if ($attemptsinfo[$quiz->id]['hasfinished']) {
+ // The student's last attempt is finished.
+ continue;
+ }
+
+ if ($attemptsinfo[$quiz->id]['count'] > 0) {
+ $str .= '<div class="info">' .
+ get_string('numattemptsmade', 'quiz', $attemptsinfo[$quiz->id]['count']) . '</div>';
+ } else {
+ $str .= '<div class="info">' . $strnoattempts . '</div>';
+ }
+
} else {
$str .= '<div class="info">' . $strnoattempts . '</div>';
}
+
} else {
// For ayone else, there is no point listing this quiz, so stop processing.
continue;
}
- // Add the output for this quiz to the rest.
- $str .= '</div>';
+ // Give a link to the quiz, and the deadline.
+ $html = '<div class="quiz overview">' .
+ '<div class="name">' . $strquiz . ': <a ' .
+ ($quiz->visible ? '' : ' class="dimmed"') .
+ ' href="' . $CFG->wwwroot . '/mod/quiz/view.php?id=' .
+ $quiz->coursemodule . '">' .
+ $quiz->name . '</a></div>';
+ $html .= '<div class="info">' . get_string('quizcloseson', 'quiz',
+ userdate($quiz->timeclose)) . '</div>';
+ $html .= $str;
+ $html .= '</div>';
if (empty($htmlarray[$quiz->course]['quiz'])) {
- $htmlarray[$quiz->course]['quiz'] = $str;
+ $htmlarray[$quiz->course]['quiz'] = $html;
} else {
- $htmlarray[$quiz->course]['quiz'] .= $str;
+ $htmlarray[$quiz->course]['quiz'] .= $html;
}
}
}
* @uses exit. This method never returns.
*/
protected function finish_regrade($nexturl) {
- redirect($nexturl, get_string('regradecomplete', 'quiz_overview'), null, \core\output\notification::NOTIFY_SUCCESS);
+ global $OUTPUT;
+ \core\notification::success(get_string('regradecomplete', 'quiz_overview'));
+ echo $OUTPUT->continue_button($nexturl);
+ echo $OUTPUT->footer();
+ die();
}
/**
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
$this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return'));
}
+
+ public function test_quiz_get_user_attempts() {
+ global $DB;
+ $this->resetAfterTest();
+
+ $dg = $this->getDataGenerator();
+ $quizgen = $dg->get_plugin_generator('mod_quiz');
+ $course = $dg->create_course();
+ $u1 = $dg->create_user();
+ $u2 = $dg->create_user();
+ $u3 = $dg->create_user();
+ $u4 = $dg->create_user();
+ $role = $DB->get_record('role', ['shortname' => 'student']);
+
+ $dg->enrol_user($u1->id, $course->id, $role->id);
+ $dg->enrol_user($u2->id, $course->id, $role->id);
+ $dg->enrol_user($u3->id, $course->id, $role->id);
+ $dg->enrol_user($u4->id, $course->id, $role->id);
+
+ $quiz1 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]);
+ $quiz2 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]);
+
+ // Questions.
+ $questgen = $dg->get_plugin_generator('core_question');
+ $quizcat = $questgen->create_question_category();
+ $question = $questgen->create_question('numerical', null, ['category' => $quizcat->id]);
+ quiz_add_quiz_question($question->id, $quiz1);
+ quiz_add_quiz_question($question->id, $quiz2);
+
+ $quizobj1a = quiz::create($quiz1->id, $u1->id);
+ $quizobj1b = quiz::create($quiz1->id, $u2->id);
+ $quizobj1c = quiz::create($quiz1->id, $u3->id);
+ $quizobj1d = quiz::create($quiz1->id, $u4->id);
+ $quizobj2a = quiz::create($quiz2->id, $u1->id);
+
+ // Set attempts.
+ $quba1a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1a->get_context());
+ $quba1a->set_preferred_behaviour($quizobj1a->get_quiz()->preferredbehaviour);
+ $quba1b = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1b->get_context());
+ $quba1b->set_preferred_behaviour($quizobj1b->get_quiz()->preferredbehaviour);
+ $quba1c = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1c->get_context());
+ $quba1c->set_preferred_behaviour($quizobj1c->get_quiz()->preferredbehaviour);
+ $quba1d = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1d->get_context());
+ $quba1d->set_preferred_behaviour($quizobj1d->get_quiz()->preferredbehaviour);
+ $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
+ $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
+
+ $timenow = time();
+
+ // User 1 passes quiz 1.
+ $attempt = quiz_create_attempt($quizobj1a, 1, false, $timenow, false, $u1->id);
+ quiz_start_new_attempt($quizobj1a, $quba1a, $attempt, 1, $timenow);
+ quiz_attempt_save_started($quizobj1a, $quba1a, $attempt);
+ $attemptobj = quiz_attempt::create($attempt->id);
+ $attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]);
+ $attemptobj->process_finish($timenow, false);
+
+ // User 2 goes overdue in quiz 1.
+ $attempt = quiz_create_attempt($quizobj1b, 1, false, $timenow, false, $u2->id);
+ quiz_start_new_attempt($quizobj1b, $quba1b, $attempt, 1, $timenow);
+ quiz_attempt_save_started($quizobj1b, $quba1b, $attempt);
+ $attemptobj = quiz_attempt::create($attempt->id);
+ $attemptobj->process_going_overdue($timenow, true);
+
+ // User 3 does not finish quiz 1.
+ $attempt = quiz_create_attempt($quizobj1c, 1, false, $timenow, false, $u3->id);
+ quiz_start_new_attempt($quizobj1c, $quba1c, $attempt, 1, $timenow);
+ quiz_attempt_save_started($quizobj1c, $quba1c, $attempt);
+
+ // User 4 abandons the quiz 1.
+ $attempt = quiz_create_attempt($quizobj1d, 1, false, $timenow, false, $u4->id);
+ quiz_start_new_attempt($quizobj1d, $quba1d, $attempt, 1, $timenow);
+ quiz_attempt_save_started($quizobj1d, $quba1d, $attempt);
+ $attemptobj = quiz_attempt::create($attempt->id);
+ $attemptobj->process_abandon($timenow, true);
+
+ // User 1 attempts the quiz three times (abandon, finish, in progress).
+ $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
+ $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
+
+ $attempt = quiz_create_attempt($quizobj2a, 1, false, $timenow, false, $u1->id);
+ quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 1, $timenow);
+ quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
+ $attemptobj = quiz_attempt::create($attempt->id);
+ $attemptobj->process_abandon($timenow, true);
+
+ $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
+ $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
+
+ $attempt = quiz_create_attempt($quizobj2a, 2, false, $timenow, false, $u1->id);
+ quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 2, $timenow);
+ quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
+ $attemptobj = quiz_attempt::create($attempt->id);
+ $attemptobj->process_finish($timenow, false);
+
+ $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
+ $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
+
+ $attempt = quiz_create_attempt($quizobj2a, 3, false, $timenow, false, $u1->id);
+ quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 3, $timenow);
+ quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
+
+ // Check for user 1.
+ $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'all');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'finished');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'unfinished');
+ $this->assertCount(0, $attempts);
+
+ // Check for user 2.
+ $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'all');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state);
+ $this->assertEquals($u2->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'finished');
+ $this->assertCount(0, $attempts);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'unfinished');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state);
+ $this->assertEquals($u2->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ // Check for user 3.
+ $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'all');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
+ $this->assertEquals($u3->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'finished');
+ $this->assertCount(0, $attempts);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'unfinished');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
+ $this->assertEquals($u3->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ // Check for user 4.
+ $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'all');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
+ $this->assertEquals($u4->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'finished');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
+ $this->assertEquals($u4->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'unfinished');
+ $this->assertCount(0, $attempts);
+
+ // Multiple attempts for user 1 in quiz 2.
+ $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'all');
+ $this->assertCount(3, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+
+ $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'finished');
+ $this->assertCount(2, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+
+ $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'unfinished');
+ $this->assertCount(1, $attempts);
+ $attempt = array_shift($attempts);
+
+ // Multiple quiz attempts fetched at once.
+ $attempts = quiz_get_user_attempts([$quiz1->id, $quiz2->id], $u1->id, 'all');
+ $this->assertCount(4, $attempts);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz1->id, $attempt->quiz);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+ $attempt = array_shift($attempts);
+ $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
+ $this->assertEquals($u1->id, $attempt->userid);
+ $this->assertEquals($quiz2->id, $attempt->quiz);
+ }
+
}
}
$mform->addElement('radio', 'pageformat', '', get_string('format'.$format, 'wiki'), $format, $attr);
}
+ $mform->addRule('pageformat', get_string('required'), 'required', null, 'client');
}
$mform->setType('pageformat', PARAM_ALPHANUMEXT);
- $mform->addRule('pageformat', get_string('required'), 'required', null, 'client');
if (!empty($this->_customdata['groups']->availablegroups)) {
foreach ($this->_customdata['groups']->availablegroups as $groupdata) {
"from": "abbrev@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz"
},
+ "acorn": {
+ "version": "3.2.0",
+ "from": "acorn@>=3.1.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.2.0.tgz"
+ },
+ "acorn-jsx": {
+ "version": "3.0.1",
+ "from": "acorn-jsx@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz"
+ },
"align-text": {
"version": "0.1.3",
"from": "align-text@>=0.1.0 <0.2.0",
"from": "ansi-color@*",
"resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz"
},
+ "ansi-escapes": {
+ "version": "1.4.0",
+ "from": "ansi-escapes@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz"
+ },
"ansi-regex": {
"version": "2.0.0",
"from": "ansi-regex@>=2.0.0 <3.0.0",
}
}
},
+ "array-union": {
+ "version": "1.0.1",
+ "from": "array-union@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz"
+ },
+ "array-uniq": {
+ "version": "1.0.2",
+ "from": "array-uniq@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz"
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "from": "arrify@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz"
+ },
"asap": {
"version": "1.0.0",
"from": "asap@>=1.0.0 <1.1.0",
}
}
},
+ "bluebird": {
+ "version": "3.4.0",
+ "from": "bluebird@>=3.1.1 <4.0.0",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.0.tgz"
+ },
"boom": {
"version": "2.10.1",
"from": "boom@>=2.0.0 <3.0.0",
"from": "builtin-modules@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz"
},
+ "caller-path": {
+ "version": "0.1.0",
+ "from": "caller-path@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz"
+ },
+ "callsites": {
+ "version": "0.2.0",
+ "from": "callsites@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz"
+ },
"camelcase": {
"version": "2.0.1",
"from": "camelcase@>=2.0.0 <3.0.0",
}
}
},
+ "cli-cursor": {
+ "version": "1.0.2",
+ "from": "cli-cursor@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz"
+ },
+ "cli-width": {
+ "version": "2.1.0",
+ "from": "cli-width@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz"
+ },
"cliui": {
"version": "2.1.0",
"from": "cliui@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz"
},
+ "code-point-at": {
+ "version": "1.0.0",
+ "from": "code-point-at@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.0.0.tgz"
+ },
"coffee-script": {
"version": "1.3.3",
"from": "coffee-script@>=1.3.3 <1.4.0",
"from": "cssproc@>=0.0.1 <0.1.0",
"resolved": "https://registry.npmjs.org/cssproc/-/cssproc-0.0.7.tgz"
},
+ "d": {
+ "version": "0.1.1",
+ "from": "d@>=0.1.1 <0.2.0",
+ "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
+ },
"dashdash": {
"version": "1.12.1",
"from": "dashdash@>=1.10.1 <2.0.0",
"from": "decamelize@>=1.1.2 <2.0.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.1.2.tgz"
},
+ "deep-is": {
+ "version": "0.1.3",
+ "from": "deep-is@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
+ },
+ "del": {
+ "version": "2.2.0",
+ "from": "del@>=2.0.2 <3.0.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-2.2.0.tgz"
+ },
"delayed-stream": {
"version": "1.0.0",
"from": "delayed-stream@>=1.0.0 <1.1.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
},
+ "doctrine": {
+ "version": "1.2.2",
+ "from": "doctrine@>=1.2.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.2.2.tgz",
+ "dependencies": {
+ "esutils": {
+ "version": "1.1.6",
+ "from": "esutils@>=1.1.6 <2.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz"
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "from": "isarray@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
+ }
+ }
+ },
"dom-serializer": {
"version": "0.1.0",
"from": "dom-serializer@>=0.0.0 <1.0.0",
"from": "error-ex@>=1.2.0 <2.0.0",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.0.tgz"
},
+ "es5-ext": {
+ "version": "0.10.11",
+ "from": "es5-ext@>=0.10.8 <0.11.0",
+ "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.11.tgz"
+ },
+ "es6-iterator": {
+ "version": "2.0.0",
+ "from": "es6-iterator@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz"
+ },
+ "es6-map": {
+ "version": "0.1.4",
+ "from": "es6-map@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz",
+ "dependencies": {
+ "es6-symbol": {
+ "version": "3.1.0",
+ "from": "es6-symbol@>=3.1.0 <3.2.0",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz"
+ }
+ }
+ },
+ "es6-set": {
+ "version": "0.1.4",
+ "from": "es6-set@>=0.1.3 <0.2.0",
+ "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz"
+ },
+ "es6-symbol": {
+ "version": "3.0.2",
+ "from": "es6-symbol@>=3.0.1 <3.1.0",
+ "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.2.tgz"
+ },
+ "es6-weak-map": {
+ "version": "2.0.1",
+ "from": "es6-weak-map@>=2.0.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz"
+ },
"escape-string-regexp": {
"version": "1.0.4",
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
"from": "escodegen@>=0.0.0 <0.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-0.0.28.tgz"
},
+ "escope": {
+ "version": "3.6.0",
+ "from": "escope@>=3.6.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz",
+ "dependencies": {
+ "estraverse": {
+ "version": "4.2.0",
+ "from": "estraverse@4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
+ }
+ }
+ },
+ "eslint": {
+ "version": "2.12.0",
+ "from": "eslint@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-2.12.0.tgz",
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "from": "ansi-styles@>=2.2.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
+ },
+ "argparse": {
+ "version": "1.0.7",
+ "from": "argparse@>=1.0.7 <2.0.0",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.7.tgz"
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "from": "chalk@>=1.1.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
+ },
+ "debug": {
+ "version": "2.2.0",
+ "from": "debug@>=2.1.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
+ },
+ "esprima": {
+ "version": "2.7.2",
+ "from": "esprima@>=2.6.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.2.tgz"
+ },
+ "estraverse": {
+ "version": "4.2.0",
+ "from": "estraverse@>=4.2.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
+ },
+ "glob": {
+ "version": "7.0.3",
+ "from": "glob@>=7.0.3 <8.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.3.tgz"
+ },
+ "js-yaml": {
+ "version": "3.6.1",
+ "from": "js-yaml@>=3.5.1 <4.0.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz"
+ },
+ "lodash": {
+ "version": "4.13.1",
+ "from": "lodash@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.13.1.tgz"
+ },
+ "minimatch": {
+ "version": "3.0.0",
+ "from": "minimatch@3.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz"
+ },
+ "progress": {
+ "version": "1.1.8",
+ "from": "progress@>=1.1.8 <2.0.0",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz"
+ },
+ "shelljs": {
+ "version": "0.6.0",
+ "from": "shelljs@>=0.6.0 <0.7.0",
+ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.0.tgz"
+ }
+ }
+ },
+ "espree": {
+ "version": "3.1.4",
+ "from": "espree@3.1.4",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-3.1.4.tgz"
+ },
"esprima": {
"version": "1.0.4",
"from": "esprima@>=1.0.2 <1.1.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz"
},
+ "esrecurse": {
+ "version": "4.1.0",
+ "from": "esrecurse@>=4.1.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
+ "dependencies": {
+ "estraverse": {
+ "version": "4.1.1",
+ "from": "estraverse@>=4.1.0 <4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz"
+ }
+ }
+ },
"estraverse": {
"version": "1.3.2",
"from": "estraverse@>=1.3.0 <1.4.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.3.2.tgz"
},
+ "esutils": {
+ "version": "2.0.2",
+ "from": "esutils@>=2.0.2 <3.0.0",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
+ },
+ "event-emitter": {
+ "version": "0.3.4",
+ "from": "event-emitter@>=0.3.4 <0.4.0",
+ "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz"
+ },
"eventemitter2": {
"version": "0.4.14",
"from": "eventemitter2@>=0.4.13 <0.5.0",
"from": "exit@>=0.1.1 <0.2.0",
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz"
},
+ "exit-hook": {
+ "version": "1.1.1",
+ "from": "exit-hook@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz"
+ },
"extend": {
"version": "3.0.0",
"from": "extend@>=3.0.0 <3.1.0",
"from": "extsprintf@1.0.2",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz"
},
+ "fast-levenshtein": {
+ "version": "1.1.3",
+ "from": "fast-levenshtein@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.3.tgz"
+ },
"faye-websocket": {
"version": "0.4.4",
"from": "faye-websocket@>=0.4.3 <0.5.0",
"from": "figures@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz"
},
+ "file-entry-cache": {
+ "version": "1.2.4",
+ "from": "file-entry-cache@>=1.1.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.2.4.tgz"
+ },
"fileset": {
"version": "0.1.8",
"from": "fileset@>=0.1.0 <0.2.0",
}
}
},
+ "flat-cache": {
+ "version": "1.0.10",
+ "from": "flat-cache@>=1.0.9 <2.0.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.0.10.tgz",
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.4",
+ "from": "graceful-fs@>=4.1.2 <5.0.0",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz"
+ }
+ }
+ },
"forever-agent": {
"version": "0.6.1",
"from": "forever-agent@>=0.6.1 <0.7.0",
}
}
},
+ "globals": {
+ "version": "9.8.0",
+ "from": "globals@>=9.2.0 <10.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.8.0.tgz"
+ },
+ "globby": {
+ "version": "4.1.0",
+ "from": "globby@>=4.0.0 <5.0.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz",
+ "dependencies": {
+ "glob": {
+ "version": "6.0.4",
+ "from": "glob@>=6.0.1 <7.0.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz"
+ },
+ "minimatch": {
+ "version": "3.0.0",
+ "from": "minimatch@>=2.0.0 <3.0.0||>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz"
+ }
+ }
+ },
"globule": {
"version": "0.1.0",
"from": "globule@>=0.1.0 <0.2.0",
}
}
},
+ "grunt-eslint": {
+ "version": "18.1.0",
+ "from": "grunt-eslint@>=18.1.0 <19.0.0",
+ "resolved": "https://registry.npmjs.org/grunt-eslint/-/grunt-eslint-18.1.0.tgz"
+ },
"grunt-legacy-log": {
"version": "0.1.3",
"from": "grunt-legacy-log@>=0.1.0 <0.2.0",
"from": "iconv-lite@>=0.2.11 <0.3.0",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz"
},
+ "ignore": {
+ "version": "3.1.2",
+ "from": "ignore@>=3.1.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.1.2.tgz"
+ },
"image-size": {
"version": "0.3.5",
"from": "image-size@>=0.3.5 <0.4.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-0.3.5.tgz"
},
+ "imurmurhash": {
+ "version": "0.1.4",
+ "from": "imurmurhash@>=0.1.4 <0.2.0",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ },
"indent-string": {
"version": "2.1.0",
"from": "indent-string@>=2.1.0 <3.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz"
},
+ "inflight": {
+ "version": "1.0.5",
+ "from": "inflight@>=1.0.4 <2.0.0",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.5.tgz"
+ },
"inherits": {
"version": "2.0.1",
"from": "inherits@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
},
+ "inquirer": {
+ "version": "0.12.0",
+ "from": "inquirer@>=0.12.0 <0.13.0",
+ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz",
+ "dependencies": {
+ "lodash": {
+ "version": "4.13.1",
+ "from": "lodash@>=4.3.0 <5.0.0"
+ }
+ }
+ },
"is-arrayish": {
"version": "0.2.1",
"from": "is-arrayish@>=0.2.1 <0.3.0",
"from": "is-finite@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz"
},
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
+ },
"is-my-json-valid": {
"version": "2.12.3",
"from": "is-my-json-valid@>=2.12.3 <3.0.0",
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.3.tgz"
},
+ "is-path-cwd": {
+ "version": "1.0.0",
+ "from": "is-path-cwd@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz"
+ },
+ "is-path-in-cwd": {
+ "version": "1.0.0",
+ "from": "is-path-in-cwd@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz"
+ },
+ "is-path-inside": {
+ "version": "1.0.0",
+ "from": "is-path-inside@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz"
+ },
"is-property": {
"version": "1.0.2",
"from": "is-property@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
},
+ "is-resolvable": {
+ "version": "1.0.0",
+ "from": "is-resolvable@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz"
+ },
"is-typedarray": {
"version": "1.0.0",
"from": "is-typedarray@>=1.0.0 <1.1.0",
"from": "json-schema@0.2.2",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.2.tgz"
},
+ "json-stable-stringify": {
+ "version": "1.0.1",
+ "from": "json-stable-stringify@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
+ },
"json-stringify-safe": {
"version": "5.0.1",
"from": "json-stringify-safe@>=5.0.1 <5.1.0",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz"
},
+ "jsonify": {
+ "version": "0.0.0",
+ "from": "jsonify@>=0.0.0 <0.1.0",
+ "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
+ },
"jsonpointer": {
"version": "2.0.0",
"from": "jsonpointer@2.0.0",
}
}
},
+ "levn": {
+ "version": "0.3.0",
+ "from": "levn@>=0.3.0 <0.4.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz"
+ },
"load-json-file": {
"version": "1.1.0",
"from": "load-json-file@>=1.0.0 <2.0.0",
"from": "mkdirp@>=0.5.0 <0.6.0",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
},
+ "ms": {
+ "version": "0.7.1",
+ "from": "ms@0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
+ },
+ "mute-stream": {
+ "version": "0.0.5",
+ "from": "mute-stream@0.0.5",
+ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz"
+ },
"node-uuid": {
"version": "1.4.7",
"from": "node-uuid@>=1.4.7 <1.5.0",
"from": "object-assign@>=4.0.1 <5.0.0",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz"
},
+ "once": {
+ "version": "1.3.3",
+ "from": "once@>=1.3.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz"
+ },
+ "onetime": {
+ "version": "1.1.0",
+ "from": "onetime@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
+ },
"optimist": {
"version": "0.3.7",
"from": "optimist@>=0.3.0 <0.4.0",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.3.7.tgz"
},
+ "optionator": {
+ "version": "0.8.1",
+ "from": "optionator@>=0.8.1 <0.9.0",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.1.tgz",
+ "dependencies": {
+ "wordwrap": {
+ "version": "1.0.0",
+ "from": "wordwrap@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
+ }
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.1",
+ "from": "os-homedir@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.1.tgz"
+ },
"pako": {
"version": "0.2.8",
"from": "pako@>=0.2.0 <0.3.0",
"from": "path-exists@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz"
},
+ "path-is-absolute": {
+ "version": "1.0.0",
+ "from": "path-is-absolute@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz"
+ },
+ "path-is-inside": {
+ "version": "1.0.1",
+ "from": "path-is-inside@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz"
+ },
"path-type": {
"version": "1.1.0",
"from": "path-type@>=1.0.0 <2.0.0",
"from": "pinkie-promise@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.0.tgz"
},
+ "pluralize": {
+ "version": "1.2.1",
+ "from": "pluralize@>=1.2.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz"
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "from": "prelude-ls@>=1.1.2 <1.2.0",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
+ },
"pretty-bytes": {
"version": "1.0.4",
"from": "pretty-bytes@>=1.0.0 <2.0.0",
"from": "qs@>=5.2.0 <5.3.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz"
},
+ "read-json-sync": {
+ "version": "1.1.1",
+ "from": "read-json-sync@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/read-json-sync/-/read-json-sync-1.1.1.tgz",
+ "dependencies": {
+ "graceful-fs": {
+ "version": "4.1.4",
+ "from": "graceful-fs@4.1.4",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.4.tgz"
+ }
+ }
+ },
"read-pkg": {
"version": "1.1.0",
"from": "read-pkg@>=1.0.0 <2.0.0",
"from": "readable-stream@>=1.1.0 <1.2.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz"
},
+ "readline2": {
+ "version": "1.0.1",
+ "from": "readline2@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz"
+ },
"redent": {
"version": "1.0.0",
"from": "redent@>=1.0.0 <2.0.0",
"from": "request@>=2.51.0 <3.0.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.67.0.tgz"
},
+ "require-uncached": {
+ "version": "1.0.2",
+ "from": "require-uncached@>=1.0.2 <2.0.0",
+ "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.2.tgz"
+ },
"resolve": {
"version": "0.4.3",
"from": "resolve@>=0.4.0 <0.5.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-0.4.3.tgz"
},
+ "resolve-from": {
+ "version": "1.0.1",
+ "from": "resolve-from@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz"
+ },
+ "restore-cursor": {
+ "version": "1.0.1",
+ "from": "restore-cursor@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
+ },
"right-align": {
"version": "0.1.3",
"from": "right-align@>=0.1.1 <0.2.0",
"from": "rimraf@>=2.2.8 <2.3.0",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz"
},
+ "run-async": {
+ "version": "0.1.0",
+ "from": "run-async@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz"
+ },
+ "rx-lite": {
+ "version": "3.1.2",
+ "from": "rx-lite@>=3.1.2 <4.0.0",
+ "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz"
+ },
"sax": {
"version": "0.5.8",
"from": "sax@>=0.5.0 <0.6.0",
"from": "signal-exit@>=2.1.2 <3.0.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-2.1.2.tgz"
},
+ "slice-ansi": {
+ "version": "0.0.4",
+ "from": "slice-ansi@0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz"
+ },
"sntp": {
"version": "1.0.9",
"from": "sntp@>=1.0.0 <2.0.0",
"from": "spdx-license-ids@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.0.tgz"
},
+ "sprintf-js": {
+ "version": "1.0.3",
+ "from": "sprintf-js@>=1.0.2 <1.1.0",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
+ },
"sshpk": {
"version": "1.7.3",
"from": "sshpk@>=1.7.0 <2.0.0",
"from": "string_decoder@>=0.10.0 <0.11.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
},
+ "string-width": {
+ "version": "1.0.1",
+ "from": "string-width@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.1.tgz"
+ },
"stringstream": {
"version": "0.0.5",
"from": "stringstream@>=0.0.4 <0.1.0",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz"
},
"strip-ansi": {
- "version": "3.0.0",
+ "version": "3.0.1",
"from": "strip-ansi@>=3.0.0 <4.0.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz"
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
},
"strip-bom": {
"version": "2.0.0",
"from": "supports-color@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
},
+ "table": {
+ "version": "3.7.8",
+ "from": "table@>=3.7.8 <4.0.0",
+ "resolved": "https://registry.npmjs.org/table/-/table-3.7.8.tgz",
+ "dependencies": {
+ "lodash": {
+ "version": "4.13.1",
+ "from": "lodash@>=4.0.0 <5.0.0"
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "from": "text-table@>=0.2.0 <0.3.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
+ },
+ "through": {
+ "version": "2.3.8",
+ "from": "through@>=2.3.6 <3.0.0",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+ },
"timethat": {
"version": "0.0.3",
"from": "timethat@>=0.0.1 <0.1.0",
"from": "trim-newlines@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz"
},
+ "tryit": {
+ "version": "1.0.2",
+ "from": "tryit@>=1.0.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.2.tgz"
+ },
"tunnel-agent": {
"version": "0.4.2",
"from": "tunnel-agent@>=0.4.1 <0.5.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.2.tgz"
},
+ "tv4": {
+ "version": "1.2.7",
+ "from": "tv4@>=1.2.7 <2.0.0",
+ "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.2.7.tgz"
+ },
"tweetnacl": {
"version": "0.13.3",
"from": "tweetnacl@>=0.13.0 <1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.13.3.tgz"
},
+ "type-check": {
+ "version": "0.3.2",
+ "from": "type-check@>=0.3.2 <0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
+ },
"typedarray": {
"version": "0.0.6",
"from": "typedarray@>=0.0.5 <0.1.0",
"from": "uri-path@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz"
},
+ "user-home": {
+ "version": "2.0.0",
+ "from": "user-home@>=2.0.0 <3.0.0",
+ "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz"
+ },
"util-deprecate": {
"version": "1.0.2",
"from": "util-deprecate@>=1.0.1 <1.1.0",
"from": "wordwrap@0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz"
},
+ "wrappy": {
+ "version": "1.0.2",
+ "from": "wrappy@>=1.0.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+ },
+ "write": {
+ "version": "0.2.1",
+ "from": "write@>=0.2.1 <0.3.0",
+ "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz"
+ },
"xml2js": {
"version": "0.2.8",
"from": "xml2js@>=0.2.0 <0.3.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.2.8.tgz"
},
+ "xmldom": {
+ "version": "0.1.22",
+ "from": "xmldom@latest",
+ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.22.tgz"
+ },
+ "xpath": {
+ "version": "0.0.23",
+ "from": "xpath@latest",
+ "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.23.tgz"
+ },
+ "xregexp": {
+ "version": "3.1.1",
+ "from": "xregexp@>=3.0.0 <4.0.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-3.1.1.tgz"
+ },
"xtend": {
"version": "4.0.1",
"from": "xtend@>=4.0.0 <5.0.0",
{
- "name": "Moodle",
- "private": true,
- "description": "Moodle",
- "devDependencies": {
- "async": "^1.5.2",
- "grunt": "0.4.5",
- "grunt-contrib-jshint": "0.11.3",
- "grunt-contrib-less": "1.1.0",
- "grunt-contrib-uglify": "0.11.0",
- "grunt-contrib-watch": "0.6.1",
- "shifter": "0.5.0"
- }
+ "name": "Moodle",
+ "private": true,
+ "description": "Moodle",
+ "devDependencies": {
+ "async": "^1.5.2",
+ "grunt": "0.4.5",
+ "grunt-contrib-jshint": "0.11.3",
+ "grunt-contrib-less": "1.1.0",
+ "grunt-contrib-uglify": "0.11.0",
+ "grunt-contrib-watch": "0.6.1",
+ "grunt-eslint": "^18.1.0",
+ "shifter": "0.5.0",
+ "xmldom": "^0.1.22",
+ "xpath": "0.0.23"
+ }
}
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>\r
+<!-- Generator: Adobe Illustrator 15.1.0, SVG Export Plug-In -->\r
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [\r
+ <!ENTITY ns_flows "http://ns.adobe.com/Flows/1.0/">\r
+]>\r
+<svg version="1.1"\r
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/"\r
+ x="0px" y="0px" width="16px" height="16px" viewBox="-0.042 -0.036 16 16"\r
+ style="overflow:visible;enable-background:new -0.042 -0.036 16 16;" xml:space="preserve">\r
+<defs>\r
+</defs>\r
+<path style="fill:#999999;" d="M15.821,15.071c0.248,0.491,0,0.893-0.55,0.893H0.646c-0.55,0-0.797-0.401-0.55-0.893l1.566-3.107\r
+ h12.592L15.821,15.071z M4.184,6.964l-2.017,4H13.75l-2.017-4H4.184z M11.23,5.964L8.409,0.369c-0.248-0.491-0.653-0.491-0.9,0\r
+ L4.688,5.964H11.23z"/>\r
+</svg>\r
<?php
require_once($CFG->libdir . '/portfoliolib.php');
+require_once($CFG->libdir . '/portfolio/plugin.php');
class portfolio_plugin_download extends portfolio_plugin_pull_base {
* @param \stdClass $tagarea
*/
public function __construct($tagarea) {
+ if (!empty($tagarea->locked)) {
+ // If the tag collection for the current tag area is locked, display the
+ // name of the collection without possibility to edit it.
+ $tagcoll = \core_tag_collection::get_by_id($tagarea->tagcollid);
+ parent::__construct('core_tag', 'tagareacollection', $tagarea->id, false,
+ \core_tag_collection::display_name($tagcoll), $tagarea->tagcollid);
+ return;
+ }
+
$tagcollections = \core_tag_collection::get_collections_menu(true);
- $editable = (count($tagcollections) > 1) && empty($tagarea->locked) &&
+ $editable = (count($tagcollections) > 1) &&
has_capability('moodle/tag:manage', context_system::instance());
$areaname = core_tag_area::display_name($tagarea->component, $tagarea->itemtype);
$edithint = new lang_string('edittagcollection', 'core_tag');
require_once($CFG->dirroot . '/' . ltrim($tagarea->callbackfile, '/'));
}
$callback = $tagarea->callback;
- return $callback($this, $exclusivemode, $fromctx, $ctx, $rec, $page);
+ return call_user_func_array($callback, [$this, $exclusivemode, $fromctx, $ctx, $rec, $page]);
}
return null;
}
$reset = optional_param('reset', 0, PARAM_BOOL);
$device = optional_param('device', '', PARAM_TEXT);
$unsettheme = optional_param('unsettheme', 0, PARAM_BOOL);
+$confirmation = optional_param('confirmation', 0, PARAM_BOOL);
admin_externalpage_setup('themeselector');
if ($reset and confirm_sesskey()) {
theme_reset_all_caches();
+} else if ($choose && $confirmation) {
+
+ $theme = theme_config::load($choose);
+ echo $OUTPUT->header();
+ echo $OUTPUT->heading(get_string('themesaved'));
+ echo $OUTPUT->box_start();
+ echo format_text(get_string('choosereadme', 'theme_'.$theme->name), FORMAT_MOODLE);
+ echo $OUTPUT->box_end();
+ echo $OUTPUT->continue_button($CFG->wwwroot . '/theme/index.php');
+ echo $OUTPUT->footer();
+ exit;
} else if ($choose && $device && !theme_is_device_locked($device) && !$unsettheme && confirm_sesskey()) {
// Load the theme to make sure it is valid.
$themename = core_useragent::get_device_type_cfg_var_name($device);
set_config($themename, $theme->name);
- // Create a new page for the display of the themes readme.
- // This ensures that the readme page is shown using the new theme.
- $confirmpage = new moodle_page();
- $confirmpage->set_context($PAGE->context);
- $confirmpage->set_url($PAGE->url);
- $confirmpage->set_pagelayout($PAGE->pagelayout);
- $confirmpage->set_pagetype($PAGE->pagetype);
- $confirmpage->set_title($PAGE->title);
- $confirmpage->set_heading($PAGE->heading);
-
- // Get the core renderer for the new theme.
- $output = $confirmpage->get_renderer('core');
-
- echo $output->header();
- echo $output->heading(get_string('themesaved'));
- echo $output->box_start();
- echo format_text(get_string('choosereadme', 'theme_'.$theme->name), FORMAT_MOODLE);
- echo $output->box_end();
- echo $output->continue_button($CFG->wwwroot . '/theme/index.php');
- echo $output->footer();
- exit;
+ $urlconfirm = new moodle_url('/theme/index.php', array('confirmation' => 1, 'choose' => $choose));
+ redirect($urlconfirm);
} else if ($device && !theme_is_device_locked($device) && $unsettheme && confirm_sesskey() && ($device != 'default')) {
// Unset the theme and continue.
unset_config(core_useragent::get_device_type_cfg_var_name($device));
defined('MOODLE_INTERNAL') || die();
-$version = 2016052300.02; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2016052300.03; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
-$release = '3.2dev (Build: 20160603)'; // Human-friendly version name
+$release = '3.2dev (Build: 20160609)'; // Human-friendly version name
$branch = '32'; // This version's branch.
$maturity = MATURITY_ALPHA; // This version's maturity level.