diff --git a/ghost/core/core/frontend/services/routing/controllers/unsubscribe.js b/ghost/core/core/frontend/services/routing/controllers/unsubscribe.js index 0793066e0a..7b730ade70 100644 --- a/ghost/core/core/frontend/services/routing/controllers/unsubscribe.js +++ b/ghost/core/core/frontend/services/routing/controllers/unsubscribe.js @@ -2,7 +2,6 @@ const debug = require('@tryghost/debug')('services:routing:controllers:unsubscri const url = require('url'); const members = require('../../../../server/services/members'); const urlUtils = require('../../../../shared/url-utils'); -const labs = require('../../../../shared/labs'); const logging = require('@tryghost/logging'); module.exports = async function unsubscribeController(req, res) { @@ -15,7 +14,7 @@ module.exports = async function unsubscribeController(req, res) { return res.end('Email address not found.'); } - if (req.method === 'POST' && labs.isSet('listUnsubscribeHeader')) { + if (req.method === 'POST') { logging.info('[List-Unsubscribe] Received POST unsubscribe for ' + query.uuid + ', newsletter: ' + (query.newsletter ?? 'null') + ', comments: ' + (query.comments ?? 'false')); // Do an actual unsubscribe diff --git a/ghost/core/core/shared/labs.js b/ghost/core/core/shared/labs.js index c35874d3fa..a905b519b9 100644 --- a/ghost/core/core/shared/labs.js +++ b/ghost/core/core/shared/labs.js @@ -21,7 +21,6 @@ const GA_FEATURES = [ 'announcementBar', 'signupForm', 'recommendations', - 'listUnsubscribeHeader', 'newEmailAddresses', 'internalLinking' ]; diff --git a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap index 8e8e09661d..aa66016d70 100644 --- a/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap +++ b/ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap @@ -1155,7 +1155,7 @@ exports[`Settings API Edit Can edit a setting 2: [headers] 1`] = ` Object { "access-control-allow-origin": "http://127.0.0.1:2369", "cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0", - "content-length": "4530", + "content-length": "4499", "content-type": "application/json; charset=utf-8", "content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/, "etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/, diff --git a/ghost/core/test/e2e-frontend/members.test.js b/ghost/core/test/e2e-frontend/members.test.js index 99d77df355..80ed4e0c29 100644 --- a/ghost/core/test/e2e-frontend/members.test.js +++ b/ghost/core/test/e2e-frontend/members.test.js @@ -9,7 +9,7 @@ const settingsCache = require('../../core/shared/settings-cache'); const DomainEvents = require('@tryghost/domain-events'); const {MemberPageViewEvent} = require('@tryghost/member-events'); const models = require('../../core/server/models'); -const {mockManager, fixtureManager} = require('../utils/e2e-framework'); +const {fixtureManager} = require('../utils/e2e-framework'); const DataGenerator = require('../utils/fixtures/data-generator'); const members = require('../../core/server/services/members'); @@ -266,14 +266,6 @@ describe('Front-end members behavior', function () { }); describe('Unsubscribe', function () { - beforeEach(function () { - mockManager.mockLabsEnabled('listUnsubscribeHeader'); - }); - - afterEach(function () { - mockManager.restore(); - }); - it('should redirect with uuid and action param', async function () { await request.get('/unsubscribe/?uuid=XXX') .expect(302) diff --git a/ghost/email-service/lib/EmailRenderer.js b/ghost/email-service/lib/EmailRenderer.js index 9ba2ad2e74..b36f80ad59 100644 --- a/ghost/email-service/lib/EmailRenderer.js +++ b/ghost/email-service/lib/EmailRenderer.js @@ -687,22 +687,17 @@ class EmailRenderer { getValue: (member) => { return this.getMemberStatusText(member); } + }, + // List unsubscribe header to unsubcribe in one-click + { + id: 'list_unsubscribe', + getValue: (member) => { + return this.createUnsubscribeUrl(member.uuid, {newsletterUuid}); + }, + required: true // Used in email headers } ]; - if (this.#labs.isSet('listUnsubscribeHeader')) { - baseDefinitions.push( - { - id: 'list_unsubscribe', - getValue: (member) => { - // Same URL - return this.createUnsubscribeUrl(member.uuid, {newsletterUuid}); - }, - required: true // Used in email headers - } - ); - } - // Now loop through all the definenitions to see which ones are actually used + to add fallbacks if needed const EMAIL_REPLACEMENT_REGEX = /%%\{(.*?)\}%%/g; const REPLACEMENT_STRING_REGEX = /^(?\w+?)(?:,? *(?:"|")(?.*?)(?:"|"))?$/; diff --git a/ghost/email-service/test/email-renderer.test.js b/ghost/email-service/test/email-renderer.test.js index 8a17bef600..7fa4f2f612 100644 --- a/ghost/email-service/test/email-renderer.test.js +++ b/ghost/email-service/test/email-renderer.test.js @@ -107,16 +107,19 @@ describe('Email renderer', function () { }; }); - it('returns an empty list of replacements if nothing is used', function () { + it('returns the unsubscribe header replacement by default', function () { const html = 'Hello world'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 0); + assert.equal(replacements.length, 1); + assert.equal(replacements[0].token.toString(), '/%%\\{list_unsubscribe\\}%%/g'); + assert.equal(replacements[0].id, 'list_unsubscribe'); + assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=myuuid&newsletter=newsletteruuid`); }); it('returns a replacement if it is used', function () { const html = 'Hello world %%{uuid}%%'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{uuid\\}%%/g'); assert.equal(replacements[0].id, 'uuid'); assert.equal(replacements[0].getValue(member), 'myuuid'); @@ -125,7 +128,7 @@ describe('Email renderer', function () { it('returns a replacement only once if used multiple times', function () { const html = 'Hello world %%{uuid}%% And %%{uuid}%%'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{uuid\\}%%/g'); assert.equal(replacements[0].id, 'uuid'); assert.equal(replacements[0].getValue(member), 'myuuid'); @@ -134,7 +137,7 @@ describe('Email renderer', function () { it('returns correct first name', function () { const html = 'Hello %%{first_name}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{first_name\\}%%/g'); assert.equal(replacements[0].id, 'first_name'); assert.equal(replacements[0].getValue(member), 'Test'); @@ -143,26 +146,16 @@ describe('Email renderer', function () { it('returns correct unsubscribe url', function () { const html = 'Hello %%{unsubscribe_url}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{unsubscribe_url\\}%%/g'); assert.equal(replacements[0].id, 'unsubscribe_url'); assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=myuuid&newsletter=newsletteruuid`); }); - it('returns correct list-unsubscribe value', function () { - labsEnabled = true; - const html = 'Hello'; - const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); - assert.equal(replacements[0].token.toString(), '/%%\\{list_unsubscribe\\}%%/g'); - assert.equal(replacements[0].id, 'list_unsubscribe'); - assert.equal(replacements[0].getValue(member), `http://example.com/subdirectory/unsubscribe/?uuid=myuuid&newsletter=newsletteruuid`); - }); - it('returns correct name', function () { const html = 'Hello %%{name}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{name\\}%%/g'); assert.equal(replacements[0].id, 'name'); assert.equal(replacements[0].getValue(member), 'Test User'); @@ -172,7 +165,7 @@ describe('Email renderer', function () { member.name = ''; const html = 'Hello %%{name_class}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{name_class\\}%%/g'); assert.equal(replacements[0].id, 'name_class'); assert.equal(replacements[0].getValue(member), 'hidden'); @@ -181,7 +174,7 @@ describe('Email renderer', function () { it('returns empty class for available name', function () { const html = 'Hello %%{name_class}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{name_class\\}%%/g'); assert.equal(replacements[0].id, 'name_class'); assert.equal(replacements[0].getValue(member), ''); @@ -190,7 +183,7 @@ describe('Email renderer', function () { it('returns correct email', function () { const html = 'Hello %%{email}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{email\\}%%/g'); assert.equal(replacements[0].id, 'email'); assert.equal(replacements[0].getValue(member), 'test@example.com'); @@ -199,7 +192,7 @@ describe('Email renderer', function () { it('returns correct status', function () { const html = 'Hello %%{status}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{status\\}%%/g'); assert.equal(replacements[0].id, 'status'); assert.equal(replacements[0].getValue(member), 'free'); @@ -209,7 +202,7 @@ describe('Email renderer', function () { member.status = 'comped'; const html = 'Hello %%{status}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{status\\}%%/g'); assert.equal(replacements[0].id, 'status'); assert.equal(replacements[0].getValue(member), 'complimentary'); @@ -227,7 +220,7 @@ describe('Email renderer', function () { ]; const html = 'Hello %%{status}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{status\\}%%/g'); assert.equal(replacements[0].id, 'status'); assert.equal(replacements[0].getValue(member), 'trialing'); @@ -236,7 +229,7 @@ describe('Email renderer', function () { it('returns manage_account_url', function () { const html = 'Hello %%{manage_account_url}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{manage_account_url\\}%%/g'); assert.equal(replacements[0].id, 'manage_account_url'); assert.equal(replacements[0].getValue(member), 'http://example.com/subdirectory/#/portal/account'); @@ -255,7 +248,7 @@ describe('Email renderer', function () { ]; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{status_text\\}%%/g'); assert.equal(replacements[0].id, 'status_text'); assert.equal(replacements[0].getValue(member), 'Your free trial ends on 13 March 2050, at which time you will be charged the regular price. You can always cancel before then.'); @@ -264,7 +257,7 @@ describe('Email renderer', function () { it('returns correct createdAt', function () { const html = 'Hello %%{created_at}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{created_at\\}%%/g'); assert.equal(replacements[0].id, 'created_at'); assert.equal(replacements[0].getValue(member), '13 March 2023'); @@ -274,7 +267,7 @@ describe('Email renderer', function () { member.createdAt = null; const html = 'Hello %%{created_at}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{created_at\\}%%/g'); assert.equal(replacements[0].id, 'created_at'); assert.equal(replacements[0].getValue(member), ''); @@ -283,7 +276,7 @@ describe('Email renderer', function () { it('supports fallback values', function () { const html = 'Hey %%{first_name, "there"}%%,'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 1); + assert.equal(replacements.length, 2); assert.equal(replacements[0].token.toString(), '/%%\\{first_name, (?:"|")there(?:"|")\\}%%/g'); assert.equal(replacements[0].id, 'first_name_2'); assert.equal(replacements[0].getValue(member), 'Test'); @@ -295,7 +288,7 @@ describe('Email renderer', function () { it('supports combination of multiple fallback values', function () { const html = 'Hey %%{first_name, "there"}%%, %%{first_name, "member"}%% %%{first_name}%% %%{first_name, "there"}%%'; const replacements = emailRenderer.buildReplacementDefinitions({html, newsletterUuid: newsletter.get('uuid')}); - assert.equal(replacements.length, 3); + assert.equal(replacements.length, 4); assert.equal(replacements[0].token.toString(), '/%%\\{first_name, (?:"|")there(?:"|")\\}%%/g'); assert.equal(replacements[0].id, 'first_name_2'); assert.equal(replacements[0].getValue(member), 'Test');