diff --git a/ghost/core/core/server/services/email-service/wrapper.js b/ghost/core/core/server/services/email-service/wrapper.js index 150f9c3eed..8dd48e8997 100644 --- a/ghost/core/core/server/services/email-service/wrapper.js +++ b/ghost/core/core/server/services/email-service/wrapper.js @@ -34,6 +34,7 @@ class EmailServiceWrapper { const linkReplacer = require('@tryghost/link-replacer'); const linkTracking = require('../link-tracking'); const audienceFeedback = require('../audience-feedback'); + const storageUtils = require('../../adapters/storage/utils'); // capture errors from mailgun client and log them in sentry const errorHandler = (error) => { @@ -60,6 +61,7 @@ class EmailServiceWrapper { }, imageSize: null, urlUtils, + storageUtils, getPostUrl: this.getPostUrl, linkReplacer, linkTracking, diff --git a/ghost/core/core/server/services/mega/post-email-serializer.js b/ghost/core/core/server/services/mega/post-email-serializer.js index 833e7e8c07..60117ab40a 100644 --- a/ghost/core/core/server/services/mega/post-email-serializer.js +++ b/ghost/core/core/server/services/mega/post-email-serializer.js @@ -10,7 +10,7 @@ const mobiledocLib = require('../../lib/mobiledoc'); const lexicalLib = require('../../lib/lexical'); const htmlToPlaintext = require('@tryghost/html-to-plaintext'); const membersService = require('../members'); -const {isUnsplashImage, isLocalContentImage} = require('@tryghost/kg-default-cards/lib/utils'); +const {isUnsplashImage} = require('@tryghost/kg-default-cards/lib/utils'); const {textColorForBackgroundColor, darkenToContrastThreshold} = require('@tryghost/color-utils'); const logging = require('@tryghost/logging'); const urlService = require('../../services/url'); @@ -19,6 +19,7 @@ const linkTracking = require('../link-tracking'); const memberAttribution = require('../member-attribution'); const feedbackButtons = require('./feedback-buttons'); const labs = require('../../../shared/labs'); +const storageUtils = require('../../adapters/storage/utils'); const ALLOWED_REPLACEMENTS = ['first_name', 'uuid']; @@ -261,7 +262,7 @@ const PostEmailSerializer = { templateSettings.headerImageWidth = 600; } - if (isLocalContentImage(templateSettings.headerImage, urlUtils.getSiteUrl())) { + if (storageUtils.isLocalImage(templateSettings.headerImage)) { // we can safely request a 1200px image - Ghost will serve the original if it's smaller templateSettings.headerImage = templateSettings.headerImage.replace(/\/content\/images\//, '/content/images/size/w1200/'); } @@ -348,7 +349,7 @@ const PostEmailSerializer = { post.feature_image_width = 600; } - if (isLocalContentImage(post.feature_image, urlUtils.getSiteUrl())) { + if (storageUtils.isLocalImage(post.feature_image)) { // we can safely request a 1200px image - Ghost will serve the original if it's smaller post.feature_image = post.feature_image.replace(/\/content\/images\//, '/content/images/size/w1200/'); } diff --git a/ghost/core/core/shared/config/overrides.json b/ghost/core/core/shared/config/overrides.json index 10e34f213a..abd80abed9 100644 --- a/ghost/core/core/shared/config/overrides.json +++ b/ghost/core/core/shared/config/overrides.json @@ -86,7 +86,8 @@ "w2400": {"width": 2400} }, "internalImageSizes": { - "icon": {"width": 256, "height": 256} + "icon": {"width": 256, "height": 256}, + "email-header-image": {"width": 1200} } } } diff --git a/ghost/email-service/lib/email-renderer.js b/ghost/email-service/lib/email-renderer.js index dea4c0126c..baa0094a5d 100644 --- a/ghost/email-service/lib/email-renderer.js +++ b/ghost/email-service/lib/email-renderer.js @@ -50,6 +50,7 @@ class EmailRenderer { #imageSize; #urlUtils; #getPostUrl; + #storageUtils; #handlebars; #renderTemplate; @@ -67,6 +68,7 @@ class EmailRenderer { * @param {{render(object, options): string}} dependencies.renderers.mobiledoc * @param {{getImageSizeFromUrl(url: string): Promise<{width: number}>}} dependencies.imageSize * @param {{urlFor(type: string, optionsOrAbsolute, absolute): string, isSiteUrl(url, context): boolean}} dependencies.urlUtils + * @param {{isLocalImage(url: string): boolean}} dependencies.storageUtils * @param {(post: Post) => string} dependencies.getPostUrl * @param {object} dependencies.linkReplacer * @param {object} dependencies.linkTracking @@ -79,6 +81,7 @@ class EmailRenderer { renderers, imageSize, urlUtils, + storageUtils, getPostUrl, linkReplacer, linkTracking, @@ -90,6 +93,7 @@ class EmailRenderer { this.#renderers = renderers; this.#imageSize = imageSize; this.#urlUtils = urlUtils; + this.#storageUtils = storageUtils; this.#getPostUrl = getPostUrl; this.#linkReplacer = linkReplacer; this.#linkTracking = linkTracking; @@ -633,9 +637,7 @@ class EmailRenderer { size.width = 600; } - // WARNING: - // TODO: this whole `isLocalContentImage` can never ever work (always false), this is old code that needs a rewrite! - if (isLocalContentImage(href, this.#urlUtils.urlFor('home', true))) { + if (this.#storageUtils.isLocalImage(href)) { // we can safely request a 1200px image - Ghost will serve the original if it's smaller return { href: href.replace(/\/content\/images\//, '/content/images/size/w1200/'), diff --git a/ghost/email-service/test/email-renderer.test.js b/ghost/email-service/test/email-renderer.test.js index 208a332f85..1a28bc5d33 100644 --- a/ghost/email-service/test/email-renderer.test.js +++ b/ghost/email-service/test/email-renderer.test.js @@ -516,4 +516,66 @@ describe('Email renderer', function () { responsePaid.html.should.not.containEql('Become a paid member of Test Blog to get access to all'); }); }); + + describe('limitImageWidth', function () { + it('Limits width of local images', async function () { + const emailRenderer = new EmailRenderer({ + imageSize: { + getImageSizeFromUrl() { + return { + width: 2000 + }; + } + }, + storageUtils: { + isLocalImage(url) { + return url === 'http://your-blog.com/content/images/2017/01/02/example.png'; + } + } + }); + const response = await emailRenderer.limitImageWidth('http://your-blog.com/content/images/2017/01/02/example.png'); + assert.equal(response.width, 600); + assert.equal(response.href, 'http://your-blog.com/content/images/size/w1200/2017/01/02/example.png'); + }); + + it('Limits width of unsplash images', async function () { + const emailRenderer = new EmailRenderer({ + imageSize: { + getImageSizeFromUrl() { + return { + width: 2000 + }; + } + }, + storageUtils: { + isLocalImage(url) { + return url === 'http://your-blog.com/content/images/2017/01/02/example.png'; + } + } + }); + const response = await emailRenderer.limitImageWidth('https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=2000'); + assert.equal(response.width, 600); + assert.equal(response.href, 'https://images.unsplash.com/photo-1657816793628-191deb91e20f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxMTc3M3wwfDF8YWxsfDJ8fHx8fHwyfHwxNjU3ODkzNjU5&ixlib=rb-1.2.1&q=80&w=1200'); + }); + + it('Does not increase width of images', async function () { + const emailRenderer = new EmailRenderer({ + imageSize: { + getImageSizeFromUrl() { + return { + width: 300 + }; + } + }, + storageUtils: { + isLocalImage(url) { + return url === 'http://your-blog.com/content/images/2017/01/02/example.png'; + } + } + }); + const response = await emailRenderer.limitImageWidth('https://example.com/image.png'); + assert.equal(response.width, 300); + assert.equal(response.href, 'https://example.com/image.png'); + }); + }); });