diff --git a/.gitignore b/.gitignore
index 0eae1ca082..689569b31b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,8 +23,6 @@ package-lock.json
/plugins/content/menu/versions/
/plugins/content/bower/bowercache/
-/routes/lang/en.json
-
/temp
/test/.testcache
/tmp
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bdc24a2fc..1e3e4a0631 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,25 @@ All notable changes to the Adapt authoring tool are documented in this file.
**IMPORTANT**: For information on how to **correctly and safely** update your installation, please consult **INSTALL.md**.
_Note that we adhere to the [semantic versioning](http://semver.org/) scheme for release numbering._
+## [0.10.1] - 2019-10-22
+
+Bugfix release.
+
+### Fixed
+- Courses dashboard: A-Z/Z-A sort is case sensitive ([#2325](https://github.com/adaptlearning/adapt_authoring/issues/2325))
+- Item copy can result in broken courseassets in other items ([#2347](https://github.com/adaptlearning/adapt_authoring/issues/2347))
+- Allow non-interactive install and upgrade scripts ([#2407](https://github.com/adaptlearning/adapt_authoring/issues/2407))
+- installation: missing translation key for app.productname ([#2410](https://github.com/adaptlearning/adapt_authoring/issues/2410))
+- Fix reading of asset type from schema ([#2416](https://github.com/adaptlearning/adapt_authoring/issues/2416))
+- Grunt tasks do not process symlinks ([#2428](https://github.com/adaptlearning/adapt_authoring/issues/2428))
+- Importing plugin with existing targetAttribute causes error when retrieving plugin schemas ([#2433](https://github.com/adaptlearning/adapt_authoring/issues/2433))
+- Support Node 12 ([#2437](https://github.com/adaptlearning/adapt_authoring/issues/2437))
+- Asset tags are not preserved on import ([#2439](https://github.com/adaptlearning/adapt_authoring/issues/2439))
+
+### Added
+- skip-version check should be passed as cli argument ([#2005](https://github.com/adaptlearning/adapt_authoring/issues/2005))
+- Plugin upload failed modal should be more descriptive ([#2444](https://github.com/adaptlearning/adapt_authoring/issues/2444))
+
## [0.10.0] - 2019-08-29
Adds ability to import courses with an older framework version, and latest bugfixes.
@@ -640,6 +659,7 @@ Initial release.
- Loading screen of death
- Session cookie security issues
+[0.10.1]: https://github.com/adaptlearning/adapt_authoring/compare/v0.10.0...v0.10.1
[0.10.0]: https://github.com/adaptlearning/adapt_authoring/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/adaptlearning/adapt_authoring/compare/v0.8.1...v0.9.0
[0.8.1]: https://github.com/adaptlearning/adapt_authoring/compare/v0.8.0...v0.8.1
diff --git a/Gruntfile.js b/Gruntfile.js
index cf3cd18a2d..e6ea02b031 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -4,13 +4,14 @@ module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
- "merge-json": {
- en: {
- src: [
- 'routes/lang/en-application.json',
- 'frontend/src/**/lang/en.json'
- ],
- dest: 'routes/lang/en.json'
+ 'generate-lang-json': {
+ options: {
+ langFileExt: '.json',
+ src: {
+ backend: 'routes/lang',
+ frontend: 'frontend/src/**/lang'
+ },
+ dest: 'temp/lang'
}
},
copy: {
@@ -87,13 +88,17 @@ module.exports = function(grunt) {
partialRegex: /^part_/,
partialsPathRegex: /\/partials\//
},
- files: {
- "frontend/src/templates/templates.js": [
- "frontend/src/core/**/*.hbs",
- "frontend/src/modules/**/*.hbs",
- "frontend/src/plugins/**/*.hbs"
- ]
- }
+ files: [
+ {
+ follow: true,
+ src: [
+ 'frontend/src/core/**/*.hbs',
+ 'frontend/src/modules/**/*.hbs',
+ 'frontend/src/plugins/**/*.hbs'
+ ],
+ dest: 'frontend/src/templates/templates.js'
+ }
+ ]
}
},
requirejs: {
@@ -217,7 +222,10 @@ module.exports = function(grunt) {
var ret = '';
for (var i = 0, l = src.length; i < l; i++) {
- grunt.file.expand({ filter: options.filter }, src[i]).forEach(function(lessPath) {
+ grunt.file.expand({
+ filter: options.filter,
+ follow: true
+ }, src[i]).forEach(function(lessPath) {
ret += '@import \'' + path.normalize(lessPath) + '\';\n';
});
}
@@ -262,6 +270,30 @@ module.exports = function(grunt) {
done();
}
});
+ grunt.registerTask('generate-lang-json', function() {
+ const fs = require('fs-extra');
+ const path = require('path');
+
+ const options = this.options();
+ const backendGlob = path.join(options.src.backend, `*${options.langFileExt}`);
+ const dest = options.dest;
+ // load each route lang file
+ /**
+ * NOTE there must be a file in routes/lang for the language to be loaded,
+ * won't work if you've only got lang files in frontend
+ */
+ grunt.file.expand({}, path.join(backendGlob)).forEach(backendPath => {
+ const basename = path.basename(backendPath);
+ const frontendGlob = path.join(options.src.frontend, basename);
+ let data = { ...fs.readJSONSync(backendPath) };
+ // load all matching frontend lang files
+ grunt.file.expand({}, frontendGlob).forEach(frontendPath => {
+ data = { ...data, ...fs.readJSONSync(frontendPath) };
+ });
+ fs.ensureDirSync(dest);
+ fs.writeJSONSync(path.join(dest, basename), data, { spaces: 2 });
+ });
+ });
grunt.registerTask('default', ['build:dev']);
grunt.registerTask('test', ['mochaTest']);
@@ -283,7 +315,7 @@ module.exports = function(grunt) {
config.isProduction = isProduction;
grunt.file.write(configFile, JSON.stringify(config, null, 2));
// run the task
- grunt.task.run(['migration-conf', 'requireBundle', 'merge-json', 'copy', 'less:' + compilation, 'handlebars', 'requirejs:'+ compilation]);
+ grunt.task.run(['migration-conf', 'requireBundle', 'generate-lang-json', 'copy', 'less:' + compilation, 'handlebars', 'requirejs:'+ compilation]);
} catch(e) {
grunt.task.run(['requireBundle', 'copy', 'less:' + compilation, 'handlebars', 'requirejs:' + compilation]);
diff --git a/frontend/src/core/helpers.js b/frontend/src/core/helpers.js
index 619de89ed7..5c8269b45c 100644
--- a/frontend/src/core/helpers.js
+++ b/frontend/src/core/helpers.js
@@ -172,12 +172,6 @@ define(function(require){
return new Handlebars.SafeString(html + '');
},
- decodeHTML: function(html) {
- var el = document.createElement('div');
- el.innerHTML = html;
- return el.childNodes.length === 0 ? "" : el.childNodes[0].nodeValue;
- },
-
ifHasPermissions: function(permissions, block) {
var hasPermission = Origin.permissions.hasPermissions(permissions.split(','));
return hasPermission ? block.fn(this) : block.inverse(this);
@@ -342,6 +336,27 @@ define(function(require){
'',
Origin.l10n.t('app.maxfileuploadsize', {size: Origin.constants.humanMaxFileUploadSize}),
''].join(''))
+ },
+
+ flattenNestedProperties: function(properties) {
+ if (!properties) return {};
+ var flatProperties = {};
+ for (var key in properties) {
+ // Check for nested properties
+ if (typeof properties[key] === 'object') {
+ for (var innerKey in properties[key]) {
+ // Check if key already exists
+ if (flatProperties[innerKey]) {
+ flatProperties[key+'.'+innerKey] = properties[key][innerKey];
+ } else {
+ flatProperties[innerKey] = properties[key][innerKey];
+ }
+ }
+ } else {
+ flatProperties[key] = properties[key];
+ }
+ }
+ return flatProperties;
}
};
diff --git a/frontend/src/libraries/handlebars.js b/frontend/src/libraries/handlebars.js
index 6fc8bcb6eb..f3d6a8eabe 100644
--- a/frontend/src/libraries/handlebars.js
+++ b/frontend/src/libraries/handlebars.js
@@ -1,7 +1,7 @@
/**!
@license
- handlebars v4.0.11
+ handlebars v4.4.0
Copyright (C) 2011-2017 by Yehuda Katz
@@ -275,11 +275,13 @@ return /******/ (function(modules) { // webpackBootstrap
var _logger2 = _interopRequireDefault(_logger);
- var VERSION = '4.0.11';
+ var VERSION = '4.4.0';
exports.VERSION = VERSION;
- var COMPILER_REVISION = 7;
-
+ var COMPILER_REVISION = 8;
exports.COMPILER_REVISION = COMPILER_REVISION;
+ var LAST_COMPATIBLE_COMPILER_REVISION = 7;
+
+ exports.LAST_COMPATIBLE_COMPILER_REVISION = LAST_COMPATIBLE_COMPILER_REVISION;
var REVISION_CHANGES = {
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
2: '== 1.0.0-rc.3',
@@ -287,7 +289,8 @@ return /******/ (function(modules) { // webpackBootstrap
4: '== 1.x.x',
5: '== 2.0.0-alpha.x',
6: '>= 2.0.0-beta.1',
- 7: '>= 4.0.0'
+ 7: '>= 4.0.0 <4.3.0',
+ 8: '>= 4.3.0'
};
exports.REVISION_CHANGES = REVISION_CHANGES;
@@ -371,6 +374,7 @@ return /******/ (function(modules) { // webpackBootstrap
exports.createFrame = createFrame;
exports.blockParams = blockParams;
exports.appendContextPath = appendContextPath;
+
var escape = {
'&': '&',
'<': '<',
@@ -588,6 +592,7 @@ return /******/ (function(modules) { // webpackBootstrap
exports.__esModule = true;
exports.registerDefaultHelpers = registerDefaultHelpers;
+ exports.moveHelperToHooks = moveHelperToHooks;
var _helpersBlockHelperMissing = __webpack_require__(11);
@@ -627,6 +632,15 @@ return /******/ (function(modules) { // webpackBootstrap
_helpersWith2['default'](instance);
}
+ function moveHelperToHooks(instance, helperName, keepHelper) {
+ if (instance.helpers[helperName]) {
+ instance.hooks[helperName] = instance.helpers[helperName];
+ if (!keepHelper) {
+ delete instance.helpers[helperName];
+ }
+ }
+ }
+
/***/ }),
/* 11 */
/***/ (function(module, exports, __webpack_require__) {
@@ -674,7 +688,7 @@ return /******/ (function(modules) { // webpackBootstrap
/* 12 */
/***/ (function(module, exports, __webpack_require__) {
- 'use strict';
+ /* WEBPACK VAR INJECTION */(function(global) {'use strict';
var _interopRequireDefault = __webpack_require__(1)['default'];
@@ -736,6 +750,16 @@ return /******/ (function(modules) { // webpackBootstrap
execIteration(i, i, i === context.length - 1);
}
}
+ } else if (global.Symbol && context[global.Symbol.iterator]) {
+ var newContext = [];
+ var iterator = context[global.Symbol.iterator]();
+ for (var it = iterator.next(); !it.done; it = iterator.next()) {
+ newContext.push(it.value);
+ }
+ context = newContext;
+ for (var j = context.length; i < j; i++) {
+ execIteration(i, i, i === context.length - 1);
+ }
} else {
var priorKey = undefined;
@@ -766,6 +790,7 @@ return /******/ (function(modules) { // webpackBootstrap
};
module.exports = exports['default'];
+ /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }())))
/***/ }),
/* 13 */
@@ -868,7 +893,13 @@ return /******/ (function(modules) { // webpackBootstrap
exports['default'] = function (instance) {
instance.registerHelper('lookup', function (obj, field) {
- return obj && obj[field];
+ if (!obj) {
+ return obj;
+ }
+ if (field === 'constructor' && !obj.propertyIsEnumerable(field)) {
+ return undefined;
+ }
+ return obj[field];
});
};
@@ -1063,23 +1094,28 @@ return /******/ (function(modules) { // webpackBootstrap
var _base = __webpack_require__(4);
+ var _helpers = __webpack_require__(10);
+
function checkRevision(compilerInfo) {
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
currentRevision = _base.COMPILER_REVISION;
- if (compilerRevision !== currentRevision) {
- if (compilerRevision < currentRevision) {
- var runtimeVersions = _base.REVISION_CHANGES[currentRevision],
- compilerVersions = _base.REVISION_CHANGES[compilerRevision];
- throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
- } else {
- // Use the embedded version info since the runtime doesn't know about this revision yet
- throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
- }
+ if (compilerRevision >= _base.LAST_COMPATIBLE_COMPILER_REVISION && compilerRevision <= _base.COMPILER_REVISION) {
+ return;
+ }
+
+ if (compilerRevision < _base.LAST_COMPATIBLE_COMPILER_REVISION) {
+ var runtimeVersions = _base.REVISION_CHANGES[currentRevision],
+ compilerVersions = _base.REVISION_CHANGES[compilerRevision];
+ throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
+ } else {
+ // Use the embedded version info since the runtime doesn't know about this revision yet
+ throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
}
}
function template(templateSpec, env) {
+
/* istanbul ignore next */
if (!env) {
throw new _exception2['default']('No environment passed to template');
@@ -1091,9 +1127,12 @@ return /******/ (function(modules) { // webpackBootstrap
templateSpec.main.decorator = templateSpec.main_d;
// Note: Using env.VM references rather than local var references throughout this section to allow
- // for external users to override these as psuedo-supported APIs.
+ // for external users to override these as pseudo-supported APIs.
env.VM.checkRevision(templateSpec.compiler);
+ // backwards compatibility for precompiled templates with compiler-version 7 (<4.3.0)
+ var templateWasPrecompiledWithCompilerV7 = templateSpec.compiler && templateSpec.compiler[0] === 7;
+
function invokePartialWrapper(partial, context, options) {
if (options.hash) {
context = Utils.extend({}, context, options.hash);
@@ -1101,13 +1140,15 @@ return /******/ (function(modules) { // webpackBootstrap
options.ids[0] = true;
}
}
-
partial = env.VM.resolvePartial.call(this, partial, context, options);
- var result = env.VM.invokePartial.call(this, partial, context, options);
+
+ var optionsWithHooks = Utils.extend({}, options, { hooks: this.hooks });
+
+ var result = env.VM.invokePartial.call(this, partial, context, optionsWithHooks);
if (result == null && env.compile) {
options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
- result = options.partials[options.name](context, options);
+ result = options.partials[options.name](context, optionsWithHooks);
}
if (result != null) {
if (options.indent) {
@@ -1174,15 +1215,6 @@ return /******/ (function(modules) { // webpackBootstrap
}
return value;
},
- merge: function merge(param, common) {
- var obj = param || common;
-
- if (param && common && param !== common) {
- obj = Utils.extend({}, common, param);
- }
-
- return obj;
- },
// An empty object to use as replacement for null-contexts
nullContext: _Object$seal({}),
@@ -1219,18 +1251,25 @@ return /******/ (function(modules) { // webpackBootstrap
ret._setup = function (options) {
if (!options.partial) {
- container.helpers = container.merge(options.helpers, env.helpers);
+ container.helpers = Utils.extend({}, env.helpers, options.helpers);
if (templateSpec.usePartial) {
- container.partials = container.merge(options.partials, env.partials);
+ container.partials = Utils.extend({}, env.partials, options.partials);
}
if (templateSpec.usePartial || templateSpec.useDecorators) {
- container.decorators = container.merge(options.decorators, env.decorators);
+ container.decorators = Utils.extend({}, env.decorators, options.decorators);
}
+
+ container.hooks = {};
+
+ var keepHelperInHelpers = options.allowCallsToHelperMissing || templateWasPrecompiledWithCompilerV7;
+ _helpers.moveHelperToHooks(container, 'helperMissing', keepHelperInHelpers);
+ _helpers.moveHelperToHooks(container, 'blockHelperMissing', keepHelperInHelpers);
} else {
container.helpers = options.helpers;
container.partials = options.partials;
container.decorators = options.decorators;
+ container.hooks = options.hooks;
}
};
@@ -1267,6 +1306,10 @@ return /******/ (function(modules) { // webpackBootstrap
return prog;
}
+ /**
+ * This is currently part of the official API, therefore implementation details should not be changed.
+ */
+
function resolvePartial(partial, context, options) {
if (!partial) {
if (options.name === '@partial-block') {
@@ -1629,8 +1672,7 @@ return /******/ (function(modules) { // webpackBootstrap
symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition_plus0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 },
terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" },
productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]],
- performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$
- /**/) {
+ performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$) {
var $0 = $$.length - 1;
switch (yystate) {
@@ -2167,11 +2209,10 @@ return /******/ (function(modules) { // webpackBootstrap
this.begin(condition);
} };
lexer.options = {};
- lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START
- /**/) {
+ lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START) {
function strip(start, end) {
- return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end);
+ return yy_.yytext = yy_.yytext.substring(start, yy_.yyleng - end + start);
}
var YYSTATE = YY_START;
@@ -2208,7 +2249,7 @@ return /******/ (function(modules) { // webpackBootstrap
if (this.conditionStack[this.conditionStack.length - 1] === 'raw') {
return 15;
} else {
- yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9);
+ strip(5, 9);
return 'END_RAW_BLOCK';
}
@@ -2571,7 +2612,7 @@ return /******/ (function(modules) { // webpackBootstrap
return;
}
- // We omit the last node if it's whitespace only and not preceeded by a non-content node.
+ // We omit the last node if it's whitespace only and not preceded by a non-content node.
var original = current.value;
current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, '');
current.leftStripped = current.value !== original;
@@ -2772,7 +2813,7 @@ return /******/ (function(modules) { // webpackBootstrap
function id(token) {
if (/^\[.*\]$/.test(token)) {
- return token.substr(1, token.length - 2);
+ return token.substring(1, token.length - 1);
} else {
return token;
}
@@ -2786,7 +2827,7 @@ return /******/ (function(modules) { // webpackBootstrap
}
function stripComment(comment) {
- return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, '');
+ return comment.replace(/^\{\{~?!-?-?/, '').replace(/-?-?~?\}\}$/, '');
}
function preparePath(data, parts, loc) {
@@ -2794,8 +2835,7 @@ return /******/ (function(modules) { // webpackBootstrap
var original = data ? '@' : '',
dig = [],
- depth = 0,
- depthString = '';
+ depth = 0;
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i].part,
@@ -2810,7 +2850,6 @@ return /******/ (function(modules) { // webpackBootstrap
throw new _exception2['default']('Invalid path: ' + original, { loc: loc });
} else if (part === '..') {
depth++;
- depthString += '../';
}
} else {
dig.push(part);
@@ -3045,11 +3084,11 @@ return /******/ (function(modules) { // webpackBootstrap
'lookup': true
};
if (knownHelpers) {
+ // the next line should use "Object.keys", but the code has been like this a long time and changing it, might
+ // cause backwards-compatibility issues... It's an old library...
+ // eslint-disable-next-line guard-for-in
for (var _name in knownHelpers) {
- /* istanbul ignore else */
- if (_name in knownHelpers) {
- this.options.knownHelpers[_name] = knownHelpers[_name];
- }
+ this.options.knownHelpers[_name] = knownHelpers[_name];
}
}
@@ -3565,10 +3604,19 @@ return /******/ (function(modules) { // webpackBootstrap
// PUBLIC API: You can override these methods in a subclass to provide
// alternative compiled forms for name lookup and buffering semantics
nameLookup: function nameLookup(parent, name /* , type*/) {
- if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
- return [parent, '.', name];
- } else {
- return [parent, '[', JSON.stringify(name), ']'];
+ var isEnumerable = [this.aliasable('container.propertyIsEnumerable'), '.call(', parent, ',"constructor")'];
+
+ if (name === 'constructor') {
+ return ['(', isEnumerable, '?', _actualLookup(), ' : undefined)'];
+ }
+ return _actualLookup();
+
+ function _actualLookup() {
+ if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) {
+ return [parent, '.', name];
+ } else {
+ return [parent, '[', JSON.stringify(name), ']'];
+ }
}
},
depthedLookup: function depthedLookup(name) {
@@ -3767,7 +3815,6 @@ return /******/ (function(modules) { // webpackBootstrap
for (var alias in this.aliases) {
// eslint-disable-line guard-for-in
var node = this.aliases[alias];
-
if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) {
varDeclarations += ', alias' + ++aliasCount + '=' + alias;
node.children[0] = 'alias' + aliasCount;
@@ -3862,7 +3909,7 @@ return /******/ (function(modules) { // webpackBootstrap
// replace it on the stack with the result of properly
// invoking blockHelperMissing.
blockValue: function blockValue(name) {
- var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
+ var blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
this.setupHelperArgs(name, 0, params);
@@ -3880,7 +3927,7 @@ return /******/ (function(modules) { // webpackBootstrap
// On stack, after, if lastHelper: value
ambiguousBlockValue: function ambiguousBlockValue() {
// We're being a bit cheeky and reusing the options value from the prior exec
- var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'),
+ var blockHelperMissing = this.aliasable('container.hooks.blockHelperMissing'),
params = [this.contextName(0)];
this.setupHelperArgs('', 0, params, true);
@@ -4171,18 +4218,33 @@ return /******/ (function(modules) { // webpackBootstrap
// If the helper is not found, `helperMissing` is called.
invokeHelper: function invokeHelper(paramSize, name, isSimple) {
var nonHelper = this.popStack(),
- helper = this.setupHelper(paramSize, name),
- simple = isSimple ? [helper.name, ' || '] : '';
+ helper = this.setupHelper(paramSize, name);
+
+ var possibleFunctionCalls = [];
- var lookup = ['('].concat(simple, nonHelper);
+ if (isSimple) {
+ // direct call to helper
+ possibleFunctionCalls.push(helper.name);
+ }
+ // call a function from the input object
+ possibleFunctionCalls.push(nonHelper);
if (!this.options.strict) {
- lookup.push(' || ', this.aliasable('helpers.helperMissing'));
+ possibleFunctionCalls.push(this.aliasable('container.hooks.helperMissing'));
}
- lookup.push(')');
- this.push(this.source.functionCall(lookup, 'call', helper.callParams));
+ var functionLookupCode = ['(', this.itemsSeparatedBy(possibleFunctionCalls, '||'), ')'];
+ var functionCall = this.source.functionCall(functionLookupCode, 'call', helper.callParams);
+ this.push(functionCall);
},
+ itemsSeparatedBy: function itemsSeparatedBy(items, separator) {
+ var result = [];
+ result.push(items[0]);
+ for (var i = 1; i < items.length; i++) {
+ result.push(separator, items[i]);
+ }
+ return result;
+ },
// [invokeKnownHelper]
//
// On stack, before: hash, inverse, program, params..., ...
@@ -4220,7 +4282,7 @@ return /******/ (function(modules) { // webpackBootstrap
var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')'];
if (!this.options.strict) {
lookup[0] = '(helper = ';
- lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing'));
+ lookup.push(' != null ? helper : ', this.aliasable('container.hooks.helperMissing'));
}
this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']);
diff --git a/frontend/src/modules/assetManagement/views/assetManagementModalView.js b/frontend/src/modules/assetManagement/views/assetManagementModalView.js
index 6885f17775..2494e66615 100644
--- a/frontend/src/modules/assetManagement/views/assetManagementModalView.js
+++ b/frontend/src/modules/assetManagement/views/assetManagementModalView.js
@@ -16,7 +16,7 @@ define(function(require) {
postRender: function() {
this.setupSubViews();
this.setupFilterAndSearchView();
- if (this.options.assetType === "Asset:image" && Origin.scaffold.getCurrentModel().get('_component') === 'graphic') {
+ if (this.options.assetType === "image" && Origin.scaffold.getCurrentModel().get('_component') === 'graphic') {
this.setupImageAutofillButton();
}
this.resizePanels();
@@ -24,8 +24,8 @@ define(function(require) {
setupSubViews: function() {
this.search = {};
- // Replace Asset and : so we can have both filtered and all asset types
- var assetType = this.options.assetType.replace('Asset', '').replace(':', '');
+
+ var assetType = this.options.assetType;
if (assetType) {
var filters = [assetType];
diff --git a/frontend/src/modules/editor/themeEditor/views/editorThemingView.js b/frontend/src/modules/editor/themeEditor/views/editorThemingView.js
index 93f8c8f551..789c31d85b 100644
--- a/frontend/src/modules/editor/themeEditor/views/editorThemingView.js
+++ b/frontend/src/modules/editor/themeEditor/views/editorThemingView.js
@@ -212,9 +212,10 @@ define(function(require) {
if (!fieldView) {
return;
}
- if (fieldView.schema.inputType === 'ColourPicker') {
+ var inputType = fieldView.schema.inputType.type || fieldView.schema.inputType;
+ if (inputType === 'ColourPicker') {
fieldView.setValue(value);
- } else if (typeof fieldView.schema.inputType === 'string' && fieldView.schema.inputType.indexOf('Asset:') > -1) {
+ } else if (inputType.indexOf('Asset') > -1) {
fieldView.setValue(value);
fieldView.render();
$('div[data-editor-id*="' + key + '"]').append(fieldView.editor.$el);
diff --git a/frontend/src/modules/pluginManagement/views/pluginManagementUploadView.js b/frontend/src/modules/pluginManagement/views/pluginManagementUploadView.js
index 31f6265d2f..de940f5549 100644
--- a/frontend/src/modules/pluginManagement/views/pluginManagementUploadView.js
+++ b/frontend/src/modules/pluginManagement/views/pluginManagementUploadView.js
@@ -61,7 +61,7 @@ define(function(require){
Origin.Notify.alert({
type: 'error',
title: Origin.l10n.t('app.uploadpluginerror'),
- text: Helpers.decodeHTML(message)
+ text: message
});
Origin.router.navigateTo('pluginManagement/upload');
},
diff --git a/frontend/src/modules/projects/views/projectsView.js b/frontend/src/modules/projects/views/projectsView.js
index 2b92e5e513..8c14b4e5da 100644
--- a/frontend/src/modules/projects/views/projectsView.js
+++ b/frontend/src/modules/projects/views/projectsView.js
@@ -133,7 +133,8 @@ define(function(require){
operators : {
skip: this.fetchCount,
limit: this.pageSize,
- sort: this.sort
+ sort: this.sort,
+ collation: { locale: navigator.language.substring(0, 2) }
}
},
success: function(collection, response) {
diff --git a/frontend/src/modules/scaffold/views/scaffoldAssetItemView.js b/frontend/src/modules/scaffold/views/scaffoldAssetItemView.js
index 13341d0cb5..9c5a25f98f 100644
--- a/frontend/src/modules/scaffold/views/scaffoldAssetItemView.js
+++ b/frontend/src/modules/scaffold/views/scaffoldAssetItemView.js
@@ -174,7 +174,7 @@ define([
Origin.trigger('modal:open', AssetManagementModalView, {
collection: new AssetCollection,
- assetType: 'Asset:image',
+ assetType: 'image',
_shouldShowScrollbar: false,
onUpdate: function(data) {
if (!data) return;
diff --git a/frontend/src/modules/scaffold/views/scaffoldAssetView.js b/frontend/src/modules/scaffold/views/scaffoldAssetView.js
index 7084f709ce..2371d4cfe6 100644
--- a/frontend/src/modules/scaffold/views/scaffoldAssetView.js
+++ b/frontend/src/modules/scaffold/views/scaffoldAssetView.js
@@ -10,6 +10,8 @@ define([
var ScaffoldAssetView = Backbone.Form.editors.Base.extend({
+ assetType: null,
+
events: {
'change input': function() { this.trigger('change', this); },
'focus input': function() { this.trigger('focus', this); },
@@ -52,9 +54,13 @@ define([
var inputType = this.schema.inputType;
var dataUrl = Helpers.isAssetExternal(this.value) ? this.value : '';
+ this.assetType = typeof inputType === 'string' ?
+ inputType.replace(/Asset|:/g, '') :
+ inputType.media;
+
this.$el.html(Handlebars.templates[this.constructor.template]({
value: this.value,
- type: inputType.media || inputType.replace('Asset:', ''),
+ type: this.assetType,
url: id ? 'api/asset/serve/' + id : dataUrl,
thumbUrl: id ? 'api/asset/thumb/' + id : dataUrl
}));
@@ -200,7 +206,7 @@ define([
Origin.trigger('modal:open', AssetManagementModalView, {
collection: new AssetCollection,
- assetType: this.schema.inputType,
+ assetType: this.assetType,
_shouldShowScrollbar: false,
onUpdate: function(data) {
if (!data) return;
diff --git a/frontend/src/modules/scaffold/views/scaffoldListView.js b/frontend/src/modules/scaffold/views/scaffoldListView.js
index 6d198905bc..7e3909d658 100644
--- a/frontend/src/modules/scaffold/views/scaffoldListView.js
+++ b/frontend/src/modules/scaffold/views/scaffoldListView.js
@@ -1,8 +1,10 @@
define([
'core/origin',
+ 'core/helpers',
+ 'core/models/courseAssetModel',
'backbone-forms',
'backbone-forms-lists'
-], function(Origin, BackboneForms) {
+], function(Origin, Helpers, CourseAssetModel, BackboneForms) {
var ScaffoldListView = Backbone.Form.editors.List.extend({
defaultValue: [],
@@ -92,9 +94,42 @@ define([
},
cloneItem: function(event) {
+ var flatItem = Helpers.flattenNestedProperties(this.editor.value);
+ var itemValues = _.values(flatItem);
+ var parentAttributes = Origin.scaffold.getCurrentModel().attributes;
+ itemValues.forEach(function(item) {
+ if (typeof item !== 'string' || item.indexOf('course/assets') === -1) return;
+
+ var itemFileName = item.substring(item.lastIndexOf('/')+1);
+ $.ajax({
+ url: 'api/asset/query',
+ type:'GET',
+ data: {search: { filename: itemFileName }},
+ success: function (result) {
+ (new CourseAssetModel()).save({
+ _courseId : Origin.editor.data.course.get('_id'),
+ _contentType : parentAttributes._type,
+ _contentTypeId : parentAttributes._id,
+ _fieldName : itemFileName,
+ _assetId : result[0]._id,
+ _contentTypeParentId: parentAttributes._parentId
+ }, {
+ error: function(error) {
+ Origin.Notify.alert({
+ type: 'error',
+ text: Origin.l10n.t('app.errorsaveasset')
+ });
+ }
+ });
+ },
+ error: function() {
+ Origin.Notify.alert({ type: 'error', text: Origin.l10n.t('app.errorduplication') });
+ }
+ });
+ });
+
this.list.addItem(this.editor.value, true);
}
-
});
Origin.on('origin:dataReady', function() {
diff --git a/install.js b/install.js
index 9bdf1cca98..25e3543f97 100644
--- a/install.js
+++ b/install.js
@@ -261,6 +261,9 @@ installHelpers.checkPrimaryDependencies(function(error) {
]
};
if(!IS_INTERACTIVE) {
+ if (installHelpers.inputHelpers.toBoolean(optimist.argv.useJSON)) {
+ USE_CONFIG = true;
+ }
return start();
}
console.log('');
@@ -282,7 +285,8 @@ installHelpers.checkPrimaryDependencies(function(error) {
function generatePromptOverrides() {
if(USE_CONFIG) {
var configJson = require('./conf/config.json');
- var configData = JSON.parse(JSON.stringify(configJson).replace(/true/g, '"y"').replace(/false/g, '"n"'));
+ var configData = JSON.parse(JSON.stringify(configJson).replace(/:true/g, ':"y"').replace(/:false/g, ':"n"'));
+ addConfig(configData);
configData.install = 'y';
}
const sessionSecret = USE_CONFIG && configData.sessionSecret || crypto.randomBytes(64).toString('hex');
@@ -345,7 +349,7 @@ function configureDatabase(callback) {
installHelpers.getInput(inputData.database.dbConfig, function(result) {
addConfig(result);
- var isStandard = !result.useConnectionUri || USE_CONFIG && configResults.useConnectionUri !== 'y';
+ var isStandard = !installHelpers.inputHelpers.toBoolean(result.useConnectionUri);
var config = inputData.database[isStandard ? 'configureStandard' : 'configureUri'];
installHelpers.getInput(config, function(result) {
@@ -360,14 +364,14 @@ function configureFeatures(callback) {
function smtp(cb) {
installHelpers.getInput(inputData.features.smtp.confirm, function(result) {
addConfig(result);
- if(!result.useSmtp || USE_CONFIG && configResults.useSmtp !== 'y') {
+ if (!installHelpers.inputHelpers.toBoolean(result.useSmtp)) {
return cb();
}
// prompt user if custom connection url or well-known-service should be used
installHelpers.getInput(inputData.features.smtp.confirmConnectionUrl, function(result) {
addConfig(result);
var smtpConfig;
- if (result.useSmtpConnectionUrl === true) {
+ if (installHelpers.inputHelpers.toBoolean(result.useSmtpConnectionUrl)) {
smtpConfig = inputData.features.smtp.configure.concat(inputData.features.smtp.configureConnectionUrl);
} else {
smtpConfig = inputData.features.smtp.configure.concat(inputData.features.smtp.configureService);
@@ -478,7 +482,7 @@ function createSuperUser(callback) {
var onError = function(error) {
handleError(error, 1, 'Failed to create admin user account. Please check the console output.');
};
- console.log(`\nNow we need to set up a 'Super Admin' account. This account can be used to manage everything on your ${app.polyglot.t('app.productname')} instance.`);
+ console.log(`\nNow we need to set up a 'Super Admin' account. This account can be used to manage everything on your authoring tool instance.`);
installHelpers.getInput(inputData.superUser, function(result) {
console.log('');
app.usermanager.deleteUser({ email: result.suEmail }, function(error, userRec) {
diff --git a/lib/application.js b/lib/application.js
index 039b277451..c868c28073 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -328,14 +328,13 @@ Origin.prototype.createServer = function (options, cb) {
Origin.prototype.startServer = function (options) {
var app = this;
- // Ensure that the options object is set.
- if(typeof options === 'undefined') {
- options = {
- skipDependencyCheck: false,
- skipVersionCheck: false,
- skipStartLog: false
- };
- }
+
+ options = { ...{
+ skipDependencyCheck: false,
+ skipVersionCheck: false,
+ skipStartLog: false
+ }, ...options };
+
checkPrerequisites(options, function(err, result) {
if(err) {
logger.log('error', chalk.red(err.message));
diff --git a/lib/dml/mongoose/index.js b/lib/dml/mongoose/index.js
index 87bc1a1135..3e119f34a2 100644
--- a/lib/dml/mongoose/index.js
+++ b/lib/dml/mongoose/index.js
@@ -7,8 +7,8 @@ var Database = require('../../database').Database,
fs = require('fs'),
path = require('path'),
mongoose = require('mongoose'),
- mongoUri = require('mongodb-uri');
- _ = require('underscore');
+ mongoUri = require('mongodb-uri'),
+ semver = require('semver');
mongoose.Promise = global.Promise;
mongoose.set('useCreateIndex', true);
@@ -32,6 +32,7 @@ function MongooseDB() {
this.conn = false;
this._models = false;
this.createdAt = new Date();
+ this.mongoVersion = null;
}
/**
@@ -69,9 +70,11 @@ MongooseDB.prototype.connect = function(db) {
dbPass = configuration.getConfig('dbPass');
dbReplicaset = configuration.getConfig('dbReplicaset');
dbConnectionUri = configuration.getConfig('dbConnectionUri');
- options = configuration.getConfig('dbOptions') || {};
- options.domainsEnabled = true;
- options.useNewUrlParser = true;
+ options = { ...configuration.getConfig('dbOptions'), ...{
+ domainsEnabled: true,
+ useNewUrlParser: true,
+ useUnifiedTopology: true
+ }};
// Construct the authentication part of the connection string.
authenticationString = dbUser && dbPass ? dbUser + ':' + dbPass + '@' : '';
@@ -109,6 +112,13 @@ MongooseDB.prototype.connect = function(db) {
this.conn.once('error', function(){ logger.log('error', 'Database Connection failed, please check your database'); }); //added to give console notification of the problem
this.updatedAt = new Date();
this._models = {};
+ this.conn.db.command({ buildInfo: 1 }, (error, info) => {
+ if (error) {
+ logger.log('error', error);
+ } else {
+ this.mongoVersion = info.version;
+ }
+ });
}.bind(this));
};
@@ -298,6 +308,7 @@ MongooseDB.prototype.retrieve = function(objectType, search, options, callback)
var limit = false;
var distinct = false;
var elemMatch = false;
+ var collation = operators && operators.collation;
var jsonOnly = options && options.jsonOnly
? true
: false;
@@ -336,7 +347,10 @@ MongooseDB.prototype.retrieve = function(objectType, search, options, callback)
}
// apply any query operators
- // do sort first
+ if (collation && semver.satisfies(this.mongoVersion, '>=3.4')) {
+ query.collation(collation);
+ }
+
if (sort && !distinct) {
query.sort(sort);
}
diff --git a/lib/installHelpers.js b/lib/installHelpers.js
index 9264310dc1..a9830dc705 100644
--- a/lib/installHelpers.js
+++ b/lib/installHelpers.js
@@ -41,6 +41,21 @@ var inputHelpers = {
*/
readline.moveCursor(process.stdout, 0, -1);
return v;
+ },
+ isFalsyString: function(v) {
+ if (typeof v !== 'string') return false;
+ switch (v.trim()) {
+ case '':
+ case 'N':
+ case 'n':
+ case 'false':
+ case 'null':
+ case 'undefined':
+ case '0':
+ return true;
+ default:
+ return false;
+ }
}
};
@@ -444,7 +459,7 @@ function installFramework(opts, callback) {
if(fs.existsSync(opts.directory) && !opts.force) {
return updateFramework(opts, callback);
}
- async.applyEachSeries([cloneRepo, updateFramework], opts, callback);
+ async.applyEachSeries([ cloneRepo, updateFramework ], opts)(callback);
}
function updateFramework(opts, callback) {
@@ -456,7 +471,7 @@ function updateFramework(opts, callback) {
installDependencies,
purgeCourseFolder,
updateFrameworkPlugins
- ], opts, callback);
+ ], opts)(callback);
}
function checkOptions(opts, action, callback) {
diff --git a/lib/outputmanager.js b/lib/outputmanager.js
index c6fa7a5175..c879da9f94 100644
--- a/lib/outputmanager.js
+++ b/lib/outputmanager.js
@@ -607,11 +607,13 @@ OutputPlugin.prototype.writeThemeVariables = function(courseId, theme, themeVari
function(seriesCallback) {
// Create LESS for properties
async.each(Object.keys(props), function(prop, innerCallback) {
+ var themeProperty = theme.properties.variables[prop];
+ var inputType = themeProperty.inputType;
// Check if the user has customised any properties
- if (savedSettings.hasOwnProperty(prop) && theme.properties.variables[prop].default !== savedSettings[prop]) {
+ if (savedSettings.hasOwnProperty(prop) && themeProperty.default !== savedSettings[prop]) {
// The user has customised this property
// Check if processing an image asset
- if (theme.properties.variables[prop].inputType === 'Asset:image') {
+ if (inputType.media === 'image' || inputType === 'Asset:image') {
// Split the path so we can process the filename
var assetPathArray = savedSettings[prop].split('/');
// Encode the filename (removing spaces, etc.)
diff --git a/lib/permissions.js b/lib/permissions.js
index 74e1eeef8c..37f6c4935b 100644
--- a/lib/permissions.js
+++ b/lib/permissions.js
@@ -1,5 +1,6 @@
// LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
-var database = require('./database'),
+var _ = require('underscore'),
+ database = require('./database'),
async = require('async'),
configuration = require('./configuration');
diff --git a/lib/tenantmanager.js b/lib/tenantmanager.js
index 6726971ac8..d0733d9188 100644
--- a/lib/tenantmanager.js
+++ b/lib/tenantmanager.js
@@ -2,6 +2,7 @@
/**
* Tenant management module
*/
+var _ = require('underscore');
var async = require('async');
var fs = require('fs-extra');
var path = require('path');
diff --git a/package.json b/package.json
index f841f857b1..70da2e1951 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "adapt_authoring",
- "version": "0.10.0",
+ "version": "0.10.1",
"license": "GPL-3.0",
"description": "A server-based user interface for authoring eLearning courses using the Adapt Framework.",
"keywords": [
@@ -14,80 +14,79 @@
"framework": "2",
"main": "index",
"engines": {
- "node": "8 || 10"
+ "node": "10 || 12"
},
"scripts": {
"test": "grunt test",
"migrate": "migrate --config conf/migrate.json --template-file migrations/helpers/template.js"
},
"dependencies": {
- "archiver": "^2.1.1",
- "async": "^2.6.1",
+ "archiver": "^3.1.1",
+ "async": "^3.1.0",
"bcrypt-nodejs": "0.0.3",
- "body-parser": "^1.18.3",
- "bower": "^1.8.4",
- "bytes": "^3.0.0",
- "chalk": "^2.4.1",
- "compression": "^1.7.2",
- "connect-mongodb-session": "^2.0.5",
+ "body-parser": "^1.19.0",
+ "bower": "^1.8.8",
+ "bytes": "^3.1.0",
+ "chalk": "^2.4.2",
+ "compression": "^1.7.4",
+ "connect-mongodb-session": "^2.2.0",
"consolidate": "^0.15.1",
- "cookie-parser": "^1.4.3",
- "email-templates": "^4.0.0",
- "errorhandler": "^1.5.0",
- "express": "^4.16.3",
- "express-session": "^1.15.6",
- "ffmpeg-static": "^2.4.0",
+ "cookie-parser": "^1.4.4",
+ "email-templates": "^6.0.2",
+ "errorhandler": "^1.5.1",
+ "express": "^4.17.1",
+ "express-session": "^1.16.2",
+ "ffmpeg-static": "^2.6.0",
"ffprobe": "^1.1.0",
"ffprobe-static": "^3.0.0",
"fluent-ffmpeg": "^2.1.2",
"formidable": "^1.2.1",
- "fs-extra": "^6.0.1",
- "glob": "^7.1.2",
- "grunt": "^1.0.2",
+ "fs-extra": "^8.1.0",
+ "glob": "^7.1.4",
+ "grunt": "^1.0.4",
"grunt-contrib-copy": "^1.0.0",
- "grunt-contrib-handlebars": "^1.0.0",
+ "grunt-contrib-handlebars": "^2.0.0",
"grunt-contrib-requirejs": "^1.0.0",
- "grunt-merge-json": "^0.9.7",
"grunt-mocha-test": "^0.13.3",
- "handlebars": "^4.0.11",
- "handlebars-form-helpers": "^0.1.3",
- "hbs": "^4.0.1",
+ "handlebars": "^4.4.0",
+ "handlebars-form-helpers": "^0.1.4",
+ "hbs": "^4.0.5",
"jshint-stylish": "^2.2.1",
"json-schema-mapper": "0.0.2",
- "junk": "^2.1.0",
- "less": "^3.0.4",
- "log-update": "^2.3.0",
+ "junk": "^3.1.0",
+ "less": "^3.10.3",
+ "log-update": "^3.3.0",
"matchdep": "^2.0.0",
- "method-override": "^2.3.10",
- "migrate-mongoose": "^3.2.2",
- "mime": "^2.3.1",
- "moment": "^2.22.1",
+ "method-override": "^3.0.0",
+ "migrate-mongoose": "^4.0.0",
+ "mime": "^2.4.4",
+ "moment": "^2.24.0",
"mongodb-uri": "^0.9.7",
- "mongoose": "^5.5.8",
- "morgan": "^1.9.0",
- "multer": "^1.3.0",
- "needle": "^2.2.1",
- "node-polyglot": "^2.2.2",
- "nodemailer": "^4.6.4",
+ "mongoose": "^5.7.3",
+ "morgan": "^1.9.1",
+ "multer": "^1.4.2",
+ "needle": "^2.4.0",
+ "node-polyglot": "^2.4.0",
+ "nodemailer": "^6.3.0",
"optimist": "^0.6.1",
"passport": "^0.4.0",
"prompt": "^1.0.0",
- "request": "^2.87.0",
- "semver": "^5.5.0",
+ "request": "^2.88.0",
+ "semver": "^6.3.0",
"serve-favicon": "^2.5.0",
"traverse": "^0.6.6",
- "underscore": "^1.9.0",
- "unzip": "^0.1.11",
- "validator": "^10.2.0",
- "winston": "3",
- "yauzl": "^2.9.1"
+ "underscore": "^1.9.1",
+ "unzipper": "^0.10.5",
+ "validator": "^11.1.0",
+ "winston": "^3.2.1",
+ "yauzl": "^2.10.0"
},
"devDependencies": {
- "mocha": "^5.2.0",
- "mocha-multi": "^1.0.1",
+ "mocha": "^6.2.1",
+ "mocha-multi": "^1.1.3",
"mocha-simple-html-reporter": "^1.1.0",
- "mongodb": "^3.0.10",
- "should": "^13.2.1",
- "supertest": "^3.1.0"
+ "mongodb": "^3.3.2",
+ "should": "^13.2.3",
+ "supertest": "^4.0.2"
}
}
diff --git a/plugins/content/bower/index.js b/plugins/content/bower/index.js
index 4f8cc970b8..ecc4accec8 100644
--- a/plugins/content/bower/index.js
+++ b/plugins/content/bower/index.js
@@ -27,7 +27,7 @@ var origin = require('../../../'),
_ = require('underscore'),
util = require('util'),
path = require('path'),
- unzip = require('unzip'),
+ unzip = require('unzipper'),
exec = require('child_process').exec,
IncomingForm = require('formidable').IncomingForm,
installHelpers = require('../../../lib/installHelpers'),
@@ -803,93 +803,114 @@ function addPackage (plugin, packageInfo, options, cb) {
return addCb(err);
}
- // don't duplicate component.name, component.version
- db.retrieve(plugin.type, { name: package.name, version: package.version }, function (err, results) {
+ const targetAttribute = pkgMeta.targetAttribute;
+
+ async.some([ 'componenttype', 'extensiontype', 'menutype', 'themetype' ], (type, asyncCallback) => {
+ if (!targetAttribute) return asyncCallback();
+
+ db.retrieve(type, { targetAttribute: targetAttribute }, (err, results) => {
+ asyncCallback(err, results && results.length);
+ });
+ }, (err, targetAttributeExists) => {
if (err) {
logger.log('error', err);
return addCb(err);
}
- if (results && 0 !== results.length) {
- // don't add duplicate
- if (options.strict) {
- return addCb(new PluginPackageError("Can't add plugin: plugin already exists!"));
- }
- return addCb(null);
+ if (targetAttributeExists) {
+ return addCb(new PluginPackageError(app.polyglot.t('app.targetattributeexists', {
+ targetAttribute
+ })));
}
- db.create(plugin.type, package, function (err, newPlugin) {
+ // don't duplicate component.name, component.version
+ db.retrieve(plugin.type, { name: package.name, version: package.version }, function (err, results) {
if (err) {
+ logger.log('error', err);
+ return addCb(err);
+ }
+
+ if (results && 0 !== results.length) {
+ // don't add duplicate
if (options.strict) {
- return addCb(err);
+ return addCb(new PluginPackageError(app.polyglot.t('app.versionexists')));
}
-
- logger.log('error', 'Failed to add package: ' + package.name, err);
return addCb(null);
}
- logger.log('info', 'Added package: ' + package.name);
-
- // #509 update content targeted by previous versions of this package
- logger.log('info', 'searching old package types ... ');
- db.retrieve(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err, results) {
-
+ db.create(plugin.type, package, function (err, newPlugin) {
if (err) {
- // strictness doesn't matter at this point
- logger.log('error', 'Failed to retrieve previous packages: ' + err.message, err);
+ if (options.strict) {
+ return addCb(err);
+ }
+
+ logger.log('error', 'Failed to add package: ' + package.name, err);
+ return addCb(null);
}
- if (results && results.length) {
- // found previous versions to update
- // only update content using the id of the most recent version
- var oldPlugin = false;
- results.forEach(function (item) {
- if (!oldPlugin) {
- oldPlugin = item;
- } else if (semver.gt(item.version, oldPlugin.version)) {
- oldPlugin = item;
- }
- });
+ logger.log('info', 'Added package: ' + package.name);
- // Persist the _isAvailableInEditor flag.
- db.update(plugin.type, {_id: newPlugin._id}, {_isAvailableInEditor: oldPlugin._isAvailableInEditor}, function(err, results) {
- if (err) {
- logger.log('error', err);
- return addCb(err);
- }
+ // #509 update content targeted by previous versions of this package
+ logger.log('info', 'searching old package types ... ');
+ db.retrieve(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err, results) {
+
+ if (err) {
+ // strictness doesn't matter at this point
+ logger.log('error', 'Failed to retrieve previous packages: ' + err.message, err);
+ }
+
+ if (results && results.length) {
+ // found previous versions to update
+ // only update content using the id of the most recent version
+ var oldPlugin = false;
+ results.forEach(function (item) {
+ if (!oldPlugin) {
+ oldPlugin = item;
+ } else if (semver.gt(item.version, oldPlugin.version)) {
+ oldPlugin = item;
+ }
+ });
- plugin.updateLegacyContent(newPlugin, oldPlugin, function (err) {
+ // Persist the _isAvailableInEditor flag.
+ db.update(plugin.type, {_id: newPlugin._id}, {_isAvailableInEditor: oldPlugin._isAvailableInEditor}, function(err, results) {
if (err) {
logger.log('error', err);
return addCb(err);
}
- // Remove older versions of this plugin
- db.destroy(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err) {
+ plugin.updateLegacyContent(newPlugin, oldPlugin, function (err) {
if (err) {
logger.log('error', err);
return addCb(err);
}
- logger.log('info', 'Successfully removed versions of ' + package.name + '(' + plugin.type + ') older than ' + newPlugin.version);
- return addCb(null, newPlugin);
+ // Remove older versions of this plugin
+ db.destroy(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err) {
+ if (err) {
+ logger.log('error', err);
+ return addCb(err);
+ }
+
+ logger.log('info', 'Successfully removed versions of ' + package.name + '(' + plugin.type + ') older than ' + newPlugin.version);
+ return addCb(null, newPlugin);
+ });
});
});
- });
- } else {
- // nothing to do!
- // Remove older versions of this plugin
- db.destroy(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err) {
- if (err) {
- logger.log('error', err);
- return addCb(err);
- }
+ } else {
+ // nothing to do!
+ // Remove older versions of this plugin
+ db.destroy(plugin.type, { name: package.name, version: { $ne: newPlugin.version } }, function (err) {
+ if (err) {
+ logger.log('error', err);
+ return addCb(err);
+ }
- logger.log('info', 'Successfully removed versions of ' + package.name + '(' + plugin.type + ') older than ' + newPlugin.version);
+ logger.log('info', 'Successfully removed versions of ' + package.name + '(' + plugin.type + ') older than ' + newPlugin.version);
- return addCb(null, newPlugin);
- });
- }
+ return addCb(null, newPlugin);
+ });
+ }
+ });
});
});
});
@@ -1058,7 +1079,7 @@ function handleUploadedPlugin (req, res, next) {
var file = files.file;
if (!file || !file.path) {
- return next(new PluginPackageError('File upload failed!'));
+ return next(new PluginPackageError(app.polyglot.t('app.fileuploaderror')));
}
// try unzipping
@@ -1104,17 +1125,19 @@ function handleUploadedPlugin (req, res, next) {
}, function(hasResults) {
if (!hasResults) {
- return next(new PluginPackageError('Cannot find expected bower.json file in the plugin root, please check the structure of your zip file and try again.'));
+ return next(app.polyglot.t('app.cannotfindbower'));
}
if (!packageJson) {
- return next(new PluginPackageError('Unrecognized plugin - a plugin should have a bower.json file'));
+ return next(app.polyglot.t('app.unrecognisedplugin'));
}
// extract the plugin type from the package
var pluginType = extractPluginType(packageJson);
if (!pluginType) {
- return next(new PluginPackageError('Unrecognized plugin type for package ' + packageJson.name));
+ return next(new PluginPackageError(app.polyglot.t('app.unrecognisedpluginforpackage', {
+ package: packageJson.name
+ })));
}
// mark as a locally installed package
@@ -1132,7 +1155,9 @@ function handleUploadedPlugin (req, res, next) {
}
// Check if the framework has been defined on the plugin and that it's not compatible
if (packageInfo.pkgMeta.framework && !semver.satisfies(semver.clean(frameworkVersion), packageInfo.pkgMeta.framework, { includePrerelease: true })) {
- return next(new PluginPackageError('This plugin is incompatible with version ' + frameworkVersion + ' of the Adapt framework'));
+ return next(new PluginPackageError(app.polyglot.t('app.incompatibleframework', {
+ framework: frameworkVersion
+ })));
}
app.contentmanager.getContentPlugin(pluginType, function (error, contentPlugin) {
if (error) {
@@ -1145,10 +1170,9 @@ function handleUploadedPlugin (req, res, next) {
function sendResponse() {
res.statusCode = 200;
- return res.json({
- success: true,
- pluginType: pluginType,
- message: 'successfully added new plugin'
+ return res.json({
+ success: true,
+ pluginType: pluginType
});
}
diff --git a/plugins/filestorage/localfs/index.js b/plugins/filestorage/localfs/index.js
index c340a741fb..f4d8abbc1b 100644
--- a/plugins/filestorage/localfs/index.js
+++ b/plugins/filestorage/localfs/index.js
@@ -232,7 +232,7 @@ LocalFileStorage.prototype.processFileUpload = function (file, newPath, options,
if (options.createMetadata) {
return self.inspectFile(newPath, file.type, function (err, withMeta) {
if (withMeta) {
- data = _.extend(data, withMeta);
+ Object.assign(data, withMeta);
}
nextFunc();
});
diff --git a/plugins/output/adapt/helpers.js b/plugins/output/adapt/helpers.js
index 004981a4ab..ba5c3a5573 100644
--- a/plugins/output/adapt/helpers.js
+++ b/plugins/output/adapt/helpers.js
@@ -167,11 +167,6 @@ function importAsset(fileMetadata, metadata, assetImported) {
}
var asset = _.extend(fileMetadata, storedFile);
- _.each(asset.tags, function iterator(tag, index) {
- if (metadata.idMap[tag]) {
- asset.tags[index] = metadata.idMap[tag];
- }
- });
origin.assetmanager.createAsset(asset, function onAssetCreated(createError, assetRec) {
if (createError) {
diff --git a/plugins/output/adapt/importsource.js b/plugins/output/adapt/importsource.js
index e415f01fdc..2e5b0a9e5a 100644
--- a/plugins/output/adapt/importsource.js
+++ b/plugins/output/adapt/importsource.js
@@ -169,14 +169,26 @@ function ImportSource(req, done) {
var fileStat = fs.statSync(assetPath);
var assetTitle = assetName;
var assetDescription = assetName;
- var tags = formTags.slice();
+ var assetJson = assetsJson[assetName];
+ var tags = [];
- if (assetsJson[assetName]) {
- assetTitle = assetsJson[assetName].title;
- assetDescription = assetsJson[assetName].description;
+ if (assetJson) {
+ assetTitle = assetJson.title;
+ assetDescription = assetJson.description;
- assetsJson[assetName].tags.forEach(function(tag) {
- tags.push(tag._id);
+ assetJson.tags.forEach(function(tag) {
+ const tagTitle = tag.title;
+ const warn = (error) => logger.log('warn', `Failed to create asset tag '${tagTitle}' ${error}`);
+
+ if(!tagTitle) return warn(new Error('Tag has no title'));
+
+ app.contentmanager.getContentPlugin('tag', function(error, plugin) {
+ if(error) return warn(error);
+ plugin.create({ title: tagTitle }, function(error, record) { // @note retrieves if tag already exists
+ if(error) return warn(error);
+ tags.push(record._id);
+ });
+ });
});
}
var fileMeta = {
diff --git a/routes/lang/en-application.json b/routes/lang/en.json
similarity index 97%
rename from routes/lang/en-application.json
rename to routes/lang/en.json
index dc5608859f..0fb8d29ad4 100644
--- a/routes/lang/en-application.json
+++ b/routes/lang/en.json
@@ -410,5 +410,12 @@
"app.searchByMail": "Search by email",
"app.maxfileuploadsize": "Maximum upload file size: %{size}.",
"app.uploadsizeerror": "File size limit exceeded. Expected no more than %{max}, received %{size}.",
+ "app.fileuploaderror": "File upload failed.",
+ "app.cannotfindbower": "Cannot find expected bower.json file in the plugin root, please check the structure of your zip file and try again.",
+ "app.unrecognisedplugin": "Unrecognised plugin - a plugin should have a bower.json file.",
+ "app.unrecognisedpluginforpackage": "Unrecognised plugin type for package %{package}.",
+ "app.incompatibleframework": "This plugin is incompatible with version %{framework} of the Adapt framework.",
+ "app.targetattributeexists": "There is a plugin already installed with a target attribute of '%{targetAttribute}'.",
+ "app.versionexists": "You already have this version of the plugin installed.",
"app.unknownuser": "Unknown User"
}
diff --git a/routes/lang/es-application.json b/routes/lang/es.json
similarity index 100%
rename from routes/lang/es-application.json
rename to routes/lang/es.json
diff --git a/routes/lang/index.js b/routes/lang/index.js
index 3452a3022a..0c9e2d3f34 100644
--- a/routes/lang/index.js
+++ b/routes/lang/index.js
@@ -1,18 +1,19 @@
var express = require('express');
var path = require('path');
var fs = require('fs');
-var server = module.exports = express();
-server.get('/lang/:lang', function (req, res, next) {
- var lang = req.params.lang; // ie 'en' for /lang/en
- var filename = path.join(__dirname, lang) + '.json';
- var file;
+var configuration = require('../../lib/configuration');
+var Constants = require('../../lib/outputmanager').Constants;
- fs.exists(filename, function(exists) {
- file = exists
- ? require(filename)
- : require(path.join(__dirname, 'en') + '.json');
+var server = module.exports = express();
- return res.json(file);
- });
+server.get('/lang/:lang', function (req, res, next) {
+ var lang = req.params.lang; // ie 'en' for /lang/en
+ var filename = path.join(configuration.serverRoot, Constants.Folders.Temp, 'lang', lang + '.json');
+ fs.exists(filename, function(exists) {
+ if(!exists) {
+ return res.status(404).end();
+ }
+ return res.json(require(filename));
+ });
});
diff --git a/server.js b/server.js
index b9cb5f5099..1433d60580 100644
--- a/server.js
+++ b/server.js
@@ -1,3 +1,4 @@
-// LICENCE https://github.com/adaptlearning/adapt_authoring/blob/master/LICENSE
-var app = require('./lib/application')();
-app.run();
+const app = require('./lib/application')();
+const argv = require('optimist').argv;
+
+app.run(argv);
diff --git a/test/entry.js b/test/entry.js
index a736337799..1e6dfb879c 100644
--- a/test/entry.js
+++ b/test/entry.js
@@ -112,7 +112,8 @@ function removeTestData(done) {
var connStr = 'mongodb://' + testConfig.dbHost + ':' + testConfig.dbPort + '/' + testConfig.dbName;
MongoClient.connect(connStr, {
domainsEnabled: true,
- useNewUrlParser: true
+ useNewUrlParser: true,
+ useUnifiedTopology: true
}, function(error, client) {
if(error) return cb(error);
diff --git a/upgrade.js b/upgrade.js
index 4c88195e04..a6adfb316b 100644
--- a/upgrade.js
+++ b/upgrade.js
@@ -142,7 +142,7 @@ function checkForUpdates(callback) {
function doUpdate(data) {
async.series([
function upgradeAuthoring(cb) {
- if(!data.adapt_authoring) {
+ if (installHelpers.inputHelpers.isFalsyString(data.adapt_authoring)) {
return cb();
}
installHelpers.updateAuthoring({
@@ -159,7 +159,7 @@ function doUpdate(data) {
});
},
function upgradeFramework(cb) {
- if(!data.adapt_framework) {
+ if (installHelpers.inputHelpers.isFalsyString(data.adapt_framework)) {
return cb();
}
var dir = path.join(configuration.tempDir, configuration.getConfig('masterTenantID'), OutputConstants.Folders.Framework);