Returned relative paths in html for Content API V2 by default (#10091)

refs #10083

- you can send `?absolute_urls=true` and Ghost will also transform the paths in the content (this is optional/conditional)
This commit is contained in:
Katharina Irrgang 2018-11-05 18:07:45 +01:00 committed by GitHub
parent 94b3735c6e
commit 1b9c61eed1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 112 additions and 24 deletions

View File

@ -39,7 +39,8 @@ module.exports = {
'fields',
'status',
'formats',
'debug'
'debug',
'absolute_urls'
],
data: [
'id',

View File

@ -16,7 +16,8 @@ module.exports = {
'limit',
'order',
'page',
'debug'
'debug',
'absolute_urls'
],
validation: {
options: {
@ -42,7 +43,8 @@ module.exports = {
'fields',
'status',
'formats',
'debug'
'debug',
'absolute_urls'
],
data: [
'id',

View File

@ -1,23 +1,35 @@
const urlService = require('../../../../../../services/url');
const {urlFor, makeAbsoluteUrls} = require('../../../../../../services/url/utils');
const forPost = (id, attrs, options) => {
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
if (attrs.feature_image) {
attrs.feature_image = urlFor('image', {image: attrs.feature_image}, true);
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
}
if (attrs.og_image) {
attrs.og_image = urlFor('image', {image: attrs.og_image}, true);
attrs.og_image = urlService.utils.urlFor('image', {image: attrs.og_image}, true);
}
if (attrs.twitter_image) {
attrs.twitter_image = urlFor('image', {image: attrs.twitter_image}, true);
attrs.twitter_image = urlService.utils.urlFor('image', {image: attrs.twitter_image}, true);
}
if (attrs.html) {
attrs.html = makeAbsoluteUrls(attrs.html, urlFor('home', true), attrs.url).html();
const urlOptions = {
assetsOnly: true
};
if (options.absolute_urls) {
urlOptions.assetsOnly = false;
}
attrs.html = urlService.utils.makeAbsoluteUrls(
attrs.html,
urlService.utils.urlFor('home', true),
attrs.url,
urlOptions
).html();
}
if (options.columns && !options.columns.includes('url')) {
@ -50,11 +62,11 @@ const forUser = (id, attrs) => {
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
if (attrs.profile_image) {
attrs.profile_image = urlFor('image', {image: attrs.profile_image}, true);
attrs.profile_image = urlService.utils.urlFor('image', {image: attrs.profile_image}, true);
}
if (attrs.cover_image) {
attrs.cover_image = urlFor('image', {image: attrs.cover_image}, true);
attrs.cover_image = urlService.utils.urlFor('image', {image: attrs.cover_image}, true);
}
return attrs;
@ -64,7 +76,7 @@ const forTag = (id, attrs) => {
attrs.url = urlService.getUrlByResourceId(id, {absolute: true});
if (attrs.feature_image) {
attrs.feature_image = urlFor('image', {image: attrs.feature_image}, true);
attrs.feature_image = urlService.utils.urlFor('image', {image: attrs.feature_image}, true);
}
return attrs;

View File

@ -124,7 +124,6 @@ function getAmperizeHTML(html, post) {
amperize = amperize || new Amperize();
// make relative URLs abolute
// @TODO: API v2 already makes the urls absolute. Remove if we drop v0.1.
html = urlService.utils.makeAbsoluteUrls(html, urlService.utils.urlFor('home', true), post.url).html();
if (!amperizeCache[post.id] || moment(new Date(amperizeCache[post.id].updated_at)).diff(new Date(post.updated_at)) < 0) {

View File

@ -381,23 +381,20 @@ function redirectToAdmin(status, res, adminPath) {
* absolute urls. Returns an object. The html string can be accessed by calling `html()` on
* the variable that takes the result of this function
*/
function makeAbsoluteUrls(html, siteUrl, itemUrl) {
var htmlContent = cheerio.load(html, {decodeEntities: false});
function makeAbsoluteUrls(html, siteUrl, itemUrl, options = {assetsOnly: false}) {
const htmlContent = cheerio.load(html, {decodeEntities: false});
const staticImageUrlPrefixRegex = new RegExp(STATIC_IMAGE_URL_PREFIX);
// convert relative resource urls to absolute
['href', 'src'].forEach(function forEach(attributeName) {
htmlContent('[' + attributeName + ']').each(function each(ix, el) {
var baseUrl,
attributeValue,
parsed;
el = htmlContent(el);
attributeValue = el.attr(attributeName);
let attributeValue = el.attr(attributeName);
// if URL is absolute move on to the next element
try {
parsed = url.parse(attributeValue);
const parsed = url.parse(attributeValue);
if (parsed.protocol) {
return;
@ -415,11 +412,15 @@ function makeAbsoluteUrls(html, siteUrl, itemUrl) {
if (attributeValue[0] === '#') {
return;
}
// compose an absolute URL
if (options.assetsOnly && !attributeValue.match(staticImageUrlPrefixRegex)) {
return;
}
// compose an absolute URL
// if the relative URL begins with a '/' use the blog URL (including sub-directory)
// as the base URL, otherwise use the post's URL.
baseUrl = attributeValue[0] === '/' ? siteUrl : itemUrl;
const baseUrl = attributeValue[0] === '/' ? siteUrl : itemUrl;
attributeValue = urlJoin(baseUrl, attributeValue);
el.attr(attributeName, attributeValue);
});

View File

@ -89,6 +89,11 @@ describe('Posts', function () {
should.exist(urlParts.protocol);
should.exist(urlParts.host);
res.body.posts[7].slug.should.eql('not-so-short-bit-complex');
res.body.posts[7].html.should.match(/<a href="\/about#nowhere" title="Relative URL/);
res.body.posts[9].slug.should.eql('ghostly-kitchen-sink');
res.body.posts[9].html.should.match(/<img src="http:\/\/127.0.0.1:2369\/content\/images\/lol.jpg"/);
done();
});
});

View File

@ -68,8 +68,19 @@ describe('Unit: v2/utils/serializers/output/posts', function () {
urlService.utils.urlFor.getCall(3).args.should.eql(['home', true]);
urlService.utils.makeAbsoluteUrls.callCount.should.eql(2);
urlService.utils.makeAbsoluteUrls.getCall(0).args.should.eql(['## markdown', 'urlFor', 'getUrlByResourceId']);
urlService.utils.makeAbsoluteUrls.getCall(1).args.should.eql(['<img href=/content/test.jpf', 'urlFor', 'getUrlByResourceId']);
urlService.utils.makeAbsoluteUrls.getCall(0).args.should.eql([
'## markdown',
'urlFor',
'getUrlByResourceId',
{assetsOnly: true}
]);
urlService.utils.makeAbsoluteUrls.getCall(1).args.should.eql([
'<img href=/content/test.jpf',
'urlFor',
'getUrlByResourceId',
{assetsOnly: true}
]);
urlService.getUrlByResourceId.callCount.should.eql(4);
urlService.getUrlByResourceId.getCall(0).args.should.eql(['id1', {absolute: true}]);
@ -77,5 +88,34 @@ describe('Unit: v2/utils/serializers/output/posts', function () {
urlService.getUrlByResourceId.getCall(2).args.should.eql(['id4', {absolute: true}]);
urlService.getUrlByResourceId.getCall(3).args.should.eql(['id2', {absolute: true}]);
});
it('absolute_urls = true', function () {
const apiConfig = {};
const frame = {
options: {
withRelated: ['tags', 'authors'],
absolute_urls: true
}
};
const ctrlResponse = {
data: [
postModel(testUtils.DataGenerator.forKnex.createPost({
id: 'id2',
html: '<img href=/content/test.jpf'
}))
],
meta: {}
};
serializers.output.posts.all(ctrlResponse, apiConfig, frame);
urlService.utils.makeAbsoluteUrls.callCount.should.eql(1);
urlService.utils.makeAbsoluteUrls.getCall(0).args.should.eql([
'<img href=/content/test.jpf',
'urlFor',
'getUrlByResourceId',
{assetsOnly: false}
]);
});
});
});

View File

@ -813,5 +813,33 @@ describe('Url', function () {
result.should.match(/<a href="http:\/\/my-ghost-blog.com\/blog\/about#nowhere" title="Relative URL">/);
});
it('asset urls only', function () {
let html = '<a href="/about" title="Relative URL"><img src="/content/images/1.jpg">';
let result = urlService.utils.makeAbsoluteUrls(html, siteUrl, itemUrl, {assetsOnly: true}).html();
result.should.match(/<img src="http:\/\/my-ghost-blog.com\/content\/images\/1.jpg">/);
result.should.match(/<a href="\/about\" title="Relative URL">/);
html = '<a href="/content/images/09/01/image.jpg">';
result = urlService.utils.makeAbsoluteUrls(html, siteUrl, itemUrl, {assetsOnly: true}).html();
result.should.match(/<a href="http:\/\/my-ghost-blog.com\/content\/images\/09\/01\/image.jpg">/);
html = '<a href="/blog/content/images/09/01/image.jpg">';
result = urlService.utils.makeAbsoluteUrls(html, siteUrl, itemUrl, {assetsOnly: true}).html();
result.should.match(/<a href="http:\/\/my-ghost-blog.com\/blog\/content\/images\/09\/01\/image.jpg">/);
html = '<img src="http://my-ghost-blog.de/content/images/09/01/image.jpg">';
result = urlService.utils.makeAbsoluteUrls(html, siteUrl, itemUrl, {assetsOnly: true}).html();
result.should.match(/<img src="http:\/\/my-ghost-blog.de\/content\/images\/09\/01\/image.jpg">/);
html = '<img src="http://external.com/image.jpg">';
result = urlService.utils.makeAbsoluteUrls(html, siteUrl, itemUrl, {assetsOnly: true}).html();
result.should.match(/<img src="http:\/\/external.com\/image.jpg">/);
});
});
});