0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Ensure hidden settings return falsy when used with the @custom helper (#17920)

no refs

When a theme setting is hidden, it should return a falsy value when used
This commit is contained in:
Michael Barrett 2023-09-13 08:38:31 +01:00 committed by GitHub
parent 212eacf4b9
commit 46897bdb3a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 31 deletions

View file

@ -72,7 +72,7 @@ export default class DesignMenuComponent extends Component {
@action @action
handleThemeSettingChange() { handleThemeSettingChange() {
this.customThemeSettings.rebuildSettingGroups(); this.customThemeSettings.updateSettingsVisibility();
this.themeManagement.updatePreviewHtmlTask.perform(); this.themeManagement.updatePreviewHtmlTask.perform();
} }

View file

@ -1,20 +1,22 @@
<div class="gh-stack"> <div class="gh-stack">
<form> <form>
{{#each @themeSettings as |setting index|}} {{#each @themeSettings as |setting index|}}
{{#if (eq setting.type "select")}} {{#if setting.visible}}
<CustomThemeSettings::Select @setting={{setting}} @index={{index}} @onChange={{@onChange}} /> {{#if (eq setting.type "select")}}
{{/if}} <CustomThemeSettings::Select @setting={{setting}} @index={{index}} @onChange={{@onChange}} />
{{#if (eq setting.type "boolean")}} {{/if}}
<CustomThemeSettings::Boolean @setting={{setting}} @index={{index}} @onChange={{@onChange}} /> {{#if (eq setting.type "boolean")}}
{{/if}} <CustomThemeSettings::Boolean @setting={{setting}} @index={{index}} @onChange={{@onChange}} />
{{#if (eq setting.type "color")}} {{/if}}
<CustomThemeSettings::Color @setting={{setting}} @index={{index}} @onChange={{@onChange}} /> {{#if (eq setting.type "color")}}
{{/if}} <CustomThemeSettings::Color @setting={{setting}} @index={{index}} @onChange={{@onChange}} />
{{#if (eq setting.type "text")}} {{/if}}
<CustomThemeSettings::Text @setting={{setting}} @index={{index}} @onChange={{@onChange}} /> {{#if (eq setting.type "text")}}
{{/if}} <CustomThemeSettings::Text @setting={{setting}} @index={{index}} @onChange={{@onChange}} />
{{#if (eq setting.type "image")}} {{/if}}
<CustomThemeSettings::Image @setting={{setting}} @index={{index}} @onChange={{@onChange}} /> {{#if (eq setting.type "image")}}
<CustomThemeSettings::Image @setting={{setting}} @index={{index}} @onChange={{@onChange}} />
{{/if}}
{{/if}} {{/if}}
{{/each}} {{/each}}
</form> </form>

View file

@ -8,5 +8,6 @@ export default Model.extend({
default: attr('string'), default: attr('string'),
value: attr(), value: attr(),
group: attr('string'), group: attr('string'),
visibility: attr('string') visibility: attr('string'),
visible: attr('boolean', {defaultValue: true})
}); });

View file

@ -5,6 +5,8 @@ import {run} from '@ember/runloop';
import {task} from 'ember-concurrency'; import {task} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking'; import {tracked} from '@glimmer/tracking';
const HIDDEN_SETTING_VALUE = null;
export default class CustomThemeSettingsServices extends Service { export default class CustomThemeSettingsServices extends Service {
@service store; @service store;
@ -34,7 +36,7 @@ export default class CustomThemeSettingsServices extends Service {
const keyValue = {}; const keyValue = {};
this.settings.forEach((setting) => { this.settings.forEach((setting) => {
keyValue[setting.key] = setting.value; keyValue[setting.key] = this._isSettingVisible(setting) ? setting.value : HIDDEN_SETTING_VALUE;
}); });
return keyValue; return keyValue;
@ -64,6 +66,8 @@ export default class CustomThemeSettingsServices extends Service {
this.settings = settings; this.settings = settings;
this.settingGroups = this._buildSettingGroups(settings); this.settingGroups = this._buildSettingGroups(settings);
this.updateSettingsVisibility();
this._hasLoaded = true; this._hasLoaded = true;
return this.settings; return this.settings;
@ -93,8 +97,10 @@ export default class CustomThemeSettingsServices extends Service {
this.settings.forEach(setting => setting.rollbackAttributes()); this.settings.forEach(setting => setting.rollbackAttributes());
} }
rebuildSettingGroups() { updateSettingsVisibility() {
this.settingGroups = this._buildSettingGroups(this.settings); this.settings.forEach((setting) => {
setting.visible = this._isSettingVisible(setting);
});
} }
_buildSettingGroups(settings) { _buildSettingGroups(settings) {
@ -116,15 +122,7 @@ export default class CustomThemeSettingsServices extends Service {
} }
this.KNOWN_GROUPS.forEach((knownGroup) => { this.KNOWN_GROUPS.forEach((knownGroup) => {
const groupSettings = settings const groupSettings = settings.filter(setting => setting.group === knownGroup.key);
.filter(setting => setting.group === knownGroup.key)
.filter((setting) => {
if (setting.visibility) {
return nql(setting.visibility).queryJSON(this.keyValueObject);
}
return true;
});
if (groupSettings.length) { if (groupSettings.length) {
groups.push(Object.assign({}, knownGroup, {settings: groupSettings})); groups.push(Object.assign({}, knownGroup, {settings: groupSettings}));
@ -133,4 +131,14 @@ export default class CustomThemeSettingsServices extends Service {
return groups; return groups;
} }
_isSettingVisible(setting) {
if (!setting.visibility) {
return true;
}
const settingsMap = this.settings.reduce((map, {key, value}) => ({...map, [key]: value}), {});
return nql(setting.visibility).queryJSON(settingsMap);
}
} }

View file

@ -1,5 +1,6 @@
const _ = require('lodash'); const _ = require('lodash');
const BREAD = require('./CustomThemeSettingsBREADService'); const BREAD = require('./CustomThemeSettingsBREADService');
const nql = require('@tryghost/nql');
const tpl = require('@tryghost/tpl'); const tpl = require('@tryghost/tpl');
const {ValidationError} = require('@tryghost/errors'); const {ValidationError} = require('@tryghost/errors');
const debug = require('@tryghost/debug')('custom-theme-settings-service'); const debug = require('@tryghost/debug')('custom-theme-settings-service');
@ -10,6 +11,8 @@ const messages = {
invalidValueForSetting: 'Invalid value for \'{key}\'. The value must follow this format: {format}.' invalidValueForSetting: 'Invalid value for \'{key}\'. The value must follow this format: {format}.'
}; };
const HIDDEN_SETTING_VALUE = null;
module.exports = class CustomThemeSettingsService { module.exports = class CustomThemeSettingsService {
/** /**
* @param {Object} options * @param {Object} options
@ -150,11 +153,15 @@ module.exports = class CustomThemeSettingsService {
this._activeThemeSettings[setting.key].value = setting.value; this._activeThemeSettings[setting.key].value = setting.value;
} }
const settingsObjects = this.listSettings();
// update the public cache // update the public cache
this._valueCache.populate(this.listSettings()); this._valueCache.populate(
this._computeCachedSettings(settingsObjects)
);
// return full setting objects // return full setting objects
return this.listSettings(); return settingsObjects;
} }
// Private ----------------------------------------------------------------- // Private -----------------------------------------------------------------
@ -233,7 +240,9 @@ module.exports = class CustomThemeSettingsService {
return; return;
} }
this._valueCache.populate(settings); this._valueCache.populate(
this._computeCachedSettings(settings)
);
} }
/** /**
@ -264,4 +273,26 @@ module.exports = class CustomThemeSettingsService {
this._activeThemeSettings = activeThemeSettings; this._activeThemeSettings = activeThemeSettings;
} }
/**
* Compute the settings to cache, taking into account visibility rules
*
* @param {Object[]} settings - list of setting objects
* @returns {Object[]} - list of setting objects with visibility rules applied
* @private
*/
_computeCachedSettings(settings) {
const settingsMap = settings.reduce((map, {key, value}) => ({...map, [key]: value}), {});
return settings.map((setting) => {
return {
...setting,
// If a setting is not visible, set the value to HIDDEN_SETTING_VALUE so that it is not exposed in the cache
// (meaning it also won't be exposed in the theme when rendering)
value: setting.visibility && nql(setting.visibility).queryJSON(settingsMap) === false
? HIDDEN_SETTING_VALUE
: setting.value
};
});
}
}; };

View file

@ -27,6 +27,7 @@
"dependencies": { "dependencies": {
"@tryghost/debug": "0.1.24", "@tryghost/debug": "0.1.24",
"@tryghost/errors": "1.2.24", "@tryghost/errors": "1.2.24",
"@tryghost/nql": "0.11.0",
"@tryghost/tpl": "0.1.24", "@tryghost/tpl": "0.1.24",
"lodash": "4.17.21" "lodash": "4.17.21"
} }

View file

@ -875,5 +875,36 @@ describe('Service', function () {
}] }]
).should.be.resolved(); ).should.be.resolved();
}); });
it('does not expose hidden settings in the public cache', async function () {
const HIDDEN_SETTING_VALUE = null;
const settingName = 'foo';
const settingDefinition = {
type: 'select',
options: ['Foo', 'Bar', 'Baz'],
default: 'Foo',
visibility: 'some_other_setting:bar'
};
await service.activateTheme('test', {
name: 'test',
customSettings: {
[settingName]: settingDefinition
}
});
await service.updateSettings(
[{
key: settingName,
value: 'Foo',
...settingDefinition
}]
);
cache.getAll().should.deepEqual({
[settingName]: HIDDEN_SETTING_VALUE
});
});
}); });
}); });