0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Add meta tags for client_id & client_secret

refs #5942

- refactor ghost_head to use Promise.props (settle is going away and this is easier)
- add a new call to fetch the frontend client, if it exists
- add meta tags for the client_id and client_secret on all pages
- don't include the meta tags if the client is not enabled, or if the labs flag is not set
This commit is contained in:
Hannah Wolfe 2015-11-04 14:20:05 +00:00
parent eb3cce0235
commit e70898a842
3 changed files with 121 additions and 73 deletions

View file

@ -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 '<meta ' + type + '="' + property + '" content="' + content + '" />';
}
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('<meta property="' + property + '" content="' + tag + '" />');
head.push(writeMetaTag(property, tag));
}
});
head.push('');
} else if (content !== null && content !== undefined) {
type = property.substring(0, 7) === 'twitter' ? 'name' : 'property';
head.push('<meta ' + type + '="' + property + '" content="' + content + '" />');
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('<meta name="generator" content="Ghost ' + safeVersion + '" />');
head.push('<link rel="alternate" type="application/rss+xml" title="' +
title + '" href="' + config.urlFor('rss', null, true) + '" />');

View file

@ -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;
});

View file

@ -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(/<meta property="ghost:client_id" content="ghost-frontend" \/>/);
rendered.string.should.match(/<meta property="ghost:client_secret" content="[a-f0-9]{12}" \/>/);
}
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(/<link rel="canonical" href="http:\/\/testurl.com\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/" \/>/);
rendered.string.should.match(/<meta name="referrer" content="origin" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="website" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="website" \/>/);
@ -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(/<meta property="og:description" \/>/);
rendered.string.should.not.match(/<meta name="twitter:description"\/>/);
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(/<link rel="canonical" href="http:\/\/testurl.com\/tag\/tagtitle\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/author\/AuthorName\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="profile" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/author\/AuthorName\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="article" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="article" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="article" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<meta property="og:site_name" content="Ghost" \/>/);
rendered.string.should.match(/<meta property="og:type" content="article" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/about\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/page\/3\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="prev" href="http:\/\/testurl.com\/page\/2\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/page\/2\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="prev" href="http:\/\/testurl.com\/" \/>/);
@ -661,6 +686,7 @@ describe('{{ghost_head}} helper', function () {
{data: {root: {context: []}}}
).then(function (rendered) {
should.exist(rendered);
expectGhostClientMeta(rendered);
rendered.string.should.match(/<link rel="canonical" href="http:\/\/testurl.com\/blog\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/blog\/rss\/" \/>/);
@ -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(/<link rel="canonical" href="http:\/\/testurl.com\/post\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);
@ -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: '<style>body {background: red;}</style>'}]
});
});
beforeEach(function () {
settingsReadStub.returns(new Promise.resolve({
settings: [{value: '<style>body {background: red;}</style>'}]
}));
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(/<link rel="canonical" href="http:\/\/testurl.com\/" \/>/);
rendered.string.should.match(/<meta name="generator" content="Ghost 0.3" \/>/);
rendered.string.should.match(/<link rel="alternate" type="application\/rss\+xml" title="Ghost" href="http:\/\/testurl.com\/rss\/" \/>/);