From 3909baa02527e158e65031060f3225c6d517939a Mon Sep 17 00:00:00 2001 From: Thomas Taylor Date: Wed, 30 Aug 2017 20:53:51 +0100 Subject: [PATCH 01/30] Add custom function to generate lang files --- .gitignore | 2 - Gruntfile.js | 42 ++++++++++++++------ package.json | 1 - routes/lang/{en-application.json => en.json} | 0 routes/lang/{es-application.json => es.json} | 0 routes/lang/index.js | 23 ++++++----- 6 files changed, 42 insertions(+), 26 deletions(-) rename routes/lang/{en-application.json => en.json} (100%) rename routes/lang/{es-application.json => es.json} (100%) diff --git a/.gitignore b/.gitignore index 20b3452841..c767703bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -23,8 +23,6 @@ /plugins/content/menu/versions/ /plugins/content/bower/bowercache/ -/routes/lang/en.json - /temp /test_frontend/img /tmp diff --git a/Gruntfile.js b/Gruntfile.js index f018329bed..8ca97c6ee9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,15 +4,6 @@ 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' - } - }, copy: { main: { files: [ @@ -194,7 +185,7 @@ module.exports = function(grunt) { }, lang: { files: ['routes/lang/*.json'], - tasks: ['merge-json'] + tasks: ['generate-lang-json'] } }, mochaTest: { @@ -251,7 +242,7 @@ module.exports = function(grunt) { grunt.file.write(configFile, JSON.stringify(config, null, 2)); // run the tasks var compilation = (config.isProduction) ? 'compile' : 'dev'; - grunt.task.run(['requireBundle', 'merge-json', 'copy', 'less:' + compilation, 'handlebars', 'requirejs:'+ compilation]); + grunt.task.run(['requireBundle', 'generate-lang-json', 'copy', 'less:' + compilation, 'handlebars', 'requirejs:'+ compilation]); }); grunt.registerTask('server', "Running Server", function() { @@ -346,7 +337,34 @@ module.exports = function(grunt) { done(); } }); + // TODO should probably have config up there ^ + grunt.registerTask('generate-lang-json', function() { + var _ = require('underscore'); + var fs = require('fs-extra'); + var path = require('path'); + + var langFileExt = '.json'; + var backendSrc = path.join('routes', 'lang', '*' + langFileExt); + var frontendSrc = path.join('frontend', 'src', '**', 'lang'); + var dest = path.join('temp', 'lang'); + // 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({}, backendSrc).forEach(function(filePath) { + var lang = path.basename(filePath, langFileExt); + var data = _.extend({}, fs.readJSONSync(filePath)); + // load all matching frontend lang files + grunt.file.expand({}, path.join(frontendSrc, lang + langFileExt)).forEach(function(filePath2) { + // TODO check for duplicates + _.extend(data, fs.readJSONSync(filePath2)); + }); + fs.ensureDirSync(dest); + fs.writeJSONSync(path.join(dest, lang + langFileExt), data, { spaces: 2 }); + }); + }); grunt.registerTask('test', ['mochaTest']); - grunt.registerTask('default', ['merge-json', 'requireBundle', 'less:dev', 'handlebars', 'watch']); + grunt.registerTask('default', ['generate-lang-json', 'requireBundle', 'less:dev', 'handlebars', 'watch']); }; diff --git a/package.json b/package.json index 13cf898a36..ff1ff37a43 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,6 @@ "grunt-contrib-requirejs": "~0.4.4", "grunt-contrib-watch": "~0.5.1", "grunt-jscs": "^2.3.0", - "grunt-merge-json": "^0.9.5", "grunt-mocha-test": "~0.7.0", "grunt-open": "~0.2.2", "handlebars": "^4.0.5", diff --git a/routes/lang/en-application.json b/routes/lang/en.json similarity index 100% rename from routes/lang/en-application.json rename to routes/lang/en.json 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)); + }); }); From 05c452a762c493cbbddaf0b394355014e0026e40 Mon Sep 17 00:00:00 2001 From: Dan Gray Date: Wed, 10 Jul 2019 17:51:33 +0100 Subject: [PATCH 02/30] Add courseasset check on clone item, fixes #2347 --- frontend/src/core/helpers.js | 22 +++++++++++ .../scaffold/views/scaffoldListView.js | 39 ++++++++++++++++++- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/frontend/src/core/helpers.js b/frontend/src/core/helpers.js index 619de89ed7..d1831482ab 100644 --- a/frontend/src/core/helpers.js +++ b/frontend/src/core/helpers.js @@ -342,6 +342,28 @@ define(function(require){ '', Origin.l10n.t('app.maxfileuploadsize', {size: Origin.constants.humanMaxFileUploadSize}), ''].join('')) + }, + + flattenNestedProperties: function(properties) { + var flatProperties = {}; + if (typeof properties !== 'undefined') { + 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/modules/scaffold/views/scaffoldListView.js b/frontend/src/modules/scaffold/views/scaffoldListView.js index 6d198905bc..49b37f867c 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; + _.each(itemValues, function(item) { + if (typeof item === 'string' && item.indexOf('course/assets') !== -1) { + 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() { From 1b2f7648eb1c36fa924895bd82d3745b4b04dde2 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Tue, 13 Aug 2019 15:16:19 +0100 Subject: [PATCH 03/30] Improve Boolean RegExp --- install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.js b/install.js index 9bdf1cca98..a6ae43d18a 100644 --- a/install.js +++ b/install.js @@ -282,7 +282,7 @@ 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"')); configData.install = 'y'; } const sessionSecret = USE_CONFIG && configData.sessionSecret || crypto.randomBytes(64).toString('hex'); From a35e1d637a885a4c5c97341b4d3bdd31f6d0cb2e Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Tue, 13 Aug 2019 15:18:16 +0100 Subject: [PATCH 04/30] Fix non-interactive install --- install.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/install.js b/install.js index a6ae43d18a..40c5a9c29f 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(''); @@ -283,6 +286,7 @@ 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"')); + 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); From f740ecc7689ed8f949c83c3c7d41fbe295b7b257 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Tue, 13 Aug 2019 15:20:37 +0100 Subject: [PATCH 05/30] Allow non-interactive manual upgrade One or both of authoring tool and framework --- lib/installHelpers.js | 13 +++++++++++++ upgrade.js | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/installHelpers.js b/lib/installHelpers.js index 9264310dc1..856ceb8ebd 100644 --- a/lib/installHelpers.js +++ b/lib/installHelpers.js @@ -41,6 +41,19 @@ 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; + } } }; 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); From 4a7f465f100cfc2f40c1689eb3b94cca32677b4c Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Wed, 14 Aug 2019 11:09:31 +0100 Subject: [PATCH 06/30] Pass command line arguments to server script --- lib/application.js | 15 +++++++-------- server.js | 7 ++++--- 2 files changed, 11 insertions(+), 11 deletions(-) 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/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); From 15368cf6897c000e64d5b4f4832f838f97c2d194 Mon Sep 17 00:00:00 2001 From: tomgreenfield Date: Fri, 16 Aug 2019 10:19:15 +0100 Subject: [PATCH 07/30] Add default to switch for clarity --- lib/installHelpers.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/installHelpers.js b/lib/installHelpers.js index 856ceb8ebd..a967b55489 100644 --- a/lib/installHelpers.js +++ b/lib/installHelpers.js @@ -53,6 +53,8 @@ var inputHelpers = { case 'undefined': case '0': return true; + default: + return false; } } }; From 0602be726582b4cfc2c43278ddece5f350dfd0d5 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Wed, 21 Aug 2019 15:32:40 +0100 Subject: [PATCH 08/30] Fix reading of asset type from schema Retain backwards compatibility --- .../assetManagement/views/assetManagementModalView.js | 6 +++--- .../editor/themeEditor/views/editorThemingView.js | 5 +++-- .../modules/scaffold/views/scaffoldAssetItemView.js | 2 +- .../src/modules/scaffold/views/scaffoldAssetView.js | 10 ++++++++-- lib/outputmanager.js | 6 ++++-- 5 files changed, 19 insertions(+), 10 deletions(-) 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 cca37fcbbc..d233aa0456 100644 --- a/frontend/src/modules/editor/themeEditor/views/editorThemingView.js +++ b/frontend/src/modules/editor/themeEditor/views/editorThemingView.js @@ -210,9 +210,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/scaffold/views/scaffoldAssetItemView.js b/frontend/src/modules/scaffold/views/scaffoldAssetItemView.js index 0f638e6f28..c595b064fb 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/lib/outputmanager.js b/lib/outputmanager.js index eeb2799236..89104840f0 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.) From 44de28e03cc11e4a90ba5edd8df96981fa74cab3 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 29 Aug 2019 10:39:44 +0100 Subject: [PATCH 09/30] Add collation to query Retain backwards compatibility --- .../src/modules/projects/views/projectsView.js | 3 ++- lib/dml/mongoose/index.js | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) 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/lib/dml/mongoose/index.js b/lib/dml/mongoose/index.js index 87bc1a1135..6494561a55 100644 --- a/lib/dml/mongoose/index.js +++ b/lib/dml/mongoose/index.js @@ -7,7 +7,8 @@ var Database = require('../../database').Database, fs = require('fs'), path = require('path'), mongoose = require('mongoose'), - mongoUri = require('mongodb-uri'); + mongoUri = require('mongodb-uri'), + semver = require('semver'); _ = require('underscore'); mongoose.Promise = global.Promise; @@ -32,6 +33,7 @@ function MongooseDB() { this.conn = false; this._models = false; this.createdAt = new Date(); + this.mongoVersion = null; } /** @@ -109,6 +111,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({ buildnfo: 1 }, (error, info) => { + if (error) { + logger.log('error', error); + } else { + this.mongoVersion = info.version; + } + }); }.bind(this)); }; @@ -298,6 +307,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 +346,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); } From 95f04bdfd2037540247b279499976971e886e429 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 29 Aug 2019 10:41:59 +0100 Subject: [PATCH 10/30] Move Underscore to correct scope --- lib/dml/mongoose/index.js | 3 +-- lib/permissions.js | 3 ++- lib/tenantmanager.js | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/dml/mongoose/index.js b/lib/dml/mongoose/index.js index 6494561a55..23a3f51bfb 100644 --- a/lib/dml/mongoose/index.js +++ b/lib/dml/mongoose/index.js @@ -9,7 +9,6 @@ var Database = require('../../database').Database, mongoose = require('mongoose'), mongoUri = require('mongodb-uri'), semver = require('semver'); - _ = require('underscore'); mongoose.Promise = global.Promise; mongoose.set('useCreateIndex', true); @@ -111,7 +110,7 @@ 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({ buildnfo: 1 }, (error, info) => { + this.conn.db.command({ buildInfo: 1 }, (error, info) => { if (error) { logger.log('error', error); } else { 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'); From e2c5b8a786e9fe3297b05ddb4f38e316bf12b006 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Fri, 6 Sep 2019 16:42:44 +0100 Subject: [PATCH 11/30] Process symlinks in Handlebars and Less tasks --- Gruntfile.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index cf3cd18a2d..d72ccfd99a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -87,13 +87,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 +221,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'; }); } From f87c88e62b33e49e215251636550790053a771e1 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Mon, 9 Sep 2019 09:33:17 +0100 Subject: [PATCH 12/30] Remove reference to Polyglot string from install script --- install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.js b/install.js index 9bdf1cca98..48e8495295 100644 --- a/install.js +++ b/install.js @@ -478,7 +478,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) { From 81eaeec66a755a39fae608c9452e1e9df92935eb Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 3 Oct 2019 10:43:07 +0100 Subject: [PATCH 13/30] Update dependencies & engines --- package.json | 92 ++++++++++++++++++++++++++-------------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/package.json b/package.json index f841f857b1..a41516f9e8 100644 --- a/package.json +++ b/package.json @@ -14,80 +14,80 @@ "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", + "underscore": "^1.9.1", "unzip": "^0.1.11", - "validator": "^10.2.0", - "winston": "3", - "yauzl": "^2.9.1" + "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" } } From d272834352be27bf61c9084539a631bf5c3a19c3 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 3 Oct 2019 10:46:57 +0100 Subject: [PATCH 14/30] =?UTF-8?q?unzip=20=E2=86=92=20unzipper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See node-unzip/issues/122 --- package.json | 2 +- plugins/content/bower/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a41516f9e8..9a81b1056d 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "serve-favicon": "^2.5.0", "traverse": "^0.6.6", "underscore": "^1.9.1", - "unzip": "^0.1.11", + "unzipper": "^0.10.5", "validator": "^11.1.0", "winston": "^3.2.1", "yauzl": "^2.10.0" diff --git a/plugins/content/bower/index.js b/plugins/content/bower/index.js index 4f8cc970b8..ee6302ac31 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'), From a823fa1bb8f2978eeab354841c5d31ad77a3dbce Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 3 Oct 2019 10:48:18 +0100 Subject: [PATCH 15/30] Upgrade Handlebars --- frontend/src/libraries/handlebars.js | 186 ++++++++++++++++++--------- 1 file changed, 124 insertions(+), 62 deletions(-) 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))']); From b41f88067e715ac5cc5cfb6981348008f1ffd7e8 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 3 Oct 2019 10:50:14 +0100 Subject: [PATCH 16/30] Use new topology engine --- lib/dml/mongoose/index.js | 8 +++++--- test/entry.js | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/dml/mongoose/index.js b/lib/dml/mongoose/index.js index 23a3f51bfb..3e119f34a2 100644 --- a/lib/dml/mongoose/index.js +++ b/lib/dml/mongoose/index.js @@ -70,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 + '@' : ''; 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); From e16b90de27cbe6d09cb275fa4a24596c79fc7017 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Mon, 7 Oct 2019 09:42:24 +0100 Subject: [PATCH 17/30] Refactor --- Gruntfile.js | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 44c0d3c338..abf5536c61 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,6 +4,16 @@ module.exports = function(grunt) { // Project configuration. grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), + 'generate-lang-json': { + options: { + langFileExt: '.json', + src: { + backend: 'routes/lang', + frontend: 'frontend/src/**/lang' + }, + dest: 'temp/lang' + } + }, copy: { main: { files: [ @@ -253,31 +263,28 @@ module.exports = function(grunt) { done(); } }); - // TODO should probably have config up there ^ grunt.registerTask('generate-lang-json', function() { - var _ = require('underscore'); - var fs = require('fs-extra'); - var path = require('path'); + const fs = require('fs-extra'); + const path = require('path'); - var langFileExt = '.json'; - var backendSrc = path.join('routes', 'lang', '*' + langFileExt); - var frontendSrc = path.join('frontend', 'src', '**', 'lang'); - var dest = path.join('temp', 'lang'); + 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({}, backendSrc).forEach(function(filePath) { - var lang = path.basename(filePath, langFileExt); - var data = _.extend({}, fs.readJSONSync(filePath)); + 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({}, path.join(frontendSrc, lang + langFileExt)).forEach(function(filePath2) { - // TODO check for duplicates - _.extend(data, fs.readJSONSync(filePath2)); + grunt.file.expand({}, frontendGlob).forEach(frontendPath => { + data = { ...data, ...fs.readJSONSync(frontendPath) }; }); fs.ensureDirSync(dest); - fs.writeJSONSync(path.join(dest, lang + langFileExt), data, { spaces: 2 }); + fs.writeJSONSync(path.join(dest, basename), data, { spaces: 2 }); }); }); From b0cba9e767299f23605e41e58e7d6450f3c3aa64 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Mon, 7 Oct 2019 17:09:59 +0100 Subject: [PATCH 18/30] Return early if target attribute already exists --- plugins/content/bower/index.js | 129 +++++++++++++++++++-------------- 1 file changed, 74 insertions(+), 55 deletions(-) diff --git a/plugins/content/bower/index.js b/plugins/content/bower/index.js index 4f8cc970b8..bd5388140a 100644 --- a/plugins/content/bower/index.js +++ b/plugins/content/bower/index.js @@ -803,93 +803,112 @@ 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(`Can't add plugin: targetAttribute already exists!`)); } - 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("Can't add plugin: plugin already exists!")); } - - 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); + }); + } + }); }); }); }); From 148bbf602d50d14b78c90d30c5e11d4b646201df Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 10 Oct 2019 11:18:49 +0100 Subject: [PATCH 19/30] Update applyEachSeries calls See async/pull/1640 --- lib/installHelpers.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/installHelpers.js b/lib/installHelpers.js index 9264310dc1..93fb53399c 100644 --- a/lib/installHelpers.js +++ b/lib/installHelpers.js @@ -444,7 +444,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 +456,7 @@ function updateFramework(opts, callback) { installDependencies, purgeCourseFolder, updateFrameworkPlugins - ], opts, callback); + ], opts)(callback); } function checkOptions(opts, action, callback) { From 33108b90f8aa729db78add65e9422dfdb9bb8c4b Mon Sep 17 00:00:00 2001 From: Dan Gray Date: Mon, 14 Oct 2019 11:34:40 +0100 Subject: [PATCH 20/30] Find or create assets tags on import, fixes #2439 --- plugins/output/adapt/helpers.js | 5 ----- plugins/output/adapt/importsource.js | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 7 deletions(-) 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..ab5d5f4f0c 100644 --- a/plugins/output/adapt/importsource.js +++ b/plugins/output/adapt/importsource.js @@ -169,14 +169,27 @@ function ImportSource(req, done) { var fileStat = fs.statSync(assetPath); var assetTitle = assetName; var assetDescription = assetName; - var tags = formTags.slice(); + var tags = []; if (assetsJson[assetName]) { assetTitle = assetsJson[assetName].title; assetDescription = assetsJson[assetName].description; assetsJson[assetName].tags.forEach(function(tag) { - tags.push(tag._id); + dbInstance.retrieve('tag', { title: tag.title}, { fields: '_id' }, function(error, results) { + if (results && results.length > 0) { + tags.push(results[0]._id); + } else { + app.contentmanager.getContentPlugin('tag', function(error, plugin) { + if(!error) { + plugin.create({ title: tag.title}, function(error, record) { + if(error) logger.log('warn', 'Failed to create asset tag: ' + (tag.title || '') + ' ' + error); + tags.push(record._id); + }); + }; + }); + } + }); }); } var fileMeta = { From 5db37863db6ac173b93d194e478e657738aeee26 Mon Sep 17 00:00:00 2001 From: nicola Date: Tue, 15 Oct 2019 11:09:49 +0100 Subject: [PATCH 21/30] Display error information on plugin upload modal --- frontend/src/core/helpers.js | 6 ----- .../views/pluginManagementUploadView.js | 2 +- plugins/content/bower/index.js | 23 +++++++++++-------- routes/lang/en-application.json | 6 +++++ 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/frontend/src/core/helpers.js b/frontend/src/core/helpers.js index 619de89ed7..ce29008315 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); 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/plugins/content/bower/index.js b/plugins/content/bower/index.js index 4f8cc970b8..a7fd7cd326 100644 --- a/plugins/content/bower/index.js +++ b/plugins/content/bower/index.js @@ -813,7 +813,7 @@ function addPackage (plugin, packageInfo, options, cb) { 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(new PluginPackageError(app.polyglot.t('app.versionexists'))); } return addCb(null); } @@ -1058,7 +1058,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 +1104,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 +1134,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 +1149,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/routes/lang/en-application.json b/routes/lang/en-application.json index dc5608859f..5ed621b262 100644 --- a/routes/lang/en-application.json +++ b/routes/lang/en-application.json @@ -410,5 +410,11 @@ "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": "Unrecognized plugin - a plugin should have a bower.json file.", + "app.unrecognisedpluginforpackage": "Unrecognized plugin type for package %{package}.", + "app.incompatibleframework": "This plugin is incompatible with version %{framework} of the Adapt framework.", + "app.versionexists": "You already have this version of the plugin installed.", "app.unknownuser": "Unknown User" } From e543db4accc4437abb253613072c404bd3c3b874 Mon Sep 17 00:00:00 2001 From: canstudios-nicolaw Date: Tue, 15 Oct 2019 13:02:03 +0100 Subject: [PATCH 22/30] Update routes/lang/en-application.json Co-Authored-By: Tom Taylor --- routes/lang/en-application.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/lang/en-application.json b/routes/lang/en-application.json index 5ed621b262..12ed15ce70 100644 --- a/routes/lang/en-application.json +++ b/routes/lang/en-application.json @@ -413,7 +413,7 @@ "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": "Unrecognized plugin - a plugin should have a bower.json file.", - "app.unrecognisedpluginforpackage": "Unrecognized plugin type for package %{package}.", + "app.unrecognisedpluginforpackage": "Unrecognised plugin type for package %{package}.", "app.incompatibleframework": "This plugin is incompatible with version %{framework} of the Adapt framework.", "app.versionexists": "You already have this version of the plugin installed.", "app.unknownuser": "Unknown User" From 972a4c8efc7dc4819394dc6f80f5d87be103a0f6 Mon Sep 17 00:00:00 2001 From: canstudios-nicolaw Date: Tue, 15 Oct 2019 13:02:12 +0100 Subject: [PATCH 23/30] Update routes/lang/en-application.json Co-Authored-By: Tom Taylor --- routes/lang/en-application.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/lang/en-application.json b/routes/lang/en-application.json index 12ed15ce70..41a9899f13 100644 --- a/routes/lang/en-application.json +++ b/routes/lang/en-application.json @@ -412,7 +412,7 @@ "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": "Unrecognized plugin - a plugin should have a bower.json file.", + "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.versionexists": "You already have this version of the plugin installed.", From 0658454ee5f48c206a281a2d97498d80ab781a10 Mon Sep 17 00:00:00 2001 From: Dan Gray Date: Tue, 15 Oct 2019 14:47:32 +0100 Subject: [PATCH 24/30] Remove underscore reference from filestorage plugin --- plugins/filestorage/localfs/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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(); }); From ac2395fcdd6dc140573e255454ab58841b903bb8 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Wed, 16 Oct 2019 09:50:27 +0100 Subject: [PATCH 25/30] Localise error --- plugins/content/bower/index.js | 4 +++- routes/lang/en-application.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/content/bower/index.js b/plugins/content/bower/index.js index 03834391be..6b737d0227 100644 --- a/plugins/content/bower/index.js +++ b/plugins/content/bower/index.js @@ -818,7 +818,9 @@ function addPackage (plugin, packageInfo, options, cb) { } if (targetAttributeExists) { - return addCb(new PluginPackageError(`Can't add plugin: targetAttribute already exists!`)); + return addCb(new PluginPackageError(app.polyglot.t('app.targetattributeexists', { + targetAttribute + }))); } // don't duplicate component.name, component.version diff --git a/routes/lang/en-application.json b/routes/lang/en-application.json index 41a9899f13..0fb8d29ad4 100644 --- a/routes/lang/en-application.json +++ b/routes/lang/en-application.json @@ -415,6 +415,7 @@ "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" } From f63ec4d3f74e52f8caf846d6d51a76d3420b0120 Mon Sep 17 00:00:00 2001 From: Dan Gray Date: Thu, 17 Oct 2019 11:09:33 +0100 Subject: [PATCH 26/30] Remove underscore and return early --- frontend/src/core/helpers.js | 25 ++++---- .../scaffold/views/scaffoldListView.js | 58 +++++++++---------- 2 files changed, 41 insertions(+), 42 deletions(-) diff --git a/frontend/src/core/helpers.js b/frontend/src/core/helpers.js index d1831482ab..145310eb67 100644 --- a/frontend/src/core/helpers.js +++ b/frontend/src/core/helpers.js @@ -345,22 +345,21 @@ define(function(require){ }, flattenNestedProperties: function(properties) { + if (!properties) return {}; var flatProperties = {}; - if (typeof properties !== 'undefined') { - 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]; - } + 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]; } + } else { + flatProperties[key] = properties[key]; } } return flatProperties; diff --git a/frontend/src/modules/scaffold/views/scaffoldListView.js b/frontend/src/modules/scaffold/views/scaffoldListView.js index 49b37f867c..7e3909d658 100644 --- a/frontend/src/modules/scaffold/views/scaffoldListView.js +++ b/frontend/src/modules/scaffold/views/scaffoldListView.js @@ -97,35 +97,35 @@ define([ var flatItem = Helpers.flattenNestedProperties(this.editor.value); var itemValues = _.values(flatItem); var parentAttributes = Origin.scaffold.getCurrentModel().attributes; - _.each(itemValues, function(item) { - if (typeof item === 'string' && item.indexOf('course/assets') !== -1) { - 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') }); - } - }); - } + 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); From a99b9a6e1f84f614aa1f3edd31381e0faa3cdb94 Mon Sep 17 00:00:00 2001 From: Dan Gray Date: Mon, 21 Oct 2019 16:26:24 +0100 Subject: [PATCH 27/30] Add return to error --- plugins/output/adapt/importsource.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/output/adapt/importsource.js b/plugins/output/adapt/importsource.js index ab5d5f4f0c..fd33d7c333 100644 --- a/plugins/output/adapt/importsource.js +++ b/plugins/output/adapt/importsource.js @@ -183,7 +183,7 @@ function ImportSource(req, done) { app.contentmanager.getContentPlugin('tag', function(error, plugin) { if(!error) { plugin.create({ title: tag.title}, function(error, record) { - if(error) logger.log('warn', 'Failed to create asset tag: ' + (tag.title || '') + ' ' + error); + if(error) return logger.log('warn', 'Failed to create asset tag: ' + (tag.title || '') + ' ' + error); tags.push(record._id); }); }; From e24bf59854210eca4efade9f34326b0efd4f7b2c Mon Sep 17 00:00:00 2001 From: Tom Taylor Date: Mon, 21 Oct 2019 17:02:48 +0100 Subject: [PATCH 28/30] Update importsource.js --- plugins/output/adapt/importsource.js | 35 ++++++++++++++-------------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/plugins/output/adapt/importsource.js b/plugins/output/adapt/importsource.js index fd33d7c333..2e5b0a9e5a 100644 --- a/plugins/output/adapt/importsource.js +++ b/plugins/output/adapt/importsource.js @@ -169,26 +169,25 @@ function ImportSource(req, done) { var fileStat = fs.statSync(assetPath); var assetTitle = assetName; var assetDescription = assetName; + var assetJson = assetsJson[assetName]; var tags = []; - if (assetsJson[assetName]) { - assetTitle = assetsJson[assetName].title; - assetDescription = assetsJson[assetName].description; - - assetsJson[assetName].tags.forEach(function(tag) { - dbInstance.retrieve('tag', { title: tag.title}, { fields: '_id' }, function(error, results) { - if (results && results.length > 0) { - tags.push(results[0]._id); - } else { - app.contentmanager.getContentPlugin('tag', function(error, plugin) { - if(!error) { - plugin.create({ title: tag.title}, function(error, record) { - if(error) return logger.log('warn', 'Failed to create asset tag: ' + (tag.title || '') + ' ' + error); - tags.push(record._id); - }); - }; - }); - } + if (assetJson) { + assetTitle = assetJson.title; + assetDescription = assetJson.description; + + 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); + }); }); }); } From 7f969a0533076fd93de42c475806b881aa589b54 Mon Sep 17 00:00:00 2001 From: Tom Greenfield Date: Thu, 17 Oct 2019 14:47:22 +0100 Subject: [PATCH 29/30] Prep for release --- CHANGELOG.md | 20 ++++++++++++++++++++ package.json | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bdc24a2fc..91b705592a 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-21 + +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/package.json b/package.json index 882b5610b6..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": [ From eda41d8bd7608e4345e1f3ab36dd5b9126bcff25 Mon Sep 17 00:00:00 2001 From: tomgreenfield Date: Mon, 21 Oct 2019 17:12:21 +0100 Subject: [PATCH 30/30] Bump date --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91b705592a..1e3e4a0631 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ 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-21 +## [0.10.1] - 2019-10-22 Bugfix release.