diff --git a/core/server/apps/amp/lib/helpers/amp_components.js b/core/server/apps/amp/lib/helpers/amp_components.js index 6b3ee65b73..19fc43bd31 100644 --- a/core/server/apps/amp/lib/helpers/amp_components.js +++ b/core/server/apps/amp/lib/helpers/amp_components.js @@ -7,7 +7,9 @@ // Here's the list of all supported extended components: https://www.ampproject.org/docs/reference/extended.html // By default supported AMP HTML tags (no additional script tag necessary): // amp-img, amp-ad, amp-embed, amp-video and amp-pixel. -var hbs = require('express-hbs'); +// (less) dirty requires +var proxy = require('../../../../helpers/proxy'), + SafeString = proxy.SafeString; function ampComponents() { var components = [], @@ -29,7 +31,7 @@ function ampComponents() { components.push(''); } - return new hbs.handlebars.SafeString(components.join('\n')); + return new SafeString(components.join('\n')); } module.exports = ampComponents; diff --git a/core/server/apps/amp/lib/helpers/amp_content.js b/core/server/apps/amp/lib/helpers/amp_content.js index 6a618bf819..736dda10fa 100644 --- a/core/server/apps/amp/lib/helpers/amp_content.js +++ b/core/server/apps/amp/lib/helpers/amp_content.js @@ -6,16 +6,19 @@ // // Converts normal HTML into AMP HTML with Amperize module and uses a cache to return it from // there if available. The cacheId is a combination of `updated_at` and the `slug`. -var hbs = require('express-hbs'), - Promise = require('bluebird'), - moment = require('moment'), - logging = require('../../../../logging'), - i18n = require('../../../../i18n'), - errors = require('../../../../errors'), - makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'), - utils = require('../../../../utils'), - amperizeCache = {}, - allowedAMPTags = [], +var Promise = require('bluebird'), + moment = require('moment'), + + // (less) dirty requires + proxy = require('../../../../helpers/proxy'), + SafeString = proxy.SafeString, + logging = proxy.logging, + i18n = proxy.i18n, + errors = proxy.errors, + makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'), + utils = require('../../../../utils'), + amperizeCache = {}, + allowedAMPTags = [], allowedAMPAttributes = {}, amperize, cleanHTML, @@ -190,7 +193,7 @@ function ampContent() { selfClosing: ['source', 'track'] }); - return new hbs.handlebars.SafeString(cleanHTML); + return new SafeString(cleanHTML); }); } diff --git a/core/server/apps/private-blogging/lib/helpers/input_password.js b/core/server/apps/private-blogging/lib/helpers/input_password.js index bc249cac97..3e8bd18ddb 100644 --- a/core/server/apps/private-blogging/lib/helpers/input_password.js +++ b/core/server/apps/private-blogging/lib/helpers/input_password.js @@ -6,12 +6,12 @@ // We use the name input_password to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -// Dirty requires -var hbs = require('express-hbs'), - utils = require('../../../../helpers/utils'), - input_password; +// (less) dirty requires +var proxy = require('../../../../helpers/proxy'), + SafeString = proxy.SafeString, + templates = proxy.templates; -input_password = function (options) { +module.exports = function input_password(options) { options = options || {}; options.hash = options.hash || {}; @@ -23,14 +23,12 @@ input_password = function (options) { extras += ' placeholder="' + options.hash.placeholder + '"'; } - output = utils.inputTemplate({ + output = templates.input({ type: 'password', name: 'password', className: className, extras: extras }); - return new hbs.handlebars.SafeString(output); + return new SafeString(output); }; - -module.exports = input_password; diff --git a/core/server/apps/proxy.js b/core/server/apps/proxy.js index e8558b3322..8a4593352b 100644 --- a/core/server/apps/proxy.js +++ b/core/server/apps/proxy.js @@ -1,6 +1,6 @@ var _ = require('lodash'), api = require('../api'), - helpers = require('../helpers'), + helpers = require('../helpers/register'), filters = require('../filters'), i18n = require('../i18n'), generateProxyFunctions; diff --git a/core/server/apps/subscribers/lib/helpers/input_email.js b/core/server/apps/subscribers/lib/helpers/input_email.js index 24f93f81c0..b6343d4edc 100644 --- a/core/server/apps/subscribers/lib/helpers/input_email.js +++ b/core/server/apps/subscribers/lib/helpers/input_email.js @@ -6,12 +6,12 @@ // We use the name input_email to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -// Dirty requires -var hbs = require('express-hbs'), - utils = require('../../../../helpers/utils'), - input_email; +// (less) dirty requires +var proxy = require('../../../../helpers/proxy'), + SafeString = proxy.SafeString, + templates = proxy.templates; -input_email = function (options) { +module.exports = function input_email(options) { options = options || {}; options.hash = options.hash || {}; @@ -31,14 +31,13 @@ input_email = function (options) { extras += ' value="' + options.hash.value + '"'; } - output = utils.inputTemplate({ + output = templates.input({ type: 'email', name: 'email', className: className, extras: extras }); - return new hbs.handlebars.SafeString(output); + return new SafeString(output); }; -module.exports = input_email; diff --git a/core/server/apps/subscribers/lib/helpers/subscribe_form.js b/core/server/apps/subscribers/lib/helpers/subscribe_form.js index d133e10d96..92ceff3b02 100644 --- a/core/server/apps/subscribers/lib/helpers/subscribe_form.js +++ b/core/server/apps/subscribers/lib/helpers/subscribe_form.js @@ -5,20 +5,19 @@ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers var _ = require('lodash'), - // Dirty requires - hbs = require('express-hbs'), - config = require('../../../../config'), - template = require('../../../../helpers/template'), - utils = require('../../../../helpers/utils'), - globalUtils = require('../../../../utils'), + // (Less) dirty requires + proxy = require('../../../../helpers/proxy'), + templates = proxy.templates, + config = proxy.config, + url = proxy.url, + SafeString = proxy.SafeString, params = ['error', 'success', 'email'], - subscribe_form, subscribeScript; function makeHidden(name, extras) { - return utils.inputTemplate({ + return templates.input({ type: 'hidden', name: name, className: name, @@ -39,19 +38,17 @@ subscribeScript = '})(window,document,\'querySelector\',\'value\');' + ''; -subscribe_form = function (options) { +module.exports = function subscribe_form(options) { var root = options.data.root, data = _.merge({}, options.hash, _.pick(root, params), { - action: globalUtils.url.urlJoin('/', globalUtils.url.getSubdir(), config.get('routeKeywords').subscribe, '/'), - script: new hbs.handlebars.SafeString(subscribeScript), - hidden: new hbs.handlebars.SafeString( + action: url.urlJoin('/', url.getSubdir(), config.get('routeKeywords').subscribe, '/'), + script: new SafeString(subscribeScript), + hidden: new SafeString( makeHidden('confirm') + makeHidden('location', root.subscribed_url ? 'value=' + root.subscribed_url : '') + makeHidden('referrer', root.subscribed_referrer ? 'value=' + root.subscribed_referrer : '') ) }); - return template.execute('subscribe_form', data, options); + return templates.execute('subscribe_form', data, options); }; - -module.exports = subscribe_form; diff --git a/core/server/data/meta/keywords.js b/core/server/data/meta/keywords.js index 90db07c0d5..96eb180344 100644 --- a/core/server/data/meta/keywords.js +++ b/core/server/data/meta/keywords.js @@ -1,4 +1,4 @@ -var visibilityFilter = require('../../utils/visibility-filter'); +var visibilityFilter = require('../../utils/visibility').filter; function getKeywords(data) { if (data.post && data.post.tags && data.post.tags.length > 0) { diff --git a/core/server/data/meta/schema.js b/core/server/data/meta/schema.js index 4aad9067ee..7efcd69fdb 100644 --- a/core/server/data/meta/schema.js +++ b/core/server/data/meta/schema.js @@ -1,7 +1,6 @@ var config = require('../../config'), - hbs = require('express-hbs'), + escapeExpression = require('../../themes/engine').escapeExpression, socialUrls = require('../../utils/social-urls'), - escapeExpression = hbs.handlebars.Utils.escapeExpression, _ = require('lodash'); function schemaImageObject(metaDataVal) { diff --git a/core/server/helpers/asset.js b/core/server/helpers/asset.js index 905a200274..62c4a6fcee 100644 --- a/core/server/helpers/asset.js +++ b/core/server/helpers/asset.js @@ -2,12 +2,12 @@ // Usage: `{{asset "css/screen.css"}}`, `{{asset "css/screen.css" ghost="true"}}` // // Returns the path to the specified asset. The ghost flag outputs the asset path for the Ghost admin +var proxy = require('./proxy'), + config = proxy.config, + getAssetUrl = proxy.metaData.getAssetUrl, + SafeString = proxy.SafeString; -var config = require('../config'), - getAssetUrl = require('../data/meta/asset_url'), - hbs = require('express-hbs'); - -function asset(path, options) { +module.exports = function asset(path, options) { var isAdmin, minify; @@ -20,9 +20,7 @@ function asset(path, options) { minify = false; } - return new hbs.handlebars.SafeString( + return new SafeString( getAssetUrl(path, isAdmin, minify) ); -} - -module.exports = asset; +}; diff --git a/core/server/helpers/author.js b/core/server/helpers/author.js index 53bd4a527e..aa8f14e797 100644 --- a/core/server/helpers/author.js +++ b/core/server/helpers/author.js @@ -10,15 +10,16 @@ // Block helper: `{{#author}}{{/author}}` // This is the default handlebars behaviour of dropping into the author object scope -var hbs = require('express-hbs'), - _ = require('lodash'), - utils = require('../utils'), - localUtils = require('./utils'), - author; +var proxy = require('./proxy'), + _ = require('lodash'), + SafeString = proxy.SafeString, + handlebars = proxy.hbs.handlebars, + templates = proxy.templates, + url = proxy.url; -author = function (options) { +module.exports = function author(options) { if (options.fn) { - return hbs.handlebars.helpers.with.call(this, this.author, options); + return handlebars.helpers.with.call(this, this.author, options); } var autolink = _.isString(options.hash.autolink) && options.hash.autolink === 'false' ? false : true, @@ -26,8 +27,8 @@ author = function (options) { if (this.author && this.author.name) { if (autolink) { - output = localUtils.linkTemplate({ - url: utils.url.urlFor('author', {author: this.author}), + output = templates.link({ + url: url.urlFor('author', {author: this.author}), text: _.escape(this.author.name) }); } else { @@ -35,7 +36,5 @@ author = function (options) { } } - return new hbs.handlebars.SafeString(output); + return new SafeString(output); }; - -module.exports = author; diff --git a/core/server/helpers/body_class.js b/core/server/helpers/body_class.js index ac7d390b48..3718ef45f1 100644 --- a/core/server/helpers/body_class.js +++ b/core/server/helpers/body_class.js @@ -6,11 +6,11 @@ // We use the name body_class to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var hbs = require('express-hbs'), - _ = require('lodash'), - body_class; +var proxy = require('./proxy'), + _ = require('lodash'), + SafeString = proxy.SafeString; -body_class = function (options) { +module.exports = function body_class(options) { var classes = [], context = options.data.root.context, post = this.post, @@ -43,7 +43,6 @@ body_class = function (options) { } classes = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, ''); - return new hbs.handlebars.SafeString(classes.trim()); + return new SafeString(classes.trim()); }; -module.exports = body_class; diff --git a/core/server/helpers/content.js b/core/server/helpers/content.js index 9c7f25faed..6c1f32d591 100644 --- a/core/server/helpers/content.js +++ b/core/server/helpers/content.js @@ -6,12 +6,12 @@ // // Enables tag-safe truncation of content by characters or words. -var hbs = require('express-hbs'), - _ = require('lodash'), - downsize = require('downsize'), - content; +var proxy = require('./proxy'), + _ = require('lodash'), + downsize = require('downsize'), + SafeString = proxy.SafeString; -content = function (options) { +module.exports = function content(options) { var truncateOptions = (options || {}).hash || {}; truncateOptions = _.pick(truncateOptions, ['words', 'characters']); _.keys(truncateOptions).map(function (key) { @@ -19,12 +19,10 @@ content = function (options) { }); if (truncateOptions.hasOwnProperty('words') || truncateOptions.hasOwnProperty('characters')) { - return new hbs.handlebars.SafeString( + return new SafeString( downsize(this.html, truncateOptions) ); } - return new hbs.handlebars.SafeString(this.html); + return new SafeString(this.html); }; - -module.exports = content; diff --git a/core/server/helpers/date.js b/core/server/helpers/date.js index 77d0cf2577..760716d79d 100644 --- a/core/server/helpers/date.js +++ b/core/server/helpers/date.js @@ -3,11 +3,13 @@ // // Formats a date using moment-timezone.js. Formats published_at by default but will also take a date as a parameter -var moment = require('moment-timezone'), - date, - timezone; +var proxy = require('./proxy'), + moment = require('moment-timezone'), + SafeString = proxy.SafeString; + +module.exports = function (date, options) { + var timezone, format, timeago, timeNow; -date = function (date, options) { if (!options && date.hasOwnProperty('hash')) { options = date; date = undefined; @@ -23,17 +25,15 @@ date = function (date, options) { // ensure that context is undefined, not null, as that can cause errors date = date === null ? undefined : date; - var f = options.hash.format || 'MMM DD, YYYY', - timeago = options.hash.timeago, - timeNow = moment().tz(timezone); + format = options.hash.format || 'MMM DD, YYYY'; + timeago = options.hash.timeago; + timeNow = moment().tz(timezone); if (timeago) { - date = timezone ? moment(date).tz(timezone).from(timeNow) : moment(date).fromNow(); + date = timezone ? moment(date).tz(timezone).from(timeNow) : moment(date).fromNow(); } else { - date = timezone ? moment(date).tz(timezone).format(f) : moment(date).format(f); + date = timezone ? moment(date).tz(timezone).format(format) : moment(date).format(format); } - return date; + return new SafeString(date); }; - -module.exports = date; diff --git a/core/server/helpers/encode.js b/core/server/helpers/encode.js index 661a92d64e..b1978837c9 100644 --- a/core/server/helpers/encode.js +++ b/core/server/helpers/encode.js @@ -4,12 +4,10 @@ // // Returns URI encoded string -var hbs = require('express-hbs'), - encode; +var proxy = require('./proxy'), + SafeString = proxy.SafeString; -encode = function (string, options) { +module.exports = function encode(string, options) { var uri = string || options; - return new hbs.handlebars.SafeString(encodeURIComponent(uri)); + return new SafeString(encodeURIComponent(uri)); }; - -module.exports = encode; diff --git a/core/server/helpers/excerpt.js b/core/server/helpers/excerpt.js index 88196800ba..7eb71c3b23 100644 --- a/core/server/helpers/excerpt.js +++ b/core/server/helpers/excerpt.js @@ -5,11 +5,12 @@ // // Defaults to words="50" -var hbs = require('express-hbs'), - _ = require('lodash'), - getMetaDataExcerpt = require('../data/meta/excerpt'); +var proxy = require('./proxy'), + _ = require('lodash'), + SafeString = proxy.SafeString, + getMetaDataExcerpt = proxy.metaData.getMetaDataExcerpt; -function excerpt(options) { +module.exports = function excerpt(options) { var truncateOptions = (options || {}).hash || {}; truncateOptions = _.pick(truncateOptions, ['words', 'characters']); @@ -17,9 +18,7 @@ function excerpt(options) { truncateOptions[key] = parseInt(truncateOptions[key], 10); }); - return new hbs.handlebars.SafeString( + return new SafeString( getMetaDataExcerpt(String(this.html), truncateOptions) ); -} - -module.exports = excerpt; +}; diff --git a/core/server/helpers/facebook_url.js b/core/server/helpers/facebook_url.js index d3030c17e5..0d1bd101d3 100644 --- a/core/server/helpers/facebook_url.js +++ b/core/server/helpers/facebook_url.js @@ -6,11 +6,11 @@ // We use the name facebook_url to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var socialUrls = require('../utils/social-urls'), - findKey = require('./utils').findKey, - facebook_url; +var proxy = require('./proxy'), + socialUrls = proxy.socialUrls, + findKey = proxy.utils.findKey; -facebook_url = function (username, options) { +module.exports = function facebook_url(username, options) { if (!options) { options = username; username = findKey('facebook', this, options.data.blog); @@ -22,5 +22,3 @@ facebook_url = function (username, options) { return null; }; - -module.exports = facebook_url; diff --git a/core/server/helpers/foreach.js b/core/server/helpers/foreach.js index 69e34a2f94..7a18f9208a 100644 --- a/core/server/helpers/foreach.js +++ b/core/server/helpers/foreach.js @@ -2,23 +2,21 @@ // Usage: `{{#foreach data}}{{/foreach}}` // // Block helper designed for looping through posts -var hbs = require('express-hbs'), - _ = require('lodash'), - logging = require('../logging'), - i18n = require('../i18n'), - visibilityFilter = require('../utils/visibility-filter'), - utils = require('./utils'), - - hbsUtils = hbs.handlebars.Utils, - foreach; +var proxy = require('./proxy'), + _ = require('lodash'), + logging = proxy.logging, + i18n = proxy.i18n, + visibilityUtils = proxy.visibility, + hbsUtils = proxy.hbs.Utils, + createFrame = proxy.hbs.handlebars.createFrame; function filterItemsByVisibility(items, options) { - var visibility = utils.parseVisibility(options); + var visibility = visibilityUtils.parser(options); - return visibilityFilter(items, visibility, !!options.hash.visibility); + return visibilityUtils.filter(items, visibility, !!options.hash.visibility); } -foreach = function (items, options) { +module.exports = function foreach(items, options) { if (!options) { logging.warn(i18n.t('warnings.helpers.foreach.iteratorNeeded')); } @@ -53,7 +51,7 @@ foreach = function (items, options) { } if (options.data) { - data = hbs.handlebars.createFrame(options.data); + data = createFrame(options.data); } function execIteration(field, index, last) { @@ -73,9 +71,9 @@ foreach = function (items, options) { } output = output + fn(items[field], { - data: data, - blockParams: hbsUtils.blockParams([items[field], field], [contextPath + field, null]) - }); + data: data, + blockParams: hbsUtils.blockParams([items[field], field], [contextPath + field, null]) + }); } function iterateCollection(context) { @@ -109,5 +107,3 @@ foreach = function (items, options) { return output; }; - -module.exports = foreach; diff --git a/core/server/helpers/get.js b/core/server/helpers/get.js index 283d074650..267582f0f7 100644 --- a/core/server/helpers/get.js +++ b/core/server/helpers/get.js @@ -1,15 +1,17 @@ // # Get Helper // Usage: `{{#get "posts" limit="5"}}`, `{{#get "tags" limit="all"}}` // Fetches data from the API -var _ = require('lodash'), - hbs = require('express-hbs'), +var proxy = require('./proxy'), + _ = require('lodash'), Promise = require('bluebird'), jsonpath = require('jsonpath'), - logging = require('../logging'), - i18n = require('../i18n'), - api = require('../api'), - labs = require('../utils/labs'), + logging = proxy.logging, + i18n = proxy.i18n, + createFrame = proxy.hbs.handlebars.createFrame, + + api = proxy.api, + labs = proxy.labs, resources, pathAliases, get; @@ -96,7 +98,7 @@ get = function get(resource, options) { options.data = options.data || {}; var self = this, - data = hbs.handlebars.createFrame(options.data), + data = createFrame(options.data), apiOptions = options.hash, apiMethod; diff --git a/core/server/helpers/ghost_foot.js b/core/server/helpers/ghost_foot.js index 128ceaef50..618267061f 100644 --- a/core/server/helpers/ghost_foot.js +++ b/core/server/helpers/ghost_foot.js @@ -5,14 +5,13 @@ // // We use the name ghost_foot to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var hbs = require('express-hbs'), - SafeString = hbs.handlebars.SafeString, +var proxy = require('./proxy'), _ = require('lodash'), - filters = require('../filters'), - settingsCache = require('../settings/cache'), - ghost_foot; + SafeString = proxy.SafeString, + filters = proxy.filters, + settingsCache = proxy.settingsCache; -ghost_foot = function ghost_foot() { +module.exports = function ghost_foot() { var foot = [], codeInjection = settingsCache.get('ghost_foot'); @@ -26,5 +25,3 @@ ghost_foot = function ghost_foot() { return new SafeString(foot.join(' ').trim()); }); }; - -module.exports = ghost_foot; diff --git a/core/server/helpers/ghost_head.js b/core/server/helpers/ghost_head.js index 03f5044d94..5b07cf8fa2 100644 --- a/core/server/helpers/ghost_head.js +++ b/core/server/helpers/ghost_head.js @@ -6,19 +6,19 @@ // We use the name ghost_head to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var getMetaData = require('../data/meta'), - hbs = require('express-hbs'), - escapeExpression = hbs.handlebars.Utils.escapeExpression, - SafeString = hbs.handlebars.SafeString, +var proxy = require('./proxy'), _ = require('lodash'), - filters = require('../filters'), - assetHelper = require('./asset'), - config = require('../config'), Promise = require('bluebird'), - labs = require('../utils/labs'), - utils = require('../utils'), - api = require('../api'), - settingsCache = require('../settings/cache'); + + getMetaData = proxy.metaData.get, + escapeExpression = proxy.escapeExpression, + SafeString = proxy.SafeString, + filters = proxy.filters, + labs = proxy.labs, + api = proxy.api, + settingsCache = proxy.settingsCache, + config = proxy.config, + url = proxy.url; function getClient() { if (labs.isSet('publicAPI') === true) { @@ -66,6 +66,10 @@ function finaliseStructuredData(metaData) { } function getAjaxHelper(clientId, clientSecret) { + // @TODO: swap this for a direct utility, rather than using the helper? see #8221 + // Note: this is here because the asset helper isn't registered when this file is first loaded + var assetHelper = proxy.hbs.handlebars.helpers.asset; + return '\n' + ''; } -function ghost_head(options) { +module.exports = function ghost_head(options) { // if server error page do nothing if (this.statusCode >= 500) { return; @@ -97,7 +101,7 @@ function ghost_head(options) { blogIcon = settingsCache.get('icon'), // CASE: blog icon is not set in config, we serve the default iconType = !blogIcon ? 'x-icon' : blogIcon.match(/\/favicon\.ico$/i) ? 'x-icon' : 'png', - favicon = !blogIcon ? '/favicon.ico' : utils.url.urlFor('image', {image: blogIcon}); + favicon = !blogIcon ? '/favicon.ico' : url.urlFor('image', {image: blogIcon}); return Promise.props(fetch).then(function (response) { client = response.client; @@ -162,6 +166,4 @@ function ghost_head(options) { }).then(function (head) { return new SafeString(head.join('\n ').trim()); }); -} - -module.exports = ghost_head; +}; diff --git a/core/server/helpers/has.js b/core/server/helpers/has.js index db9f757a91..3edf5c82d1 100644 --- a/core/server/helpers/has.js +++ b/core/server/helpers/has.js @@ -3,12 +3,12 @@ // // Checks if a post has a particular property -var _ = require('lodash'), - logging = require('../logging'), - i18n = require('../i18n'), - has; +var proxy = require('./proxy'), + _ = require('lodash'), + logging = proxy.logging, + i18n = proxy.i18n; -has = function (options) { +module.exports = function has(options) { options = options || {}; options.hash = options.hash || {}; @@ -33,7 +33,7 @@ has = function (options) { } function evaluateAuthorList(expr, author) { - var authorList = expr.split(',').map(function (v) { + var authorList = expr.split(',').map(function (v) { return v.trim().toLocaleLowerCase(); }); @@ -53,5 +53,3 @@ has = function (options) { } return options.inverse(this); }; - -module.exports = has; diff --git a/core/server/helpers/image.js b/core/server/helpers/image.js index 866ddba098..7d7e32c347 100644 --- a/core/server/helpers/image.js +++ b/core/server/helpers/image.js @@ -4,15 +4,13 @@ // Returns the URL for the current object scope i.e. If inside a post scope will return image permalink // `absolute` flag outputs absolute URL, else URL is relative. -var utils = require('../utils'), - image; +var proxy = require('./proxy'), + url = proxy.url; -image = function (options) { +module.exports = function image(options) { var absolute = options && options.hash.absolute; if (this.image) { - return utils.url.urlFor('image', {image: this.image}, absolute); + return url.urlFor('image', {image: this.image}, absolute); } }; - -module.exports = image; diff --git a/core/server/helpers/index.js b/core/server/helpers/index.js index e49cb37c86..8533484c73 100644 --- a/core/server/helpers/index.js +++ b/core/server/helpers/index.js @@ -1,14 +1,8 @@ -var hbs = require('express-hbs'), - Promise = require('bluebird'), - errors = require('../errors'), - config = require('../config'), - coreHelpers = {}, - registerHelpers; - -// @TODO think about a config option for this e.g. theme.devmode? -if (config.get('env') !== 'production') { - hbs.handlebars.logger.level = 0; -} +var coreHelpers = {}, + register = require('./register'), + registerThemeHelper = register.registerThemeHelper, + registerAsyncThemeHelper = register.registerAsyncThemeHelper, + registerAllCoreHelpers; coreHelpers.asset = require('./asset'); coreHelpers.author = require('./author'); @@ -39,38 +33,7 @@ coreHelpers.title = require('./title'); coreHelpers.twitter_url = require('./twitter_url'); coreHelpers.url = require('./url'); -// Register an async handlebars helper for a given handlebars instance -function registerAsyncHelper(hbs, name, fn) { - hbs.registerAsyncHelper(name, function (context, options, cb) { - // Handle the case where we only get context and cb - if (!cb) { - cb = options; - options = undefined; - } - - // Wrap the function passed in with a when.resolve so it can return either a promise or a value - Promise.resolve(fn.call(this, context, options)).then(function (result) { - cb(result); - }).catch(function (err) { - throw new errors.IncorrectUsageError({ - err: err, - context: 'registerAsyncThemeHelper: ' + name - }); - }); - }); -} - -// Register a handlebars helper for themes -function registerThemeHelper(name, fn) { - hbs.registerHelper(name, fn); -} - -// Register an async handlebars helper for themes -function registerAsyncThemeHelper(name, fn) { - registerAsyncHelper(hbs, name, fn); -} - -registerHelpers = function () { +registerAllCoreHelpers = function registerAllCoreHelpers() { // Register theme helpers registerThemeHelper('asset', coreHelpers.asset); registerThemeHelper('author', coreHelpers.author); @@ -105,6 +68,4 @@ registerHelpers = function () { }; module.exports = coreHelpers; -module.exports.loadCoreHelpers = registerHelpers; -module.exports.registerThemeHelper = registerThemeHelper; -module.exports.registerAsyncThemeHelper = registerAsyncThemeHelper; +module.exports.loadCoreHelpers = registerAllCoreHelpers; diff --git a/core/server/helpers/is.js b/core/server/helpers/is.js index 8484b1eb2a..b411da14ed 100644 --- a/core/server/helpers/is.js +++ b/core/server/helpers/is.js @@ -1,12 +1,12 @@ // # Is Helper // Usage: `{{#is "paged"}}`, `{{#is "index, paged"}}` // Checks whether we're in a given context. -var _ = require('lodash'), - logging = require('../logging'), - i18n = require('../i18n'), - is; +var proxy = require('./proxy'), + _ = require('lodash'), + logging = proxy.logging, + i18n = proxy.i18n; -is = function (context, options) { +module.exports = function is(context, options) { options = options || {}; var currentContext = options.data.root.context; @@ -30,4 +30,3 @@ is = function (context, options) { return options.inverse(this); }; -module.exports = is; diff --git a/core/server/helpers/meta_description.js b/core/server/helpers/meta_description.js index cbcfd26b0f..5999fc47d9 100644 --- a/core/server/helpers/meta_description.js +++ b/core/server/helpers/meta_description.js @@ -6,7 +6,8 @@ // We use the name meta_description to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var getMetaDataDescription = require('../data/meta/description'); +var proxy = require('./proxy'), + getMetaDataDescription = proxy.metaData.getMetaDataDescription; function meta_description(options) { options = options || {}; diff --git a/core/server/helpers/meta_title.js b/core/server/helpers/meta_title.js index 75ec22892d..ae6b6f64d3 100644 --- a/core/server/helpers/meta_title.js +++ b/core/server/helpers/meta_title.js @@ -6,7 +6,8 @@ // We use the name meta_title to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var getMetaDataTitle = require('../data/meta/title'); +var proxy = require('./proxy'), + getMetaDataTitle = proxy.metaData.getMetaDataTitle; function meta_title(options) { return getMetaDataTitle(this, options.data.root); diff --git a/core/server/helpers/navigation.js b/core/server/helpers/navigation.js index 42ab985e30..b17da43927 100644 --- a/core/server/helpers/navigation.js +++ b/core/server/helpers/navigation.js @@ -2,15 +2,14 @@ // `{{navigation}}` // Outputs navigation menu of static urls -var _ = require('lodash'), - hbs = require('express-hbs'), - i18n = require('../i18n'), - errors = require('../errors'), - template = require('./template'), - navigation; +var proxy = require('./proxy'), + _ = require('lodash'), + SafeString = proxy.SafeString, + i18n = proxy.i18n, + errors = proxy.errors, + templates = proxy.templates; -navigation = function (options) { - /*jshint unused:false*/ +module.exports = function navigation(options) { var navigationData = options.data.blog.navigation, currentUrl = options.data.root.relativeUrl, self = this, @@ -58,7 +57,7 @@ navigation = function (options) { // {{navigation}} should no-op if no data passed in if (navigationData.length === 0) { - return new hbs.SafeString(''); + return new SafeString(''); } output = navigationData.map(function (e) { @@ -73,7 +72,6 @@ navigation = function (options) { data = _.merge({}, {navigation: output}); - return template.execute('navigation', data, options); + return templates.execute('navigation', data, options); }; -module.exports = navigation; diff --git a/core/server/helpers/page_url.js b/core/server/helpers/page_url.js index 6d8d19f5c6..0f796ab955 100644 --- a/core/server/helpers/page_url.js +++ b/core/server/helpers/page_url.js @@ -8,15 +8,13 @@ // // We use the name page_url to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var getPaginatedUrl = require('../data/meta/paginated_url'), - page_url; +var proxy = require('./proxy'), + getPaginatedUrl = proxy.metaData.getPaginatedUrl; -page_url = function (page, options) { +module.exports = function page_url(page, options) { if (!options) { options = page; page = 1; } return getPaginatedUrl(page, options.data.root); }; - -module.exports = page_url; diff --git a/core/server/helpers/pagination.js b/core/server/helpers/pagination.js index d7f2e32d06..2cfda4a63c 100644 --- a/core/server/helpers/pagination.js +++ b/core/server/helpers/pagination.js @@ -2,10 +2,11 @@ // `{{pagination}}` // Outputs previous and next buttons, along with info about the current page -var _ = require('lodash'), - errors = require('../errors'), - i18n = require('../i18n'), - template = require('./template'), +var proxy = require('./proxy'), + _ = require('lodash'), + errors = proxy.errors, + i18n = proxy.i18n, + templates = proxy.templates, pagination; pagination = function (options) { @@ -37,7 +38,7 @@ pagination = function (options) { var data = _.merge({}, this.pagination); - return template.execute('pagination', data, options); + return templates.execute('pagination', data, options); }; module.exports = pagination; diff --git a/core/server/helpers/plural.js b/core/server/helpers/plural.js index dfac8ecba7..90132ac480 100644 --- a/core/server/helpers/plural.js +++ b/core/server/helpers/plural.js @@ -8,13 +8,13 @@ // The 3rd argument is the string that will be output if the variable's value is 1 // The 4th argument is the string that will be output if the variable's value is 2+ -var hbs = require('express-hbs'), - errors = require('../errors'), - i18n = require('../i18n'), - _ = require('lodash'), - plural; +var proxy = require('./proxy'), + _ = require('lodash'), + errors = proxy.errors, + i18n = proxy.i18n, + SafeString = proxy.SafeString; -plural = function (number, options) { +module.exports = function plural(number, options) { if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) || _.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) { throw new errors.IncorrectUsageError({ @@ -23,12 +23,11 @@ plural = function (number, options) { } if (number === 0) { - return new hbs.handlebars.SafeString(options.hash.empty.replace('%', number)); + return new SafeString(options.hash.empty.replace('%', number)); } else if (number === 1) { - return new hbs.handlebars.SafeString(options.hash.singular.replace('%', number)); + return new SafeString(options.hash.singular.replace('%', number)); } else if (number >= 2) { - return new hbs.handlebars.SafeString(options.hash.plural.replace('%', number)); + return new SafeString(options.hash.plural.replace('%', number)); } }; -module.exports = plural; diff --git a/core/server/helpers/post_class.js b/core/server/helpers/post_class.js index 3d0566c61e..9eea810e93 100644 --- a/core/server/helpers/post_class.js +++ b/core/server/helpers/post_class.js @@ -6,19 +6,20 @@ // We use the name body_class to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var hbs = require('express-hbs'), - _ = require('lodash'), - post_class; +var proxy = require('./proxy'), + _ = require('lodash'), + SafeString = proxy.SafeString; -post_class = function (options) { - /*jshint unused:false*/ +module.exports = function post_class() { var classes = ['post'], tags = this.post && this.post.tags ? this.post.tags : this.tags || [], featured = this.post && this.post.featured ? this.post.featured : this.featured || false, page = this.post && this.post.page ? this.post.page : this.page || false; if (tags) { - classes = classes.concat(tags.map(function (tag) { return 'tag-' + tag.slug; })); + classes = classes.concat(tags.map(function (tag) { + return 'tag-' + tag.slug; + })); } if (featured) { @@ -29,8 +30,8 @@ post_class = function (options) { classes.push('page'); } - classes = _.reduce(classes, function (memo, item) { return memo + ' ' + item; }, ''); - return new hbs.handlebars.SafeString(classes.trim()); + classes = _.reduce(classes, function (memo, item) { + return memo + ' ' + item; + }, ''); + return new SafeString(classes.trim()); }; - -module.exports = post_class; diff --git a/core/server/helpers/prev_next.js b/core/server/helpers/prev_next.js index db1704904f..0d566f47bc 100644 --- a/core/server/helpers/prev_next.js +++ b/core/server/helpers/prev_next.js @@ -3,12 +3,15 @@ // `{{#prev_post}}next post{{/next_post}}' -var api = require('../api'), - schema = require('../data/schema').checks, - Promise = require('bluebird'), - fetch, prevNext; +var proxy = require('./proxy'), + Promise = require('bluebird'), -fetch = function (apiOptions, options) { + api = proxy.api, + isPost = proxy.checks.isPost, + + fetch; + +fetch = function fetch(apiOptions, options) { return api.posts.read(apiOptions).then(function (result) { var related = result.posts[0]; @@ -25,19 +28,17 @@ fetch = function (apiOptions, options) { // If prevNext method is called without valid post data then we must return a promise, if there is valid post data // then the promise is handled in the api call. -prevNext = function (options) { +module.exports = function prevNext(options) { options = options || {}; var apiOptions = { include: options.name === 'prev_post' ? 'previous,previous.author,previous.tags' : 'next,next.author,next.tags' }; - if (schema.isPost(this) && this.status === 'published') { + if (isPost(this) && this.status === 'published') { apiOptions.slug = this.slug; return fetch(apiOptions, options); } else { return Promise.resolve(options.inverse(this)); } }; - -module.exports = prevNext; diff --git a/core/server/helpers/proxy.js b/core/server/helpers/proxy.js new file mode 100644 index 0000000000..2f783e652e --- /dev/null +++ b/core/server/helpers/proxy.js @@ -0,0 +1,81 @@ +// This file defines everything that helpers "require" +// With the exception of modules like lodash, Bluebird +// We can later refactor to enforce this something like we do in apps +var hbs = require('../themes/engine'), + _ = require('lodash'), + settingsCache = require('../settings/cache'), + config = require('../config'); + +// Direct requires: +// - lodash +// - bluebird +// - downsize +// - moment-timezone +// - jsonpath + +module.exports = { + hbs: hbs, + SafeString: hbs.SafeString, + escapeExpression: hbs.escapeExpression, + + // TODO: Expose less of the API to make this safe + api: require('../api'), + // TODO: Only expose "get" + settingsCache: settingsCache, + + // These 3 are kind of core and required all the time + errors: require('../errors'), + i18n: require('../i18n'), + logging: require('../logging'), + + // This is used to detect if "isPost" is true in prevNext. + checks: require('../data/schema').checks, + + // Config! + // Keys used: + // minifyAssets in asset helper + // isPrivacyDisabled & referrerPolicy used in ghost_head + // Subscribe app uses routeKeywords + config: { + get: config.get.bind(config), + isPrivacyDisabled: config.isPrivacyDisabled.bind(config) + }, + + // Labs utils for enabling/disabling helpers + labs: require('../utils/labs'), + + // System for apps to hook into one day maybe + filters: require('../filters'), + + // Things required from data/meta + metaData: { + get: require('../data/meta'), // ghost_head + getAssetUrl: require('../data/meta/asset_url'), // asset + getMetaDataExcerpt: require('../data/meta/excerpt'), // excerpt + getMetaDataDescription: require('../data/meta/description'), // meta_desc + getMetaDataTitle: require('../data/meta/title'), // meta_title + getPaginatedUrl: require('../data/meta/paginated_url'), // page_url + getMetaDataUrl: require('../data/meta/url') // url + }, + + // The local template thing, should this be merged with the channels one? + templates: require('./template'), + + // Various utils, needs cleaning up / simplifying + socialUrls: require('../utils/social-urls'), + url: require('../utils').url, + utils: { + findKey: function findKey(key /* ...objects... */) { + var objects = Array.prototype.slice.call(arguments, 1); + + return _.reduceRight(objects, function (result, object) { + if (object && _.has(object, key) && !_.isEmpty(object[key])) { + result = object[key]; + } + + return result; + }, null); + } + }, + visibility: require('../utils/visibility') +}; diff --git a/core/server/helpers/register.js b/core/server/helpers/register.js new file mode 100644 index 0000000000..c65befc637 --- /dev/null +++ b/core/server/helpers/register.js @@ -0,0 +1,34 @@ +var hbs = require('../themes/engine'), + Promise = require('bluebird'), + errors = require('../errors'); + +// Register an async handlebars helper for a given handlebars instance +function asyncHelperWrapper(hbs, name, fn) { + hbs.registerAsyncHelper(name, function returnAsync(context, options, cb) { + // Handle the case where we only get context and cb + if (!cb) { + cb = options; + options = undefined; + } + + // Wrap the function passed in with a when.resolve so it can return either a promise or a value + Promise.resolve(fn.call(this, context, options)).then(function (result) { + cb(result); + }).catch(function (err) { + throw new errors.IncorrectUsageError({ + err: err, + context: 'registerAsyncThemeHelper: ' + name + }); + }); + }); +} + +// Register a handlebars helper for themes +module.exports.registerThemeHelper = function registerThemeHelper(name, fn) { + hbs.registerHelper(name, fn); +}; + +// Register an async handlebars helper for themes +module.exports.registerAsyncThemeHelper = function registerAsyncThemeHelper(name, fn) { + asyncHelperWrapper(hbs, name, fn); +}; diff --git a/core/server/helpers/tags.js b/core/server/helpers/tags.js index adc8dc7305..5b0f052b79 100644 --- a/core/server/helpers/tags.js +++ b/core/server/helpers/tags.js @@ -6,14 +6,15 @@ // // Note that the standard {{#each tags}} implementation is unaffected by this helper -var hbs = require('express-hbs'), - _ = require('lodash'), - utils = require('../utils'), - localUtils = require('./utils'), - visibilityFilter = require('../utils/visibility-filter'), - tags; +var proxy = require('./proxy'), + _ = require('lodash'), -tags = function (options) { + SafeString = proxy.SafeString, + templates = proxy.templates, + url = proxy.url, + visibilityUtils = proxy.visibility; + +module.exports = function tags(options) { options = options || {}; options.hash = options.hash || {}; @@ -24,18 +25,18 @@ tags = function (options) { limit = options.hash.limit ? parseInt(options.hash.limit, 10) : undefined, from = options.hash.from ? parseInt(options.hash.from, 10) : 1, to = options.hash.to ? parseInt(options.hash.to, 10) : undefined, - visibility = localUtils.parseVisibility(options), + visibility = visibilityUtils.parser(options), output = ''; function createTagList(tags) { function processTag(tag) { - return autolink ? localUtils.linkTemplate({ - url: utils.url.urlFor('tag', {tag: tag}), + return autolink ? templates.link({ + url: url.urlFor('tag', {tag: tag}), text: _.escape(tag.name) }) : _.escape(tag.name); } - return visibilityFilter(tags, visibility, !!options.hash.visibility, processTag); + return visibilityUtils.filter(tags, visibility, !!options.hash.visibility, processTag); } if (this.tags && this.tags.length) { @@ -49,7 +50,5 @@ tags = function (options) { output = prefix + output + suffix; } - return new hbs.handlebars.SafeString(output); + return new SafeString(output); }; - -module.exports = tags; diff --git a/core/server/helpers/template.js b/core/server/helpers/template.js index 2475618f47..d4b5cc2138 100644 --- a/core/server/helpers/template.js +++ b/core/server/helpers/template.js @@ -1,13 +1,15 @@ -var templates = {}, - hbs = require('express-hbs'), - errors = require('../errors'), - i18n = require('../i18n'); +var templates = {}, + + _ = require('lodash'), + hbs = require('../themes/engine'), + errors = require('../errors'), + i18n = require('../i18n'); // ## Template utils // Execute a template helper // All template helpers are register as partial view. -templates.execute = function (name, context, options) { +templates.execute = function execute(name, context, options) { var partial = hbs.handlebars.partials[name]; if (partial === undefined) { @@ -21,7 +23,12 @@ templates.execute = function (name, context, options) { hbs.registerPartial(partial); } - return new hbs.handlebars.SafeString(partial(context, options)); + return new hbs.SafeString(partial(context, options)); }; +templates.asset = _.template('<%= source %>?v=<%= version %>'); +templates.link = _.template('<%= text %>'); +templates.script = _.template(''); +templates.input = _.template(' />'); + module.exports = templates; diff --git a/core/server/helpers/title.js b/core/server/helpers/title.js index adf42a3cc5..c7df1b6779 100644 --- a/core/server/helpers/title.js +++ b/core/server/helpers/title.js @@ -3,11 +3,10 @@ // // Overrides the standard behaviour of `{[title}}` to ensure the content is correctly escaped -var hbs = require('express-hbs'), - title; +var proxy = require('./proxy'), + SafeString = proxy.SafeString, + escapeExpression = proxy.escapeExpression; -title = function () { - return new hbs.handlebars.SafeString(hbs.handlebars.Utils.escapeExpression(this.title || '')); +module.exports = function title() { + return new SafeString(escapeExpression(this.title || '')); }; - -module.exports = title; diff --git a/core/server/helpers/twitter_url.js b/core/server/helpers/twitter_url.js index 38b381e51a..fe1092ed27 100644 --- a/core/server/helpers/twitter_url.js +++ b/core/server/helpers/twitter_url.js @@ -6,11 +6,11 @@ // We use the name twitter_url to match the helper for consistency: // jscs:disable requireCamelCaseOrUpperCaseIdentifiers -var socialUrls = require('../utils/social-urls'), - findKey = require('./utils').findKey, - twitter_url; +var proxy = require('./proxy'), + socialUrls = proxy.socialUrls, + findKey = proxy.utils.findKey; -twitter_url = function twitter_url(username, options) { +module.exports = function twitter_url(username, options) { if (!options) { options = username; username = findKey('twitter', this, options.data.blog); @@ -22,5 +22,3 @@ twitter_url = function twitter_url(username, options) { return null; }; - -module.exports = twitter_url; diff --git a/core/server/helpers/url.js b/core/server/helpers/url.js index ae9bc754fa..0cb4afe1e4 100644 --- a/core/server/helpers/url.js +++ b/core/server/helpers/url.js @@ -4,16 +4,15 @@ // Returns the URL for the current object scope i.e. If inside a post scope will return post permalink // `absolute` flag outputs absolute URL, else URL is relative -var hbs = require('express-hbs'), - getMetaDataUrl = require('../data/meta/url'); +var proxy = require('./proxy'), + SafeString = proxy.SafeString, + getMetaDataUrl = proxy.metaData.getMetaDataUrl; -function url(options) { +module.exports = function url(options) { var absolute = options && options.hash.absolute, - url = getMetaDataUrl(this, absolute); + outputUrl = getMetaDataUrl(this, absolute); - url = encodeURI(decodeURI(url)); + outputUrl = encodeURI(decodeURI(outputUrl)); - return new hbs.SafeString(url); -} - -module.exports = url; + return new SafeString(outputUrl); +}; diff --git a/core/server/helpers/utils.js b/core/server/helpers/utils.js deleted file mode 100644 index ae8e596e8a..0000000000 --- a/core/server/helpers/utils.js +++ /dev/null @@ -1,30 +0,0 @@ -var _ = require('lodash'), - utils; - -utils = { - assetTemplate: _.template('<%= source %>?v=<%= version %>'), - linkTemplate: _.template('<%= text %>'), - scriptTemplate: _.template(''), - inputTemplate: _.template(' />'), - // @TODO this can probably be made more generic and used in more places - findKey: function findKey(key, object, data) { - if (object && _.has(object, key) && !_.isEmpty(object[key])) { - return object[key]; - } - - if (data && _.has(data, key) && !_.isEmpty(data[key])) { - return data[key]; - } - - return null; - }, - parseVisibility: function parseVisibility(options) { - if (!options.hash.visibility) { - return ['public']; - } - - return _.map(options.hash.visibility.split(','), _.trim); - } -}; - -module.exports = utils; diff --git a/core/server/middleware/error-handler.js b/core/server/middleware/error-handler.js index 906dd7b088..18eb52743d 100644 --- a/core/server/middleware/error-handler.js +++ b/core/server/middleware/error-handler.js @@ -4,9 +4,18 @@ var _ = require('lodash'), errors = require('../errors'), i18n = require('../i18n'), templates = require('../controllers/frontend/templates'), + escapeExpression = hbs.Utils.escapeExpression, _private = {}, errorHandler = {}; +_private.createHbsEngine = function createHbsEngine() { + var engine = hbs.create(); + // @TODO get rid of this after #8126 + engine.registerHelper('asset', require('../helpers/asset')); + + return engine.express4(); +}; + /** * This function splits the stack into pieces, that are then rendered using the following handlebars code: * ``` @@ -111,7 +120,7 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, /*jshint un // It can be that something went wrong with the theme or otherwise loading handlebars // This ensures that no matter what res.render will work here if (_.isEmpty(req.app.engines)) { - req.app.engine('hbs', hbs.express3()); + req.app.engine('hbs', _private.createHbsEngine()); } res.render(templates.error(err.statusCode), templateData, function renderResponse(err, html) { @@ -124,9 +133,9 @@ _private.HTMLErrorRenderer = function HTMLErrorRender(err, req, res, /*jshint un return res.status(500).send( '
' + i18n.t('errors.errors.encounteredError') + '
' + - '' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '' + + '
' + escapeExpression(err.message || err) + '' + '
' + i18n.t('errors.errors.whilstTryingToRender') + '
' + - err.statusCode + ' ' + '' + hbs.handlebars.Utils.escapeExpression(err.message || err) + '' + err.statusCode + ' ' + '
' + escapeExpression(err.message || err) + '' ); }); }; diff --git a/core/server/themes/active.js b/core/server/themes/active.js index ad2e60dac2..f1fa76b89d 100644 --- a/core/server/themes/active.js +++ b/core/server/themes/active.js @@ -20,8 +20,7 @@ var _ = require('lodash'), join = require('path').join, themeConfig = require('./config'), config = require('../config'), - // @TODO: remove this require - hbs = require('express-hbs'), + engine = require('./engine'), // Current instance of ActiveTheme currentActiveTheme; @@ -62,17 +61,13 @@ class ActiveTheme { } get partialsPath() { - return join(this.path, 'partials'); + return this._partials.length > 0 ? join(this.path, 'partials') : null; } get mounted() { return this._mounted; } - hasPartials() { - return this._partials.length > 0; - } - hasTemplate(templateName) { return this._templates.indexOf(templateName) > -1; } @@ -82,17 +77,6 @@ class ActiveTheme { } mount(blogApp) { - let hbsOptions = { - partialsDir: [config.get('paths').helperTemplates], - onCompile: function onCompile(exhbs, source) { - return exhbs.handlebars.compile(source, {preventIndent: true}); - } - }; - - if (this.hasPartials()) { - hbsOptions.partialsDir.push(this.partialsPath); - } - // reset the asset hash // @TODO: set this on the theme instead of globally, or use proper file-based hash config.set('assetHash', null); @@ -100,7 +84,7 @@ class ActiveTheme { blogApp.cache = {}; // Set the views and engine blogApp.set('views', this.path); - blogApp.engine('hbs', hbs.express3(hbsOptions)); + blogApp.engine('hbs', engine.configure(this.partialsPath)); this._mounted = true; } diff --git a/core/server/themes/engine.js b/core/server/themes/engine.js new file mode 100644 index 0000000000..925802845b --- /dev/null +++ b/core/server/themes/engine.js @@ -0,0 +1,27 @@ +var hbs = require('express-hbs'), + config = require('../config'), + instance = hbs.create(); + +// @TODO think about a config option for this e.g. theme.devmode? +if (config.get('env') !== 'production') { + instance.handlebars.logger.level = 0; +} + +instance.escapeExpression = instance.handlebars.Utils.escapeExpression; + +instance.configure = function configure(partialsPath) { + var hbsOptions = { + partialsDir: [config.get('paths').helperTemplates], + onCompile: function onCompile(exhbs, source) { + return exhbs.handlebars.compile(source, {preventIndent: true}); + } + }; + + if (partialsPath) { + hbsOptions.partialsDir.push(partialsPath); + } + + return instance.express4(hbsOptions); +}; + +module.exports = instance; diff --git a/core/server/themes/middleware.js b/core/server/themes/middleware.js index 211c52db46..1aa00057c3 100644 --- a/core/server/themes/middleware.js +++ b/core/server/themes/middleware.js @@ -1,5 +1,5 @@ var _ = require('lodash'), - hbs = require('express-hbs'), + hbs = require('./engine'), utils = require('../utils'), errors = require('../errors'), i18n = require('../i18n'), diff --git a/core/server/utils/labs.js b/core/server/utils/labs.js index e19ff313a0..0ac5542615 100644 --- a/core/server/utils/labs.js +++ b/core/server/utils/labs.js @@ -1,7 +1,7 @@ var settingsCache = require('../settings/cache'), _ = require('lodash'), Promise = require('bluebird'), - hbs = require('express-hbs'), + SafeString = require('../themes/engine').SafeString, errors = require('../errors'), logging = require('../logging'), i18n = require('../i18n'), @@ -32,7 +32,7 @@ labs.enabledHelper = function enabledHelper(options, callback) { logging.error(new errors.GhostError(errDetails)); - errString = new hbs.handlebars.SafeString( + errString = new SafeString( '' ); diff --git a/core/server/utils/visibility-filter.js b/core/server/utils/visibility.js similarity index 73% rename from core/server/utils/visibility-filter.js rename to core/server/utils/visibility.js index cf4e6437b0..a85814ed5d 100644 --- a/core/server/utils/visibility-filter.js +++ b/core/server/utils/visibility.js @@ -7,7 +7,7 @@ var _ = require('lodash'); * @param {Function} [fn] * @returns {Array|Object} filtered items */ -module.exports = function visibilityFilter(items, visibility, explicit, fn) { +module.exports.filter = function visibilityFilter(items, visibility, explicit, fn) { var memo = _.isArray(items) ? [] : {}; if (_.includes(visibility, 'all')) { @@ -27,3 +27,11 @@ module.exports = function visibilityFilter(items, visibility, explicit, fn) { return memo; }, memo); }; + +module.exports.parser = function visibilityParser(options) { + if (!options.hash.visibility) { + return ['public']; + } + + return _.map(options.hash.visibility.split(','), _.trim); +}; diff --git a/core/test/unit/apps_spec.js b/core/test/unit/apps_spec.js index c0ba9277ae..39bef050bb 100644 --- a/core/test/unit/apps_spec.js +++ b/core/test/unit/apps_spec.js @@ -4,7 +4,7 @@ var should = require('should'), EventEmitter = require('events').EventEmitter, _ = require('lodash'), Promise = require('bluebird'), - helpers = require('../../server/helpers'), + helpers = require('../../server/helpers/register'), filters = require('../../server/filters'), i18n = require('../../server/i18n'), @@ -218,7 +218,7 @@ describe('Apps', function () { }); it('allows helper registration with permission', function () { - var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'), + var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'), appProxy = new AppProxy({ name: 'TestApp', permissions: { @@ -234,7 +234,7 @@ describe('Apps', function () { }); it('does not allow helper registration without permission', function () { - var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'), + var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'), appProxy = new AppProxy({ name: 'TestApp', permissions: { @@ -255,7 +255,7 @@ describe('Apps', function () { }); it('does allow INTERNAL app to register helper without permission', function () { - var registerSpy = sandbox.spy(helpers, 'registerThemeHelper'), + var registerSpy = sandbox.stub(helpers, 'registerThemeHelper'), appProxy = new AppProxy({ name: 'TestApp', permissions: {}, diff --git a/core/test/unit/server_helpers/date_spec.js b/core/test/unit/server_helpers/date_spec.js index f967719104..c88b1cf91d 100644 --- a/core/test/unit/server_helpers/date_spec.js +++ b/core/test/unit/server_helpers/date_spec.js @@ -29,7 +29,7 @@ describe('{{date}} helper', function () { var rendered = helpers.date.call({published_at: d}, context); should.exist(rendered); - rendered.should.equal(moment(d).tz(timezones).format(format)); + String(rendered).should.equal(moment(d).tz(timezones).format(format)); }); }); @@ -57,7 +57,7 @@ describe('{{date}} helper', function () { var rendered = helpers.date.call({published_at: d}, context); should.exist(rendered); - rendered.should.equal(moment(d).tz(timezones).from(timeNow)); + String(rendered).should.equal(moment(d).tz(timezones).from(timeNow)); }); }); }); diff --git a/core/test/unit/server_helpers/foreach_spec.js b/core/test/unit/server_helpers/foreach_spec.js index 5e8742d75b..32a43108d0 100644 --- a/core/test/unit/server_helpers/foreach_spec.js +++ b/core/test/unit/server_helpers/foreach_spec.js @@ -3,7 +3,8 @@ var should = require('should'), // jshint ignore:line _ = require('lodash'), // Stuff we are testing - helpers = require('../../../server/helpers'), + helpers = require.main.require('core/server/helpers'), + handlebars = require.main.require('core/server/themes/engine').handlebars, sandbox = sinon.sandbox.create(); @@ -250,8 +251,7 @@ describe('{{#foreach}} helper', function () { }); describe('(compile)', function () { - var handlebars = require('express-hbs').handlebars, - objectHash = { + var objectHash = { posts: { first: {title: 'first'}, second: {title: 'second'}, diff --git a/core/test/unit/server_helpers/ghost_foot_spec.js b/core/test/unit/server_helpers/ghost_foot_spec.js index 53f992c458..ab3a35908f 100644 --- a/core/test/unit/server_helpers/ghost_foot_spec.js +++ b/core/test/unit/server_helpers/ghost_foot_spec.js @@ -3,7 +3,8 @@ var should = require('should'), // jshint ignore:line // Stuff we are testing helpers = require('../../../server/helpers'), - settingsCache = require('../../../server/settings/cache'), + proxy = require('../../../server/helpers/proxy'), + settingsCache = proxy.settingsCache, sandbox = sinon.sandbox.create(); diff --git a/core/test/unit/server_helpers/ghost_head_spec.js b/core/test/unit/server_helpers/ghost_head_spec.js index 7afcf9ddd6..f13a54637c 100644 --- a/core/test/unit/server_helpers/ghost_head_spec.js +++ b/core/test/unit/server_helpers/ghost_head_spec.js @@ -5,9 +5,10 @@ var should = require('should'), // jshint ignore:line moment = require('moment'), configUtils = require('../../utils/configUtils'), helpers = require('../../../server/helpers'), - api = require('../../../server/api'), - labs = require('../../../server/utils/labs'), - settingsCache = require('../../../server/settings/cache'), + proxy = require('../../../server/helpers/proxy'), + settingsCache = proxy.settingsCache, + api = proxy.api, + labs = proxy.labs, sandbox = sinon.sandbox.create(); diff --git a/core/test/unit/server_helpers/navigation_spec.js b/core/test/unit/server_helpers/navigation_spec.js index bee036672d..dc41888c3c 100644 --- a/core/test/unit/server_helpers/navigation_spec.js +++ b/core/test/unit/server_helpers/navigation_spec.js @@ -1,5 +1,5 @@ var should = require('should'), - hbs = require('express-hbs'), + hbs = require('../../../server/themes/engine'), configUtils = require('../../utils/configUtils'), path = require('path'), diff --git a/core/test/unit/server_helpers/pagination_spec.js b/core/test/unit/server_helpers/pagination_spec.js index 53bf1a3c01..7ad7881436 100644 --- a/core/test/unit/server_helpers/pagination_spec.js +++ b/core/test/unit/server_helpers/pagination_spec.js @@ -1,5 +1,5 @@ var should = require('should'), // jshint ignore:line - hbs = require('express-hbs'), + hbs = require('../../../server/themes/engine'), configUtils = require('../../utils/configUtils'), path = require('path'), diff --git a/core/test/unit/server_helpers_index_spec.js b/core/test/unit/server_helpers_index_spec.js index c98d48d710..2fe8d3353f 100644 --- a/core/test/unit/server_helpers_index_spec.js +++ b/core/test/unit/server_helpers_index_spec.js @@ -1,7 +1,7 @@ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers var should = require('should'), // jshint ignore:line _ = require('lodash'), - hbs = require('express-hbs'), + hbs = require.main.require('core/server/themes/engine'), // Stuff we are testing helpers = require.main.require('core/server/helpers'); @@ -22,7 +22,7 @@ describe('Helpers', function () { }); // This will work when we finish refactoring - it.skip('should have exactly the right helpers', function () { + it('should have exactly the right helpers', function () { var foundHelpers, missingHelpers, unexpectedHelpers; foundHelpers = _.keys(hbs.handlebars.helpers); diff --git a/core/test/unit/server_helpers_template_spec.js b/core/test/unit/server_helpers_template_spec.js index 3983c70dc7..69854c78d4 100644 --- a/core/test/unit/server_helpers_template_spec.js +++ b/core/test/unit/server_helpers_template_spec.js @@ -1,8 +1,8 @@ var should = require('should'), - hbs = require('express-hbs'), + hbs = require.main.require('core/server/themes/engine'), // Stuff we are testing - template = require('../../server/helpers/template'); + template = require.main.require('core/server/helpers/template'); describe('Helpers Template', function () { it('can execute a template', function () { diff --git a/core/test/unit/themes/active_spec.js b/core/test/unit/themes/active_spec.js index 3da47ac16d..df995df19a 100644 --- a/core/test/unit/themes/active_spec.js +++ b/core/test/unit/themes/active_spec.js @@ -1,10 +1,10 @@ var should = require('should'), // jshint ignore:line sinon = require('sinon'), - hbs = require('express-hbs'), config = require('../../../server/config'), // is only exposed via themes.getActive() activeTheme = require('../../../server/themes/active'), + engine = require('../../../server/themes/engine'), sandbox = sinon.sandbox.create(); @@ -15,11 +15,11 @@ describe('Themes', function () { describe('Active', function () { describe('Mount', function () { - var hbsStub, configStub, + var engineStub, configStub, fakeBlogApp, fakeLoadedTheme, fakeCheckedTheme; beforeEach(function () { - hbsStub = sandbox.stub(hbs, 'express3'); + engineStub = sandbox.stub(engine, 'configure'); configStub = sandbox.stub(config, 'set'); fakeBlogApp = { @@ -58,11 +58,9 @@ describe('Themes', function () { fakeBlogApp.set.calledOnce.should.be.true(); fakeBlogApp.set.calledWith('views', 'my/fake/theme/path').should.be.true(); - // Check handlebars was initialised correctly - hbsStub.calledOnce.should.be.true(); - hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir'); - hbsStub.firstCall.args[0].partialsDir.should.be.an.Array().with.lengthOf(2); - hbsStub.firstCall.args[0].partialsDir[1].should.eql('my/fake/theme/path/partials'); + // Check handlebars was configured correctly + engineStub.calledOnce.should.be.true(); + engineStub.calledWith('my/fake/theme/path/partials').should.be.true(); // Check the theme is now mounted activeTheme.get().mounted.should.be.true(); @@ -91,10 +89,9 @@ describe('Themes', function () { fakeBlogApp.set.calledOnce.should.be.true(); fakeBlogApp.set.calledWith('views', 'my/fake/theme/path').should.be.true(); - // Check handlebars was initialised correctly - hbsStub.calledOnce.should.be.true(); - hbsStub.firstCall.args[0].should.be.an.Object().and.have.property('partialsDir'); - hbsStub.firstCall.args[0].partialsDir.should.have.lengthOf(1); + // Check handlebars was configured correctly + engineStub.calledOnce.should.be.true(); + engineStub.calledWith().should.be.true(); // Check the theme is now mounted activeTheme.get().mounted.should.be.true(); diff --git a/core/test/unit/themes/middleware_spec.js b/core/test/unit/themes/middleware_spec.js index cdeebdfc18..30a19204b8 100644 --- a/core/test/unit/themes/middleware_spec.js +++ b/core/test/unit/themes/middleware_spec.js @@ -1,6 +1,6 @@ var should = require('should'), // jshint ignore:line sinon = require('sinon'), - hbs = require('express-hbs'), + hbs = require('../../../server/themes/engine'), themes = require('../../../server/themes'), // is only exposed via themes.getActive()