2021-07-06 13:02:37 +03:00
|
|
|
const debug = require('@tryghost/debug')('themes');
|
2019-07-01 17:56:23 +03:00
|
|
|
const fs = require('fs-extra');
|
2021-07-07 13:51:04 +03:00
|
|
|
const ObjectID = require('bson-objectid');
|
|
|
|
|
|
|
|
const tpl = require('@tryghost/tpl');
|
|
|
|
const logging = require('@tryghost/logging');
|
|
|
|
const errors = require('@tryghost/errors');
|
2019-07-01 17:56:23 +03:00
|
|
|
|
|
|
|
const validate = require('./validate');
|
|
|
|
const list = require('./list');
|
2019-07-09 17:35:18 +03:00
|
|
|
const ThemeStorage = require('./ThemeStorage');
|
2019-07-01 17:56:23 +03:00
|
|
|
const themeLoader = require('./loader');
|
|
|
|
const toJSON = require('./to-json');
|
|
|
|
|
2021-06-30 16:56:57 +03:00
|
|
|
const settingsCache = require('../../../shared/settings-cache');
|
2021-07-07 13:51:04 +03:00
|
|
|
const bridge = require('../../../bridge');
|
2019-07-01 17:56:23 +03:00
|
|
|
|
2021-07-06 17:38:28 +03:00
|
|
|
const messages = {
|
|
|
|
themeDoesNotExist: 'Theme does not exist.',
|
|
|
|
invalidThemeName: 'Please select a valid theme.',
|
|
|
|
overrideCasper: 'Please rename your zip, it\'s not allowed to override the default casper theme.',
|
|
|
|
destroyCasper: 'Deleting the default casper theme is not allowed.',
|
|
|
|
destroyActive: 'Deleting the active theme is not allowed.'
|
|
|
|
};
|
|
|
|
|
2019-07-01 17:56:23 +03:00
|
|
|
let themeStorage;
|
|
|
|
|
|
|
|
const getStorage = () => {
|
2019-07-09 17:35:18 +03:00
|
|
|
themeStorage = themeStorage || new ThemeStorage();
|
2019-07-01 17:56:23 +03:00
|
|
|
|
|
|
|
return themeStorage;
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
2021-07-07 13:51:04 +03:00
|
|
|
getZip: async (themeName) => {
|
2019-07-01 17:56:23 +03:00
|
|
|
const theme = list.get(themeName);
|
|
|
|
|
|
|
|
if (!theme) {
|
2021-07-07 13:51:04 +03:00
|
|
|
throw new errors.BadRequestError({
|
2021-07-06 17:38:28 +03:00
|
|
|
message: tpl(messages.invalidThemeName)
|
2021-07-07 13:51:04 +03:00
|
|
|
});
|
2019-07-01 17:56:23 +03:00
|
|
|
}
|
|
|
|
|
2021-07-07 13:51:04 +03:00
|
|
|
return await getStorage().serve({
|
2019-07-01 17:56:23 +03:00
|
|
|
name: themeName
|
|
|
|
});
|
|
|
|
},
|
2021-07-07 13:51:04 +03:00
|
|
|
setFromZip: async (zip) => {
|
2019-07-01 17:56:23 +03:00
|
|
|
const shortName = getStorage().getSanitizedFileName(zip.name.split('.zip')[0]);
|
2020-06-03 18:29:34 +03:00
|
|
|
const backupName = `${shortName}_${ObjectID()}`;
|
2019-07-01 17:56:23 +03:00
|
|
|
|
|
|
|
// check if zip name is casper.zip
|
|
|
|
if (zip.name === 'casper.zip') {
|
2020-05-22 21:22:20 +03:00
|
|
|
throw new errors.ValidationError({
|
2021-07-06 17:38:28 +03:00
|
|
|
message: tpl(messages.overrideCasper)
|
2019-07-01 17:56:23 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
let checkedTheme;
|
2021-07-07 13:51:04 +03:00
|
|
|
let overrideTheme;
|
2021-02-23 11:49:48 +03:00
|
|
|
let renamedExisting = false;
|
2019-07-01 17:56:23 +03:00
|
|
|
|
2021-07-07 13:51:04 +03:00
|
|
|
try {
|
|
|
|
checkedTheme = await validate.checkSafe(zip, true);
|
|
|
|
const themeExists = await getStorage().exists(shortName);
|
|
|
|
// CASE: move the existing theme to a backup folder
|
|
|
|
if (themeExists) {
|
|
|
|
renamedExisting = true;
|
|
|
|
await getStorage().rename(shortName, backupName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// CASE: store extracted theme
|
|
|
|
await getStorage().save({
|
|
|
|
name: shortName,
|
|
|
|
path: checkedTheme.path
|
|
|
|
});
|
|
|
|
// CASE: loads the theme from the fs & sets the theme on the themeList
|
|
|
|
const loadedTheme = await themeLoader.loadOneTheme(shortName);
|
|
|
|
overrideTheme = (shortName === settingsCache.get('active_theme'));
|
|
|
|
// CASE: if this is the active theme, we are overriding
|
|
|
|
if (overrideTheme) {
|
|
|
|
debug('Activating theme (method C, on API "override")', shortName);
|
|
|
|
bridge.activateTheme(loadedTheme, checkedTheme);
|
|
|
|
}
|
|
|
|
|
|
|
|
// @TODO: unify the name across gscan and Ghost!
|
|
|
|
return {
|
|
|
|
themeOverridden: overrideTheme,
|
|
|
|
theme: toJSON(shortName, checkedTheme)
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
// restore backup if we renamed an existing theme but saving failed
|
|
|
|
if (renamedExisting) {
|
|
|
|
return getStorage().exists(shortName).then((themeExists) => {
|
|
|
|
if (!themeExists) {
|
|
|
|
return getStorage().rename(backupName, shortName).then(() => {
|
|
|
|
throw error;
|
2019-07-01 17:56:23 +03:00
|
|
|
});
|
2021-07-07 13:51:04 +03:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
throw error;
|
|
|
|
} finally {
|
|
|
|
// @TODO: we should probably do this as part of saving the theme
|
|
|
|
// CASE: remove extracted dir from gscan happens in background
|
|
|
|
if (checkedTheme) {
|
|
|
|
fs.remove(checkedTheme.path)
|
2020-06-03 18:29:34 +03:00
|
|
|
.catch((err) => {
|
|
|
|
logging.error(new errors.GhostError({err: err}));
|
|
|
|
});
|
2021-07-07 13:51:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// CASE: remove the backup we created earlier
|
|
|
|
getStorage()
|
|
|
|
.delete(backupName)
|
|
|
|
.catch((err) => {
|
|
|
|
logging.error(new errors.GhostError({err: err}));
|
|
|
|
});
|
|
|
|
}
|
2019-07-01 17:56:23 +03:00
|
|
|
},
|
2021-07-07 13:51:04 +03:00
|
|
|
destroy: async function (themeName) {
|
2019-07-01 17:56:23 +03:00
|
|
|
if (themeName === 'casper') {
|
2020-05-22 21:22:20 +03:00
|
|
|
throw new errors.ValidationError({
|
2021-07-06 17:38:28 +03:00
|
|
|
message: tpl(messages.destroyCasper)
|
2019-07-01 17:56:23 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (themeName === settingsCache.get('active_theme')) {
|
2020-05-22 21:22:20 +03:00
|
|
|
throw new errors.ValidationError({
|
2021-07-06 17:38:28 +03:00
|
|
|
message: tpl(messages.destroyActive)
|
2019-07-01 17:56:23 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const theme = list.get(themeName);
|
|
|
|
|
|
|
|
if (!theme) {
|
2020-05-22 21:22:20 +03:00
|
|
|
throw new errors.NotFoundError({
|
2021-07-06 17:38:28 +03:00
|
|
|
message: tpl(messages.themeDoesNotExist)
|
2019-07-01 17:56:23 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-07 13:51:04 +03:00
|
|
|
let result = await getStorage().delete(themeName);
|
|
|
|
list.del(themeName);
|
|
|
|
return result;
|
2019-07-01 17:56:23 +03:00
|
|
|
}
|
|
|
|
};
|