diff --git a/core/server/helpers/ghost_head.js b/core/server/helpers/ghost_head.js index 43b59914b8a7..8bfe1ba8f37c 100644 --- a/core/server/helpers/ghost_head.js +++ b/core/server/helpers/ghost_head.js @@ -21,21 +21,47 @@ var hbs = require('express-hbs'), excerpt = require('./excerpt'), tagsHelper = require('./tags'), imageHelper = require('./image'), + + labs = require('../utils/labs'), + blog, ghost_head; -function getImage(ops, context, contextObject) { +function getClient() { + return labs.isSet('publicAPI').then(function (publicAPI) { + if (publicAPI === true) { + return api.clients.read({slug: 'ghost-frontend'}).then(function (client) { + client = client.clients[0]; + if (client.status === 'enabled') { + return { + id: client.slug, + secret: client.secret + }; + } + + return {}; + }); + } + + return {}; + }); +} + +function writeMetaTag(property, content, type) { + type = type || property.substring(0, 7) === 'twitter' ? 'name' : 'property'; + return ''; +} + +function getImage(props, context, contextObject) { if (context === 'home' || context === 'author') { contextObject.image = contextObject.cover; } - ops.push(imageHelper.call(contextObject, {hash: {absolute: true}})); + props.image = imageHelper.call(contextObject, {hash: {absolute: true}}); if (context === 'post' && contextObject.author) { - ops.push(imageHelper.call(contextObject.author, {hash: {absolute: true}})); + props.author_image = imageHelper.call(contextObject.author, {hash: {absolute: true}}); } - - return ops; } function getPaginationUrls(pagination, relativeUrl, secure, head) { @@ -84,11 +110,11 @@ function addContextMetaData(context, data, metaData) { function initMetaData(context, data, results) { var metaData = { - url: results[0].value(), - metaDescription: results[1].value() || null, - metaTitle: results[2].value(), - coverImage: results.length > 3 ? results[3].value() : null, - authorImage: results.length > 4 ? results[4].value() : null, + url: results.url, + metaDescription: results.meta_description || null, + metaTitle: results.meta_title, + coverImage: results.image, + authorImage: results.author_image, publishedDate: null, modifiedDate: null, tags: null, @@ -97,7 +123,9 @@ function initMetaData(context, data, results) { ogType: 'website', keywords: null, blog: blog, - title: blog.title + title: blog.title, + clientId: results.client.id, + clientSecret: results.client.secret }; if (!metaData.metaDescription) { @@ -228,19 +256,17 @@ function chooseSchema(metaData, context, data) { } function finaliseStructuredData(structuredData, tags, head) { - var type; _.each(structuredData, function (content, property) { if (property === 'article:tag') { _.each(tags, function (tag) { if (tag !== '') { tag = hbs.handlebars.Utils.escapeExpression(tag.trim()); - head.push(''); + head.push(writeMetaTag(property, tag)); } }); head.push(''); } else if (content !== null && content !== undefined) { - type = property.substring(0, 7) === 'twitter' ? 'name' : 'property'; - head.push(''); + head.push(writeMetaTag(property, content)); } }); return head; @@ -262,21 +288,22 @@ ghost_head = function (options) { useStructuredData = !config.isPrivacyDisabled('useStructuredData'), head = [], safeVersion = this.safeVersion, - ops = [], + props = {}, structuredData, schema, title = hbs.handlebars.Utils.escapeExpression(blog.title), context = self.context ? self.context[0] : null, contextObject = self[context] || blog; - // Push Async calls to an array of promises - ops.push(urlHelper.call(self, {hash: {absolute: true}})); - ops.push(meta_description.call(self, options)); - ops.push(meta_title.call(self, options)); - ops = getImage(ops, context, contextObject); + // Store Async calls in an object of named promises + props.url = urlHelper.call(self, {hash: {absolute: true}}); + props.meta_description = meta_description.call(self, options); + props.meta_title = meta_title.call(self, options); + props.client = getClient(); + getImage(props, context, contextObject); // Resolves promises then push pushes meta data into ghost_head - return Promise.settle(ops).then(function (results) { + return Promise.props(props).then(function (results) { if (context) { var metaData = initMetaData(context, self, results), tags = tagsHelper.call(self.post, {hash: {autolink: 'false'}}).string.split(','); @@ -310,7 +337,13 @@ ghost_head = function (options) { // Formats schema script/JSONLD data and pushes to head array finaliseSchema(schema, head); } + + if (metaData.clientId && metaData.clientSecret) { + head.push(writeMetaTag('ghost:client_id', metaData.clientId)); + head.push(writeMetaTag('ghost:client_secret', metaData.clientSecret)); + } } + head.push(''); head.push(''); diff --git a/core/server/utils/labs.js b/core/server/utils/labs.js index 3a0853b6d88f..79b30f37a49f 100644 --- a/core/server/utils/labs.js +++ b/core/server/utils/labs.js @@ -11,7 +11,15 @@ flagIsSet = function flagIsSet(flag) { return setting.key === 'labs'; }); - labsValue = JSON.parse(labs.value); + if (!labs || !labs.value) { + return false; + } + + try { + labsValue = JSON.parse(labs.value); + } catch (e) { + return false; + } return !!labsValue[flag] && labsValue[flag] === true; }); diff --git a/core/test/unit/server_helpers/ghost_head_spec.js b/core/test/unit/server_helpers/ghost_head_spec.js index 81fc09fd1b1e..8c04e9414105 100644 --- a/core/test/unit/server_helpers/ghost_head_spec.js +++ b/core/test/unit/server_helpers/ghost_head_spec.js @@ -10,14 +10,47 @@ var should = require('should'), // Stuff we are testing handlebars = hbs.handlebars, helpers = require('../../../server/helpers'), - api = require('../../../server/api'); + api = require('../../../server/api'), + + labs = require('../../../server/utils/labs'), + + sandbox = sinon.sandbox.create(); describe('{{ghost_head}} helper', function () { - var sandbox; + var settingsReadStub; + before(function () { utils.loadHelpers(); }); + afterEach(function () { + sandbox.restore(); + }); + after(function () { + utils.restoreConfig(); + }); + + beforeEach(function () { + settingsReadStub = sandbox.stub(api.settings, 'read').returns(new Promise.resolve({ + settings: [ + {value: ''} + ] + })); + + sandbox.stub(api.clients, 'read').returns(new Promise.resolve({ + clients: [ + {slug: 'ghost-frontend', secret: 'a1bcde23cfe5', status: 'enabled'} + ] + })); + + sandbox.stub(labs, 'isSet').returns(new Promise.resolve(true)); + }); + + function expectGhostClientMeta(rendered) { + rendered.string.should.match(//); + rendered.string.should.match(//); + } + describe('without Code Injection', function () { before(function () { utils.overrideConfig({ @@ -30,25 +63,6 @@ describe('{{ghost_head}} helper', function () { }); }); - after(function () { - utils.restoreConfig(); - }); - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - sandbox.stub(api.settings, 'read', function () { - return Promise.resolve({ - settings: [ - {value: ''} - ] - }); - }); - }); - - afterEach(function () { - sandbox.restore(); - }); - it('has loaded ghost_head helper', function () { should.exist(handlebars.helpers.ghost_head); }); @@ -59,6 +73,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['paged', 'index']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -75,6 +90,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['home', 'index']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -115,6 +131,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['tag']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -156,6 +173,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['tag']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -196,6 +214,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['tag']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.not.match(//); rendered.string.should.not.match(//); rendered.string.should.not.match(/"description":/); @@ -217,6 +236,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['tag']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -242,6 +262,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['author']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -284,6 +305,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['paged', 'author']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -335,7 +357,7 @@ describe('{{ghost_head}} helper', function () { re4 = new RegExp('"dateModified": "' + post.updated_at); should.exist(rendered); - + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -407,7 +429,7 @@ describe('{{ghost_head}} helper', function () { re4 = new RegExp('"dateModified": "' + post.updated_at); should.exist(rendered); - + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -477,7 +499,7 @@ describe('{{ghost_head}} helper', function () { re4 = new RegExp('"dateModified": "' + post.updated_at); should.exist(rendered); - + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -546,7 +568,7 @@ describe('{{ghost_head}} helper', function () { re4 = new RegExp('"dateModified": "' + post.updated_at); should.exist(rendered); - + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -593,6 +615,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['page']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -609,6 +632,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['index', 'paged'], pagination: {total: 4, page: 3, next: 4, prev: 2}}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -627,6 +651,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['index', 'paged'], pagination: {total: 3, page: 2, next: 3, prev: 1}}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -661,6 +686,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: []}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -684,19 +710,6 @@ describe('{{ghost_head}} helper', function () { useStructuredData: false } }); - sandbox = sinon.sandbox.create(); - sandbox.stub(api.settings, 'read', function () { - return Promise.resolve({ - settings: [ - {value: ''} - ] - }); - }); - }); - - after(function () { - utils.restoreConfig(); - sandbox.restore(); }); it('does not return structured data', function (done) { @@ -721,6 +734,7 @@ describe('{{ghost_head}} helper', function () { {data: {root: {context: ['post']}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//); @@ -733,13 +747,10 @@ describe('{{ghost_head}} helper', function () { }); describe('with Code Injection', function () { - before(function () { - sandbox = sinon.sandbox.create(); - sandbox.stub(api.settings, 'read', function () { - return Promise.resolve({ - settings: [{value: ''}] - }); - }); + beforeEach(function () { + settingsReadStub.returns(new Promise.resolve({ + settings: [{value: ''}] + })); utils.overrideConfig({ url: 'http://testurl.com/', @@ -751,17 +762,13 @@ describe('{{ghost_head}} helper', function () { }); }); - after(function () { - sandbox.restore(); - utils.restoreConfig(); - }); - it('returns meta tag plus injected code', function (done) { helpers.ghost_head.call( {safeVersion: '0.3', context: ['paged', 'index'], post: false}, {data: {root: {context: []}}} ).then(function (rendered) { should.exist(rendered); + expectGhostClientMeta(rendered); rendered.string.should.match(//); rendered.string.should.match(//); rendered.string.should.match(//);