mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
fddf2ee42f
Closes #3226 - Remove dependent property from the computed content property that is used to build the active theme selector. - Add validation to the Settings model so that it rejects attempts to set an activeTheme that is not installed.
181 lines
5.9 KiB
JavaScript
181 lines
5.9 KiB
JavaScript
var Settings,
|
|
ghostBookshelf = require('./base'),
|
|
uuid = require('node-uuid'),
|
|
_ = require('lodash'),
|
|
errors = require('../errors'),
|
|
when = require('when'),
|
|
validation = require('../data/validation'),
|
|
|
|
defaultSettings;
|
|
|
|
// For neatness, the defaults file is split into categories.
|
|
// It's much easier for us to work with it as a single level
|
|
// instead of iterating those categories every time
|
|
function parseDefaultSettings() {
|
|
var defaultSettingsInCategories = require('../data/default-settings.json'),
|
|
defaultSettingsFlattened = {};
|
|
|
|
_.each(defaultSettingsInCategories, function (settings, categoryName) {
|
|
_.each(settings, function (setting, settingName) {
|
|
setting.type = categoryName;
|
|
setting.key = settingName;
|
|
defaultSettingsFlattened[settingName] = setting;
|
|
});
|
|
});
|
|
|
|
return defaultSettingsFlattened;
|
|
}
|
|
|
|
function getDefaultSettings() {
|
|
if (!defaultSettings) {
|
|
defaultSettings = parseDefaultSettings();
|
|
}
|
|
|
|
return defaultSettings;
|
|
}
|
|
|
|
// Each setting is saved as a separate row in the database,
|
|
// but the overlying API treats them as a single key:value mapping
|
|
Settings = ghostBookshelf.Model.extend({
|
|
|
|
tableName: 'settings',
|
|
|
|
defaults: function () {
|
|
return {
|
|
uuid: uuid.v4(),
|
|
type: 'core'
|
|
};
|
|
},
|
|
|
|
validate: function () {
|
|
var self = this,
|
|
setting = this.toJSON();
|
|
|
|
return validation.validateSchema(self.tableName, setting).then(function () {
|
|
return validation.validateSettings(getDefaultSettings(), self);
|
|
}).then(function () {
|
|
var themeName = setting.value || '';
|
|
|
|
if (setting.key !== 'activeTheme') {
|
|
return when.resolve();
|
|
}
|
|
|
|
return validation.validateActiveTheme(themeName);
|
|
});
|
|
},
|
|
|
|
saving: function () {
|
|
// disabling sanitization until we can implement a better version
|
|
// All blog setting keys that need their values to be escaped.
|
|
// if (this.get('type') === 'blog' && _.contains(['title', 'description', 'email'], this.get('key'))) {
|
|
// this.set('value', this.sanitize('value'));
|
|
// }
|
|
|
|
return ghostBookshelf.Model.prototype.saving.apply(this, arguments);
|
|
}
|
|
|
|
}, {
|
|
/**
|
|
* Returns an array of keys permitted in a method's `options` hash, depending on the current method.
|
|
* @param {String} methodName The name of the method to check valid options for.
|
|
* @return {Array} Keys allowed in the `options` hash of the model's method.
|
|
*/
|
|
permittedOptions: function (methodName) {
|
|
var options = ghostBookshelf.Model.permittedOptions(),
|
|
|
|
// whitelists for the `options` hash argument on methods, by method name.
|
|
// these are the only options that can be passed to Bookshelf / Knex.
|
|
validOptions = {
|
|
add: ['user'],
|
|
edit: ['user']
|
|
};
|
|
|
|
if (validOptions[methodName]) {
|
|
options = options.concat(validOptions[methodName]);
|
|
}
|
|
|
|
return options;
|
|
},
|
|
|
|
findOne: function (options) {
|
|
// Allow for just passing the key instead of attributes
|
|
if (!_.isObject(options)) {
|
|
options = { key: options };
|
|
}
|
|
return when(ghostBookshelf.Model.findOne.call(this, options));
|
|
},
|
|
|
|
edit: function (data, options) {
|
|
var self = this;
|
|
options = this.filterOptions(options, 'edit');
|
|
|
|
if (!Array.isArray(data)) {
|
|
data = [data];
|
|
}
|
|
|
|
return when.map(data, function (item) {
|
|
// Accept an array of models as input
|
|
if (item.toJSON) { item = item.toJSON(); }
|
|
if (!(_.isString(item.key) && item.key.length > 0)) {
|
|
return when.reject(new errors.ValidationError('Setting key cannot be empty.'));
|
|
}
|
|
|
|
item = self.filterData(item);
|
|
|
|
return Settings.forge({ key: item.key }).fetch(options).then(function (setting) {
|
|
|
|
if (setting) {
|
|
return setting.save({value: item.value}, options);
|
|
}
|
|
|
|
return when.reject(new errors.NotFoundError('Unable to find setting to update: ' + item.key));
|
|
|
|
}, errors.logAndThrowError);
|
|
});
|
|
},
|
|
|
|
populateDefault: function (key) {
|
|
if (!getDefaultSettings()[key]) {
|
|
return when.reject(new errors.NotFoundError('Unable to find default setting: ' + key));
|
|
}
|
|
|
|
// TOOD: databaseVersion and currentVersion special cases?
|
|
|
|
this.findOne({ key: key }).then(function (foundSetting) {
|
|
if (foundSetting) {
|
|
return foundSetting;
|
|
}
|
|
|
|
var defaultSetting = _.clone(getDefaultSettings()[key]);
|
|
defaultSetting.value = defaultSetting.defaultValue;
|
|
|
|
return Settings.forge(defaultSetting).save(null, {user: 1});
|
|
});
|
|
},
|
|
|
|
populateDefaults: function () {
|
|
return this.findAll().then(function (allSettings) {
|
|
var usedKeys = allSettings.models.map(function (setting) { return setting.get('key'); }),
|
|
insertOperations = [];
|
|
|
|
_.each(getDefaultSettings(), function (defaultSetting, defaultSettingKey) {
|
|
var isMissingFromDB = usedKeys.indexOf(defaultSettingKey) === -1;
|
|
// Temporary code to deal with old databases with currentVersion settings
|
|
if (defaultSettingKey === 'databaseVersion' && usedKeys.indexOf('currentVersion') !== -1) {
|
|
isMissingFromDB = false;
|
|
}
|
|
if (isMissingFromDB) {
|
|
defaultSetting.value = defaultSetting.defaultValue;
|
|
insertOperations.push(Settings.forge(defaultSetting).save(null, {user: 1}));
|
|
}
|
|
});
|
|
|
|
return when.all(insertOperations);
|
|
});
|
|
}
|
|
|
|
});
|
|
|
|
module.exports = {
|
|
Settings: Settings
|
|
};
|