diff --git a/core/server/api/canary/utils/serializers/input/utils/url.js b/core/server/api/canary/utils/serializers/input/utils/url.js
index 7968b28001..bec5eeb5d3 100644
--- a/core/server/api/canary/utils/serializers/input/utils/url.js
+++ b/core/server/api/canary/utils/serializers/input/utils/url.js
@@ -1,23 +1,5 @@
-const _ = require('lodash');
-const url = require('url');
const urlUtils = require('../../../../../../lib/url-utils');
-const handleCanonicalUrl = (canonicalUrl) => {
- const blogURl = urlUtils.getSiteUrl();
- const isSameProtocol = url.parse(canonicalUrl).protocol === url.parse(blogURl).protocol;
- const blogDomain = blogURl.replace(/^http(s?):\/\//, '').replace(/\/$/, '');
- const absolute = canonicalUrl.replace(/^http(s?):\/\//, '');
-
- // We only want to transform to a relative URL when the canonical URL matches the current
- // Blog URL incl. the same protocol. This allows users to keep e.g. Facebook comments after
- // a http -> https switch
- if (absolute.startsWith(blogDomain) && isSameProtocol) {
- return urlUtils.absoluteToRelative(canonicalUrl);
- }
-
- return canonicalUrl;
-};
-
const handleImageUrl = (imageUrl) => {
const blogDomain = urlUtils.getSiteUrl().replace(/^http(s?):\/\//, '').replace(/\/$/, '');
const imageUrlAbsolute = imageUrl.replace(/^http(s?):\/\//, '');
@@ -30,44 +12,7 @@ const handleImageUrl = (imageUrl) => {
return imageUrl;
};
-const handleContentUrls = (content) => {
- const blogDomain = urlUtils.getSiteUrl().replace(/^http(s?):\/\//, '').replace(/\/$/, '');
- const imagePathRe = new RegExp(`(http(s?)://)?${blogDomain}/${urlUtils.STATIC_IMAGE_URL_PREFIX}`, 'g');
-
- const matches = _.uniq(content.match(imagePathRe));
-
- if (matches) {
- matches.forEach((match) => {
- const relative = urlUtils.absoluteToRelative(match);
- content = content.replace(new RegExp(match, 'g'), relative);
- });
- }
-
- return content;
-};
-
const forPost = (attrs, options) => {
- // make all content image URLs relative, ref: https://github.com/TryGhost/Ghost/issues/10477
- if (attrs.mobiledoc) {
- attrs.mobiledoc = handleContentUrls(attrs.mobiledoc);
- }
-
- if (attrs.feature_image) {
- attrs.feature_image = handleImageUrl(attrs.feature_image);
- }
-
- if (attrs.og_image) {
- attrs.og_image = handleImageUrl(attrs.og_image);
- }
-
- if (attrs.twitter_image) {
- attrs.twitter_image = handleImageUrl(attrs.twitter_image);
- }
-
- if (attrs.canonical_url) {
- attrs.canonical_url = handleCanonicalUrl(attrs.canonical_url);
- }
-
if (options && options.withRelated) {
options.withRelated.forEach((relation) => {
if (relation === 'tags' && attrs.tags) {
diff --git a/core/server/api/canary/utils/serializers/output/utils/url.js b/core/server/api/canary/utils/serializers/output/utils/url.js
index 892585c63c..5e66a7bacb 100644
--- a/core/server/api/canary/utils/serializers/output/utils/url.js
+++ b/core/server/api/canary/utils/serializers/output/utils/url.js
@@ -30,38 +30,28 @@ const forPost = (id, attrs, frame) => {
}
}
- if (attrs.feature_image) {
- attrs.feature_image = urlUtils.urlFor('image', {image: attrs.feature_image}, true);
- }
-
- if (attrs.og_image) {
- attrs.og_image = urlUtils.urlFor('image', {image: attrs.og_image}, true);
- }
-
- if (attrs.twitter_image) {
- attrs.twitter_image = urlUtils.urlFor('image', {image: attrs.twitter_image}, true);
- }
-
- if (attrs.canonical_url) {
- attrs.canonical_url = urlUtils.relativeToAbsolute(attrs.canonical_url);
- }
-
- if (attrs.html) {
- const urlOptions = {
- assetsOnly: true
- };
-
- if (frame.options.absolute_urls) {
- urlOptions.assetsOnly = false;
- }
-
- attrs.html = urlUtils.htmlRelativeToAbsolute(
- attrs.html,
- attrs.url,
- urlOptions
+ if (attrs.mobiledoc) {
+ attrs.mobiledoc = urlUtils.mobiledocRelativeToAbsolute(
+ attrs.mobiledoc,
+ attrs.url
);
}
+ ['html', 'codeinjection_head', 'codeinjection_foot'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.htmlRelativeToAbsolute(
+ attrs[attr],
+ attrs.url
+ );
+ }
+ });
+
+ ['feature_image', 'og_image', 'twitter_image', 'canonical_url'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.relativeToAbsolute(attrs[attr]);
+ }
+ });
+
if (frame.options.columns && !frame.options.columns.includes('url')) {
delete attrs.url;
}
diff --git a/core/server/api/v0.1/decorators/urls.js b/core/server/api/v0.1/decorators/urls.js
index aeb4e9f0cc..f288eda078 100644
--- a/core/server/api/v0.1/decorators/urls.js
+++ b/core/server/api/v0.1/decorators/urls.js
@@ -9,25 +9,27 @@ const urlsForPost = (id, attrs, options) => {
}
if (options && options.context && options.context.public && options.absolute_urls) {
- if (attrs.feature_image) {
- attrs.feature_image = urlUtils.urlFor('image', {image: attrs.feature_image}, true);
+ if (attrs.mobiledoc) {
+ attrs.mobiledoc = urlUtils.mobiledocRelativeToAbsolute(
+ attrs.mobiledoc,
+ attrs.url
+ );
}
- if (attrs.og_image) {
- attrs.og_image = urlUtils.urlFor('image', {image: attrs.og_image}, true);
- }
+ ['html', 'codeinjection_head', 'codeinjection_foot'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.htmlRelativeToAbsolute(
+ attrs[attr],
+ attrs.url
+ );
+ }
+ });
- if (attrs.twitter_image) {
- attrs.twitter_image = urlUtils.urlFor('image', {image: attrs.twitter_image}, true);
- }
-
- if (attrs.html) {
- attrs.html = urlUtils.htmlRelativeToAbsolute(attrs.html, attrs.url);
- }
-
- if (attrs.url) {
- attrs.url = urlUtils.urlFor({relativeUrl: attrs.url}, true);
- }
+ ['feature_image', 'og_image', 'twitter_image', 'canonical_url', 'url'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.relativeToAbsolute(attrs[attr]);
+ }
+ });
}
if (options && options.withRelated) {
diff --git a/core/server/api/v2/utils/serializers/input/utils/url.js b/core/server/api/v2/utils/serializers/input/utils/url.js
index 1bba9a6fff..24272e8ee1 100644
--- a/core/server/api/v2/utils/serializers/input/utils/url.js
+++ b/core/server/api/v2/utils/serializers/input/utils/url.js
@@ -1,23 +1,5 @@
-const _ = require('lodash');
-const url = require('url');
const urlUtils = require('../../../../../../lib/url-utils');
-const handleCanonicalUrl = (canonicalUrl) => {
- const siteUrl = urlUtils.getSiteUrl();
- const isSameProtocol = url.parse(canonicalUrl).protocol === url.parse(siteUrl).protocol;
- const siteDomain = siteUrl.replace(/^http(s?):\/\//, '').replace(/\/$/, '');
- const absolute = canonicalUrl.replace(/^http(s?):\/\//, '');
-
- // We only want to transform to a relative URL when the canonical URL matches the current
- // Site URL incl. the same protocol. This allows users to keep e.g. Facebook comments after
- // a http -> https switch
- if (absolute.startsWith(siteDomain) && isSameProtocol) {
- return urlUtils.absoluteToRelative(canonicalUrl);
- }
-
- return canonicalUrl;
-};
-
const handleImageUrl = (imageUrl) => {
const siteDomain = urlUtils.getSiteUrl().replace(/^http(s?):\/\//, '').replace(/\/$/, '');
const imageUrlAbsolute = imageUrl.replace(/^http(s?):\/\//, '');
@@ -30,44 +12,7 @@ const handleImageUrl = (imageUrl) => {
return imageUrl;
};
-const handleContentUrls = (content) => {
- const siteDomain = urlUtils.getSiteUrl().replace(/^http(s?):\/\//, '').replace(/\/$/, '');
- const imagePathRe = new RegExp(`(http(s?)://)?${siteDomain}/${urlUtils.STATIC_IMAGE_URL_PREFIX}`, 'g');
-
- const matches = _.uniq(content.match(imagePathRe));
-
- if (matches) {
- matches.forEach((match) => {
- const relative = urlUtils.absoluteToRelative(match);
- content = content.replace(new RegExp(match, 'g'), relative);
- });
- }
-
- return content;
-};
-
const forPost = (attrs, options) => {
- // make all content image URLs relative, ref: https://github.com/TryGhost/Ghost/issues/10477
- if (attrs.mobiledoc) {
- attrs.mobiledoc = handleContentUrls(attrs.mobiledoc);
- }
-
- if (attrs.feature_image) {
- attrs.feature_image = handleImageUrl(attrs.feature_image);
- }
-
- if (attrs.og_image) {
- attrs.og_image = handleImageUrl(attrs.og_image);
- }
-
- if (attrs.twitter_image) {
- attrs.twitter_image = handleImageUrl(attrs.twitter_image);
- }
-
- if (attrs.canonical_url) {
- attrs.canonical_url = handleCanonicalUrl(attrs.canonical_url);
- }
-
if (options && options.withRelated) {
options.withRelated.forEach((relation) => {
if (relation === 'tags' && attrs.tags) {
diff --git a/core/server/api/v2/utils/serializers/output/utils/url.js b/core/server/api/v2/utils/serializers/output/utils/url.js
index b688bee3a4..793a20879b 100644
--- a/core/server/api/v2/utils/serializers/output/utils/url.js
+++ b/core/server/api/v2/utils/serializers/output/utils/url.js
@@ -30,38 +30,38 @@ const forPost = (id, attrs, frame) => {
}
}
- if (attrs.feature_image) {
- attrs.feature_image = urlUtils.urlFor('image', {image: attrs.feature_image}, true);
+ const urlOptions = {};
+
+ // v2 only transforms asset URLS, v3 will transform all urls so that
+ // input/output transformations are balanced and all URLs are absolute
+ if (!frame.options.absolute_urls) {
+ urlOptions.assetsOnly = true;
}
- if (attrs.og_image) {
- attrs.og_image = urlUtils.urlFor('image', {image: attrs.og_image}, true);
- }
-
- if (attrs.twitter_image) {
- attrs.twitter_image = urlUtils.urlFor('image', {image: attrs.twitter_image}, true);
- }
-
- if (attrs.canonical_url) {
- attrs.canonical_url = urlUtils.relativeToAbsolute(attrs.canonical_url);
- }
-
- if (attrs.html) {
- const urlOptions = {
- assetsOnly: true
- };
-
- if (frame.options.absolute_urls) {
- urlOptions.assetsOnly = false;
- }
-
- attrs.html = urlUtils.htmlRelativeToAbsolute(
- attrs.html,
+ if (attrs.mobiledoc) {
+ attrs.mobiledoc = urlUtils.mobiledocRelativeToAbsolute(
+ attrs.mobiledoc,
attrs.url,
urlOptions
);
}
+ ['html', 'codeinjection_head', 'codeinjection_foot'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.htmlRelativeToAbsolute(
+ attrs[attr],
+ attrs.url,
+ urlOptions
+ );
+ }
+ });
+
+ ['feature_image', 'og_image', 'twitter_image', 'canonical_url'].forEach((attr) => {
+ if (attrs[attr]) {
+ attrs[attr] = urlUtils.relativeToAbsolute(attrs[attr], attrs.url, urlOptions);
+ }
+ });
+
if (frame.options.columns && !frame.options.columns.includes('url')) {
delete attrs.url;
}
diff --git a/core/server/models/post.js b/core/server/models/post.js
index 59b99b692f..3f46723aa0 100644
--- a/core/server/models/post.js
+++ b/core/server/models/post.js
@@ -11,6 +11,7 @@ const config = require('../config');
const settingsCache = require('../services/settings/cache');
const converters = require('../lib/mobiledoc/converters');
const relations = require('./relations');
+const urlUtils = require('../lib/url-utils');
const MOBILEDOC_REVISIONS_COUNT = 10;
const ALL_STATUSES = ['published', 'draft', 'scheduled'];
@@ -349,6 +350,39 @@ Post = ghostBookshelf.Model.extend({
this.set('mobiledoc', JSON.stringify(converters.mobiledocConverter.blankStructure()));
}
+ // ensure all URLs are stored as relative
+ // note: html is not necessary to change because it's a generated later from mobiledoc
+ const urlTransformMap = {
+ mobiledoc: 'mobiledocAbsoluteToRelative',
+ custom_excerpt: 'htmlAbsoluteToRelative',
+ codeinjection_head: 'htmlAbsoluteToRelative',
+ codeinjection_foot: 'htmlAbsoluteToRelative',
+ feature_image: 'absoluteToRelative',
+ og_image: 'absoluteToRelative',
+ twitter_image: 'absoluteToRelative',
+ canonical_url: {
+ method: 'absoluteToRelative',
+ options: {
+ ignoreProtocol: false
+ }
+ }
+ };
+
+ Object.entries(urlTransformMap).forEach(([attr, transform]) => {
+ let method = transform;
+ let options = {};
+
+ if (typeof transform === 'object') {
+ method = transform.method;
+ options = transform.options || {};
+ }
+
+ if (this.hasChanged(attr) && this.get(attr)) {
+ const transformedValue = urlUtils[method](this.get(attr), options);
+ this.set(attr, transformedValue);
+ }
+ });
+
// CASE: mobiledoc has changed, generate html
// CASE: html is null, but mobiledoc exists (only important for migrations & importing)
if (this.hasChanged('mobiledoc') || (!this.get('html') && (options.migrating || options.importing))) {
diff --git a/core/test/regression/models/model_posts_spec.js b/core/test/regression/models/model_posts_spec.js
index 3b1b17c6c7..3f01f06ca0 100644
--- a/core/test/regression/models/model_posts_spec.js
+++ b/core/test/regression/models/model_posts_spec.js
@@ -1134,6 +1134,44 @@ describe('Post Model', function () {
done();
}).catch(done);
});
+
+ it('transforms absolute urls to relative', function (done) {
+ const post = {
+ title: 'Absolute->Relative URL Transform Test',
+ mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"http://127.0.0.1:2369/content/images/card.jpg"}]],"markups":[["a",["href","http://127.0.0.1:2369/test"]]],"sections":[[1,"p",[[0,[0],1,"Testing"]]],[10,0]]}',
+ custom_excerpt: 'Testing links in custom excerpts',
+ codeinjection_head: '',
+ codeinjection_foot: '',
+ feature_image: 'http://127.0.0.1:2369/content/images/feature.png',
+ og_image: 'http://127.0.0.1:2369/content/images/og.png',
+ twitter_image: 'http://127.0.0.1:2369/content/images/twitter.png',
+ canonical_url: 'http://127.0.0.1:2369/canonical'
+ };
+
+ models.Post.add(post, context).then((createdPost) => {
+ createdPost.get('mobiledoc').should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"/content/images/card.jpg"}]],"markups":[["a",["href","/test"]]],"sections":[[1,"p",[[0,[0],1,"Testing"]]],[10,0]]}');
+ createdPost.get('html').should.equal('
Testing
');
+ createdPost.get('custom_excerpt').should.equal('Testing links in custom excerpts');
+ createdPost.get('codeinjection_head').should.equal('');
+ createdPost.get('codeinjection_foot').should.equal('');
+ createdPost.get('feature_image').should.equal('/content/images/feature.png');
+ createdPost.get('og_image').should.equal('/content/images/og.png');
+ createdPost.get('twitter_image').should.equal('/content/images/twitter.png');
+ createdPost.get('canonical_url').should.equal('/canonical');
+
+ // ensure canonical_url is not transformed when protocol does not match
+ return createdPost.save({
+ canonical_url: 'https://127.0.0.1:2369/https-internal',
+ // sanity check for general absolute->relative transform during edits
+ feature_image: 'http://127.0.0.1:2369/content/images/updated_feature.png'
+ });
+ }).then((updatedPost) => {
+ updatedPost.get('canonical_url').should.equal('https://127.0.0.1:2369/https-internal');
+ updatedPost.get('feature_image').should.equal('/content/images/updated_feature.png');
+
+ done();
+ }).catch(done);
+ });
});
describe('destroy', function () {
diff --git a/core/test/unit/api/canary/utils/serializers/input/posts_spec.js b/core/test/unit/api/canary/utils/serializers/input/posts_spec.js
index 0e93a70446..8943e97eae 100644
--- a/core/test/unit/api/canary/utils/serializers/input/posts_spec.js
+++ b/core/test/unit/api/canary/utils/serializers/input/posts_spec.js
@@ -221,174 +221,6 @@ describe('Unit: canary/utils/serializers/input/posts', function () {
});
describe('edit', function () {
- describe('Ensure relative urls are returned for standard image urls', function () {
- describe('no subdir', function () {
- let sandbox;
-
- after(function () {
- sandbox.restore();
- });
-
- before(function () {
- sandbox = sinon.createSandbox();
- urlUtils.stubUrlUtils({url: 'https://mysite.com'}, sandbox);
- });
-
- it('when mobiledoc contains an absolute URL to image', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- }
- },
- data: {
- posts: [
- {
- id: 'id1',
- mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"https://mysite.com/content/images/2019/02/image.jpg"}]]}'
- }
- ]
- }
- };
-
- serializers.input.posts.edit(apiConfig, frame);
-
- let postData = frame.data.posts[0];
- postData.mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"/content/images/2019/02/image.jpg"}]]}');
- });
-
- it('when mobiledoc contains multiple absolute URLs to images with different protocols', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- }
- },
- data: {
- posts: [
- {
- id: 'id1',
- mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"https://mysite.com/content/images/2019/02/image.jpg"}],["image",{"src":"http://mysite.com/content/images/2019/02/image.png"}]]'
- }
- ]
- }
- };
-
- serializers.input.posts.edit(apiConfig, frame);
-
- let postData = frame.data.posts[0];
- postData.mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"/content/images/2019/02/image.jpg"}],["image",{"src":"/content/images/2019/02/image.png"}]]');
- });
-
- it('when blog url is without subdir', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- },
- withRelated: ['tags', 'authors']
- },
- data: {
- posts: [
- {
- id: 'id1',
- feature_image: 'https://mysite.com/content/images/image.jpg',
- og_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
- twitter_image: 'https://mysite.com/blog/content/images/image.jpg',
- tags: [{
- id: 'id3',
- feature_image: 'http://mysite.com/content/images/image.jpg'
- }],
- authors: [{
- id: 'id4',
- name: 'Ghosty',
- profile_image: 'https://somestorage.com/blog/images/image.jpg'
- }]
- }
- ]
- }
- };
- serializers.input.posts.edit(apiConfig, frame);
- let postData = frame.data.posts[0];
- postData.feature_image.should.eql('/content/images/image.jpg');
- postData.og_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
- postData.twitter_image.should.eql('https://mysite.com/blog/content/images/image.jpg');
- postData.tags[0].feature_image.should.eql('/content/images/image.jpg');
- postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/images/image.jpg');
- });
- });
-
- describe('with subdir', function () {
- let sandbox;
-
- after(function () {
- sandbox.restore();
- });
-
- before(function () {
- sandbox = sinon.createSandbox();
- urlUtils.stubUrlUtils({url: 'https://mysite.com/blog'}, sandbox);
- });
-
- it('when blog url is with subdir', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- },
- withRelated: ['tags', 'authors']
- },
- data: {
- posts: [
- {
- id: 'id1',
- feature_image: 'https://mysite.com/blog/content/images/image.jpg',
- og_image: 'https://mysite.com/content/images/image.jpg',
- twitter_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
- tags: [{
- id: 'id3',
- feature_image: 'http://mysite.com/blog/mycustomstorage/content/images/image.jpg'
- }],
- authors: [{
- id: 'id4',
- name: 'Ghosty',
- profile_image: 'https://somestorage.com/blog/content/images/image.jpg'
- }]
- }
- ]
- }
- };
- serializers.input.posts.edit(apiConfig, frame);
- let postData = frame.data.posts[0];
- postData.feature_image.should.eql('/blog/content/images/image.jpg');
- postData.og_image.should.eql('https://mysite.com/content/images/image.jpg');
- postData.twitter_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
- postData.tags[0].feature_image.should.eql('http://mysite.com/blog/mycustomstorage/content/images/image.jpg');
- postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/content/images/image.jpg');
- });
- });
- });
-
describe('Ensure html to mobiledoc conversion', function () {
it('no transformation when no html source option provided', function () {
const apiConfig = {};
diff --git a/core/test/unit/api/canary/utils/serializers/output/utils/url_spec.js b/core/test/unit/api/canary/utils/serializers/output/utils/url_spec.js
index 9649030714..3078ab416d 100644
--- a/core/test/unit/api/canary/utils/serializers/output/utils/url_spec.js
+++ b/core/test/unit/api/canary/utils/serializers/output/utils/url_spec.js
@@ -9,7 +9,9 @@ describe('Unit: canary/utils/serializers/output/utils/url', function () {
beforeEach(function () {
sinon.stub(urlService, 'getUrlByResourceId').returns('getUrlByResourceId');
sinon.stub(urlUtils, 'urlFor').returns('urlFor');
+ sinon.stub(urlUtils, 'relativeToAbsolute').returns('relativeToAbsolute');
sinon.stub(urlUtils, 'htmlRelativeToAbsolute').returns({html: sinon.stub()});
+ sinon.stub(urlUtils, 'mobiledocRelativeToAbsolute').returns({});
});
afterEach(function () {
@@ -28,21 +30,32 @@ describe('Unit: canary/utils/serializers/output/utils/url', function () {
it('meta & models & relations', function () {
const post = pageModel(testUtils.DataGenerator.forKnex.createPost({
id: 'id1',
- feature_image: 'value'
+ mobiledoc: '{}',
+ html: 'html',
+ custom_excerpt: 'customExcerpt',
+ codeinjection_head: 'codeinjectionHead',
+ codeinjection_foot: 'codeinjectionFoot',
+ feature_image: 'featureImage',
+ og_image: 'ogImage',
+ twitter_image: 'twitterImage',
+ canonical_url: 'canonicalUrl'
}));
urlUtil.forPost(post.id, post, {options: {}});
post.hasOwnProperty('url').should.be.true();
- urlUtils.urlFor.callCount.should.eql(1);
- urlUtils.urlFor.getCall(0).args.should.eql(['image', {image: 'value'}, true]);
+ // feature_image, og_image, twitter_image, canonical_url
+ urlUtils.relativeToAbsolute.callCount.should.eql(4);
- urlUtils.htmlRelativeToAbsolute.callCount.should.eql(1);
+ // mobiledoc
+ urlUtils.mobiledocRelativeToAbsolute.callCount.should.eql(1);
+
+ // html, codeinjection_head, codeinjection_foot
+ urlUtils.htmlRelativeToAbsolute.callCount.should.eql(3);
urlUtils.htmlRelativeToAbsolute.getCall(0).args.should.eql([
- '## markdown',
- 'getUrlByResourceId',
- {assetsOnly: true}
+ 'html',
+ 'getUrlByResourceId'
]);
urlService.getUrlByResourceId.callCount.should.eql(1);
diff --git a/core/test/unit/api/shared/serializers/input/utils/url_spec.js b/core/test/unit/api/shared/serializers/input/utils/url_spec.js
index 78af13c36e..bafbd960a0 100644
--- a/core/test/unit/api/shared/serializers/input/utils/url_spec.js
+++ b/core/test/unit/api/shared/serializers/input/utils/url_spec.js
@@ -13,45 +13,5 @@ describe('Unit: v2/utils/serializers/input/utils/url', function () {
afterEach(function () {
sinon.restore();
});
-
- it('should transform canonical_url when protocol and domain match', function () {
- const attrs = {
- canonical_url: 'https://blogurl.com/hello-world'
- };
-
- url.forPost(attrs, {});
-
- should.equal(attrs.canonical_url, '/hello-world');
- });
-
- it('should transform canonical_url when protocol and domain match with backslash in the end', function () {
- const attrs = {
- canonical_url: 'https://blogurl.com/hello-world/'
- };
-
- url.forPost(attrs, {});
-
- should.equal(attrs.canonical_url, '/hello-world/');
- });
-
- it('should not transform canonical_url when different domains', function () {
- const attrs = {
- canonical_url: 'http://ghost.org/no-transform'
- };
-
- url.forPost(attrs, {});
-
- should.equal(attrs.canonical_url, 'http://ghost.org/no-transform');
- });
-
- it('should not transform canonical_url when different protocols', function () {
- const attrs = {
- canonical_url: 'http://blogurl.com/no-transform'
- };
-
- url.forPost(attrs, {});
-
- should.equal(attrs.canonical_url, 'http://blogurl.com/no-transform');
- });
});
});
diff --git a/core/test/unit/api/v2/utils/serializers/input/posts_spec.js b/core/test/unit/api/v2/utils/serializers/input/posts_spec.js
index 257e6498f7..32d37c6fb7 100644
--- a/core/test/unit/api/v2/utils/serializers/input/posts_spec.js
+++ b/core/test/unit/api/v2/utils/serializers/input/posts_spec.js
@@ -221,174 +221,6 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
});
describe('edit', function () {
- describe('Ensure relative urls are returned for standard image urls', function () {
- describe('no subdir', function () {
- let sandbox;
-
- after(function () {
- sandbox.restore();
- });
-
- before(function () {
- sandbox = sinon.createSandbox();
- urlUtils.stubUrlUtils({url: 'https://mysite.com'}, sandbox);
- });
-
- it('when mobiledoc contains an absolute URL to image', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- }
- },
- data: {
- posts: [
- {
- id: 'id1',
- mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"https://mysite.com/content/images/2019/02/image.jpg"}]]}'
- }
- ]
- }
- };
-
- serializers.input.posts.edit(apiConfig, frame);
-
- let postData = frame.data.posts[0];
- postData.mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"/content/images/2019/02/image.jpg"}]]}');
- });
-
- it('when mobiledoc contains multiple absolute URLs to images with different protocols', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- }
- },
- data: {
- posts: [
- {
- id: 'id1',
- mobiledoc: '{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"https://mysite.com/content/images/2019/02/image.jpg"}],["image",{"src":"http://mysite.com/content/images/2019/02/image.png"}]]'
- }
- ]
- }
- };
-
- serializers.input.posts.edit(apiConfig, frame);
-
- let postData = frame.data.posts[0];
- postData.mobiledoc.should.equal('{"version":"0.3.1","atoms":[],"cards":[["image",{"src":"/content/images/2019/02/image.jpg"}],["image",{"src":"/content/images/2019/02/image.png"}]]');
- });
-
- it('when blog url is without subdir', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- },
- withRelated: ['tags', 'authors']
- },
- data: {
- posts: [
- {
- id: 'id1',
- feature_image: 'https://mysite.com/content/images/image.jpg',
- og_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
- twitter_image: 'https://mysite.com/blog/content/images/image.jpg',
- tags: [{
- id: 'id3',
- feature_image: 'http://mysite.com/content/images/image.jpg'
- }],
- authors: [{
- id: 'id4',
- name: 'Ghosty',
- profile_image: 'https://somestorage.com/blog/images/image.jpg'
- }]
- }
- ]
- }
- };
- serializers.input.posts.edit(apiConfig, frame);
- let postData = frame.data.posts[0];
- postData.feature_image.should.eql('/content/images/image.jpg');
- postData.og_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
- postData.twitter_image.should.eql('https://mysite.com/blog/content/images/image.jpg');
- postData.tags[0].feature_image.should.eql('/content/images/image.jpg');
- postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/images/image.jpg');
- });
- });
-
- describe('with subdir', function () {
- let sandbox;
-
- after(function () {
- sandbox.restore();
- });
-
- before(function () {
- sandbox = sinon.createSandbox();
- urlUtils.stubUrlUtils({url: 'https://mysite.com/blog'}, sandbox);
- });
-
- it('when blog url is with subdir', function () {
- const apiConfig = {};
- const frame = {
- options: {
- context: {
- user: 0,
- api_key: {
- id: 1,
- type: 'content'
- }
- },
- withRelated: ['tags', 'authors']
- },
- data: {
- posts: [
- {
- id: 'id1',
- feature_image: 'https://mysite.com/blog/content/images/image.jpg',
- og_image: 'https://mysite.com/content/images/image.jpg',
- twitter_image: 'https://mysite.com/mycustomstorage/images/image.jpg',
- tags: [{
- id: 'id3',
- feature_image: 'http://mysite.com/blog/mycustomstorage/content/images/image.jpg'
- }],
- authors: [{
- id: 'id4',
- name: 'Ghosty',
- profile_image: 'https://somestorage.com/blog/content/images/image.jpg'
- }]
- }
- ]
- }
- };
- serializers.input.posts.edit(apiConfig, frame);
- let postData = frame.data.posts[0];
- postData.feature_image.should.eql('/blog/content/images/image.jpg');
- postData.og_image.should.eql('https://mysite.com/content/images/image.jpg');
- postData.twitter_image.should.eql('https://mysite.com/mycustomstorage/images/image.jpg');
- postData.tags[0].feature_image.should.eql('http://mysite.com/blog/mycustomstorage/content/images/image.jpg');
- postData.authors[0].profile_image.should.eql('https://somestorage.com/blog/content/images/image.jpg');
- });
- });
- });
-
describe('Ensure html to mobiledoc conversion', function () {
it('no transformation when no html source option provided', function () {
const apiConfig = {};
diff --git a/core/test/unit/api/v2/utils/serializers/output/utils/url_spec.js b/core/test/unit/api/v2/utils/serializers/output/utils/url_spec.js
index e9c16643ae..bb8e988f3a 100644
--- a/core/test/unit/api/v2/utils/serializers/output/utils/url_spec.js
+++ b/core/test/unit/api/v2/utils/serializers/output/utils/url_spec.js
@@ -9,7 +9,9 @@ describe('Unit: v2/utils/serializers/output/utils/url', function () {
beforeEach(function () {
sinon.stub(urlService, 'getUrlByResourceId').returns('getUrlByResourceId');
sinon.stub(urlUtils, 'urlFor').returns('urlFor');
+ sinon.stub(urlUtils, 'relativeToAbsolute').returns('relativeToAbsolute');
sinon.stub(urlUtils, 'htmlRelativeToAbsolute').returns({html: sinon.stub()});
+ sinon.stub(urlUtils, 'mobiledocRelativeToAbsolute').returns({});
});
afterEach(function () {
@@ -28,19 +30,31 @@ describe('Unit: v2/utils/serializers/output/utils/url', function () {
it('meta & models & relations', function () {
const post = pageModel(testUtils.DataGenerator.forKnex.createPost({
id: 'id1',
- feature_image: 'value'
+ mobiledoc: '{}',
+ html: 'html',
+ custom_excerpt: 'customExcerpt',
+ codeinjection_head: 'codeinjectionHead',
+ codeinjection_foot: 'codeinjectionFoot',
+ feature_image: 'featureImage',
+ og_image: 'ogImage',
+ twitter_image: 'twitterImage',
+ canonical_url: 'canonicalUrl'
}));
urlUtil.forPost(post.id, post, {options: {}});
post.hasOwnProperty('url').should.be.true();
- urlUtils.urlFor.callCount.should.eql(1);
- urlUtils.urlFor.getCall(0).args.should.eql(['image', {image: 'value'}, true]);
+ // feature_image, og_image, twitter_image, canonical_url
+ urlUtils.relativeToAbsolute.callCount.should.eql(4);
- urlUtils.htmlRelativeToAbsolute.callCount.should.eql(1);
+ // mobiledoc
+ urlUtils.mobiledocRelativeToAbsolute.callCount.should.eql(1);
+
+ // html, codeinjection_head, codeinjection_foot
+ urlUtils.htmlRelativeToAbsolute.callCount.should.eql(3);
urlUtils.htmlRelativeToAbsolute.getCall(0).args.should.eql([
- '## markdown',
+ 'html',
'getUrlByResourceId',
{assetsOnly: true}
]);
diff --git a/package.json b/package.json
index 0f1054d90d..8842530296 100644
--- a/package.json
+++ b/package.json
@@ -45,7 +45,7 @@
"@tryghost/members-ssr": "0.6.0",
"@tryghost/social-urls": "0.1.2",
"@tryghost/string": "^0.1.3",
- "@tryghost/url-utils": "0.6.0",
+ "@tryghost/url-utils": "0.6.1",
"ajv": "6.10.2",
"amperize": "0.6.0",
"analytics-node": "3.3.0",
diff --git a/yarn.lock b/yarn.lock
index ab4edfa6f9..0dea3688c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -297,17 +297,15 @@
dependencies:
unidecode "^0.1.8"
-"@tryghost/url-utils@0.6.0":
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/@tryghost/url-utils/-/url-utils-0.6.0.tgz#12ad31781c03cfb6cd7eedbc8b4c690d4ef3e4d1"
- integrity sha512-BN9l448lW2ykE0/QIeCijs1eVFGPtta1JCol6X4jzoqzy/hjL/YyGKj5ugLVOX+Fjl9Y/sblF6Yac+UzoaHkiA==
+"@tryghost/url-utils@0.6.1":
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/@tryghost/url-utils/-/url-utils-0.6.1.tgz#cb3a1c199ff855a131588258e43bbcb1599b856c"
+ integrity sha512-FfHc/OoMqKvKbQ8Rir09wkeFZPV7FZMfmnKaVFOUoJPuULetFmfS8yP0WNBHNfGj197aT+JyyJH2QpFokvPprQ==
dependencies:
cheerio "0.22.0"
moment "2.24.0"
moment-timezone "0.5.23"
- remark-parse "^7.0.1"
- remark-stringify "^7.0.3"
- unified "^8.4.0"
+ remark "^11.0.1"
unist-util-visit "^2.0.0"
"@types/bluebird@^3.5.26", "@types/bluebird@^3.5.27":
@@ -7310,7 +7308,7 @@ regexpp@^2.0.1:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
-remark-parse@^7.0.1:
+remark-parse@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-7.0.1.tgz#0c13d67e0d7b82c2ad2d8b6604ec5fae6c333c2b"
integrity sha512-WOZLa545jYXtSy+txza6ACudKWByQac4S2DmGk+tAGO/3XnVTOxwyCIxB7nTcLlk8Aayhcuf3cV1WV6U6L7/DQ==
@@ -7331,7 +7329,7 @@ remark-parse@^7.0.1:
vfile-location "^2.0.0"
xtend "^4.0.1"
-remark-stringify@^7.0.3:
+remark-stringify@^7.0.0:
version "7.0.3"
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-7.0.3.tgz#9221e9770b0b395af83a0d5881a44b6fcb9d0a2a"
integrity sha512-+jgmjNjm2kR7y2Ns1BATXRlFr+iQ7sDcpSgytfU77nkw7UCd5yJNArSxB3MU3Uul7HuyYNTCjetoGfy8xLia1A==
@@ -7351,6 +7349,15 @@ remark-stringify@^7.0.3:
unherit "^1.0.4"
xtend "^4.0.1"
+remark@^11.0.1:
+ version "11.0.1"
+ resolved "https://registry.yarnpkg.com/remark/-/remark-11.0.1.tgz#3c16e1ed84c78a661299991bb8d5fa7ee5d18e3c"
+ integrity sha512-Fl2AvN+yU6sOBAjUz3xNC5iEvLkXV8PZicLOOLifjU8uKGusNvhHfGRCfETsqyvRHZ24JXqEyDY4hRLhoUd30A==
+ dependencies:
+ remark-parse "^7.0.0"
+ remark-stringify "^7.0.0"
+ unified "^8.2.0"
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -8709,7 +8716,7 @@ unidecode@^0.1.8:
resolved "https://registry.yarnpkg.com/unidecode/-/unidecode-0.1.8.tgz#efbb301538bc45246a9ac8c559d72f015305053e"
integrity sha1-77swFTi8RSRqmsjFWdcvAVMFBT4=
-unified@^8.4.0:
+unified@^8.2.0:
version "8.4.1"
resolved "https://registry.yarnpkg.com/unified/-/unified-8.4.1.tgz#99bd0393f58a139eaa51832cfbcc0e7f6573a1e1"
integrity sha512-YPj/uIIZSO7mMIZQj/5Z3hDl4lshWYRQGs5TgUCjHTVdklUWH+O94mK5Cy77SEcmEUwGhnUcudMuH/zIwporqw==