mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Modified outbound link tagger to use ?ref=domain instead of ?ref=siteTitle (#16431)
refs TryGhost/Team#2755 - Outbound link tagger will now append ?ref=example.com or ?ref=example.ghost.io instead of ?ref=siteTitle
This commit is contained in:
parent
25f4974846
commit
0640b9c4aa
6 changed files with 132 additions and 76 deletions
|
@ -2,6 +2,7 @@ const urlService = require('../url');
|
|||
const urlUtils = require('../../../shared/url-utils');
|
||||
const settingsCache = require('../../../shared/settings-cache');
|
||||
const labs = require('../../../shared/labs');
|
||||
const config = require('../../../shared/config');
|
||||
|
||||
class MemberAttributionServiceWrapper {
|
||||
init() {
|
||||
|
@ -35,7 +36,7 @@ class MemberAttributionServiceWrapper {
|
|||
|
||||
this.outboundLinkTagger = new OutboundLinkTagger({
|
||||
isEnabled: () => !labs.isSet('outboundLinkTagging') || !!settingsCache.get('outbound_link_tagging'),
|
||||
getSiteTitle: () => settingsCache.get('title'),
|
||||
getSiteUrl: () => config.getSiteUrl(),
|
||||
urlUtils
|
||||
});
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ If you prefer to use a contact form, almost all of the great embedded form servi
|
|||
"feature_image_caption": null,
|
||||
"featured": false,
|
||||
"frontmatter": null,
|
||||
"html": "<p>If you want to set up a contact page for people to be able to reach out to you, the simplest way is to set up a simple page like this and list the different ways people can reach out to you.</p><h3 id=\\"for-example-heres-how-to-reach-us\\">For example, here's how to reach us!</h3><ul><li><a href=\\"https://twitter.com/ghost?ref=ghost\\">@Ghost</a> on Twitter</li><li><a href=\\"https://www.facebook.com/ghost\\">@Ghost</a> on Facebook</li><li><a href=\\"https://instagram.com/ghost?ref=ghost\\">@Ghost</a> on Instagram</li></ul><p>If you prefer to use a contact form, almost all of the great embedded form services work great with Ghost and are easy to set up:</p><figure class=\\"kg-card kg-image-card\\"><a href=\\"https://ghost.org/integrations/?tag=forms&ref=ghost\\"><img src=\\"https://static.ghost.org/v4.0.0/images/integrations.png\\" class=\\"kg-image\\" alt loading=\\"lazy\\" width=\\"2944\\" height=\\"1716\\"></a></figure>",
|
||||
"html": "<p>If you want to set up a contact page for people to be able to reach out to you, the simplest way is to set up a simple page like this and list the different ways people can reach out to you.</p><h3 id=\\"for-example-heres-how-to-reach-us\\">For example, here's how to reach us!</h3><ul><li><a href=\\"https://twitter.com/ghost?ref=127.0.0.1\\">@Ghost</a> on Twitter</li><li><a href=\\"https://www.facebook.com/ghost\\">@Ghost</a> on Facebook</li><li><a href=\\"https://instagram.com/ghost?ref=127.0.0.1\\">@Ghost</a> on Instagram</li></ul><p>If you prefer to use a contact form, almost all of the great embedded form services work great with Ghost and are easy to set up:</p><figure class=\\"kg-card kg-image-card\\"><a href=\\"https://ghost.org/integrations/?tag=forms&ref=127.0.0.1\\"><img src=\\"https://static.ghost.org/v4.0.0/images/integrations.png\\" class=\\"kg-image\\" alt loading=\\"lazy\\" width=\\"2944\\" height=\\"1716\\"></a></figure>",
|
||||
"id": "6194d3ce51e2700162531a79",
|
||||
"meta_description": null,
|
||||
"meta_title": null,
|
||||
|
@ -174,7 +174,7 @@ Ghost is a non-profit organization, and we give away all our intellectual proper
|
|||
"feature_image_caption": null,
|
||||
"featured": false,
|
||||
"frontmatter": null,
|
||||
"html": "<p>Oh hey, you clicked every link of our starter content and even clicked this small link in the footer! If you like Ghost and you're enjoying the product so far, we'd hugely appreciate your support in any way you care to show it.</p><p>Ghost is a non-profit organization, and we give away all our intellectual property as open source software. If you believe in what we do, there are a number of ways you can give us a hand, and we hugely appreciate all of them:</p><ul><li>Contribute code via <a href=\\"https://github.com/tryghost?ref=ghost\\">GitHub</a></li><li>Contribute financially via <a href=\\"https://github.com/sponsors/TryGhost?ref=ghost\\">GitHub Sponsors</a></li><li>Contribute financially via <a href=\\"https://opencollective.com/ghost?ref=ghost\\">Open Collective</a></li><li>Contribute reviews via <strong>writing a blog post</strong></li><li>Contribute good vibes via <strong>telling your friends</strong> about us</li></ul><p>Thanks for checking us out!</p>",
|
||||
"html": "<p>Oh hey, you clicked every link of our starter content and even clicked this small link in the footer! If you like Ghost and you're enjoying the product so far, we'd hugely appreciate your support in any way you care to show it.</p><p>Ghost is a non-profit organization, and we give away all our intellectual property as open source software. If you believe in what we do, there are a number of ways you can give us a hand, and we hugely appreciate all of them:</p><ul><li>Contribute code via <a href=\\"https://github.com/tryghost?ref=127.0.0.1\\">GitHub</a></li><li>Contribute financially via <a href=\\"https://github.com/sponsors/TryGhost?ref=127.0.0.1\\">GitHub Sponsors</a></li><li>Contribute financially via <a href=\\"https://opencollective.com/ghost?ref=127.0.0.1\\">Open Collective</a></li><li>Contribute reviews via <strong>writing a blog post</strong></li><li>Contribute good vibes via <strong>telling your friends</strong> about us</li></ul><p>Thanks for checking us out!</p>",
|
||||
"id": "6194d3ce51e2700162531a7b",
|
||||
"meta_description": null,
|
||||
"meta_title": null,
|
||||
|
@ -275,7 +275,7 @@ exports[`Pages Content API Can request pages 2: [headers] 1`] = `
|
|||
Object {
|
||||
"access-control-allow-origin": "*",
|
||||
"cache-control": "public, max-age=0",
|
||||
"content-length": "9209",
|
||||
"content-length": "9233",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"content-version": StringMatching /v\\\\d\\+\\\\\\.\\\\d\\+/,
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -354,7 +354,7 @@ describe('Posts Content API', function () {
|
|||
let response = await agent
|
||||
.get(`posts/${post.id}/`)
|
||||
.expectStatus(200);
|
||||
assert(response.body.posts[0].html.includes('<a href="https://example.com/?ref=ghost">Link</a><a href="invalid">Test</a>'), 'Html not expected (should contain ?ref): ' + response.body.posts[0].html);
|
||||
assert(response.body.posts[0].html.includes('<a href="https://example.com/?ref=127.0.0.1">Link</a><a href="invalid">Test</a>'), 'Html not expected (should contain ?ref): ' + response.body.posts[0].html);
|
||||
|
||||
// Disable outbound link tracking
|
||||
mockManager.mockSetting('outbound_link_tagging', false);
|
||||
|
|
|
@ -17,12 +17,12 @@ class OutboundLinkTagger {
|
|||
*
|
||||
* @param {Object} deps
|
||||
* @param {() => boolean} deps.isEnabled
|
||||
* @param {() => string} deps.getSiteTitle
|
||||
* @param {() => string} deps.getSiteUrl
|
||||
* @param {{isSiteUrl(url, context): boolean}} deps.urlUtils
|
||||
*/
|
||||
constructor({isEnabled, getSiteTitle, urlUtils}) {
|
||||
constructor({isEnabled, getSiteUrl, urlUtils}) {
|
||||
this._isEnabled = isEnabled;
|
||||
this._getSiteTitle = getSiteTitle;
|
||||
this._getSiteUrl = getSiteUrl;
|
||||
this._urlUtils = urlUtils;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,8 @@ class OutboundLinkTagger {
|
|||
return this._isEnabled();
|
||||
}
|
||||
|
||||
get siteTitle() {
|
||||
return this._getSiteTitle();
|
||||
get siteUrl() {
|
||||
return new URL(this._getSiteUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,14 +60,16 @@ class OutboundLinkTagger {
|
|||
return url;
|
||||
}
|
||||
|
||||
// Tag url with site's base domain, excluding www
|
||||
const domain = this.getDomainFromUrl(this.siteUrl);
|
||||
if (useNewsletter) {
|
||||
const name = slugify(useNewsletter.get('name'));
|
||||
|
||||
// If newsletter name ends with newsletter, don't add it again
|
||||
const ref = name.endsWith('newsletter') ? name : `${name}-newsletter`;
|
||||
const ref = name.endsWith('newsletter') ? `${domain}-${name}` : `${domain}-${name}-newsletter`;
|
||||
url.searchParams.append('ref', ref);
|
||||
} else {
|
||||
url.searchParams.append('ref', slugify(this.siteTitle));
|
||||
url.searchParams.append('ref', domain);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
@ -84,6 +86,21 @@ class OutboundLinkTagger {
|
|||
return this.addToUrl(url);
|
||||
});
|
||||
}
|
||||
|
||||
/** *
|
||||
* Extracts the domain from a given URL for use in the ?ref= parameter
|
||||
* e.g. https://example.com/ -> example.com
|
||||
* e.g. https://example.ghost.io/ -> example.ghost.io
|
||||
* e.g. https://www.example.com/ -> example.com
|
||||
*
|
||||
* @param {URL} url to extract domain from
|
||||
* @returns {string} just the domain, e.g. example.com or example.ghost.io, but not www.example.com
|
||||
*/
|
||||
getDomainFromUrl(url) {
|
||||
const hostname = url.hostname;
|
||||
const domain = hostname.replace(/^www\./, '');
|
||||
return domain;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OutboundLinkTagger;
|
||||
|
|
|
@ -14,18 +14,18 @@ describe('OutboundLinkTagger', function () {
|
|||
describe('addToUrl', function () {
|
||||
it('uses sluggified sitename for external urls', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/');
|
||||
const updatedUrl = await service.addToUrl(url);
|
||||
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=hello-world');
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=blog.com');
|
||||
});
|
||||
|
||||
it('does not add if disabled', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => false
|
||||
});
|
||||
const url = new URL('https://example.com/');
|
||||
|
@ -36,7 +36,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('uses sluggified newsletter name for internal urls', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/');
|
||||
|
@ -51,12 +51,12 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
const updatedUrl = await service.addToUrl(url, newsletter);
|
||||
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=used-newsletter-name-newsletter');
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=blog.com-used-newsletter-name-newsletter');
|
||||
});
|
||||
|
||||
it('does not repeat newsletter at the end of the newsletter name', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/');
|
||||
|
@ -70,12 +70,12 @@ describe('OutboundLinkTagger', function () {
|
|||
};
|
||||
const updatedUrl = await service.addToUrl(url, newsletter);
|
||||
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=weekly-newsletter');
|
||||
should(updatedUrl.toString()).equal('https://example.com/?ref=blog.com-weekly-newsletter');
|
||||
});
|
||||
|
||||
it('does not add ref to blocked domains', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://facebook.com/');
|
||||
|
@ -91,7 +91,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add ref if utm_source is present', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/?utm_source=hello');
|
||||
|
@ -101,7 +101,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add ref if ref is present', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/?ref=hello');
|
||||
|
@ -111,7 +111,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add ref if source is present', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const url = new URL('https://example.com/?source=hello');
|
||||
|
@ -123,19 +123,19 @@ describe('OutboundLinkTagger', function () {
|
|||
describe('addToHtml', function () {
|
||||
it('adds refs to external links', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => false
|
||||
}
|
||||
});
|
||||
const html = await service.addToHtml('<a href="https://example.com/test-site">Hello world</a><a href="https://other.com/test/">Hello world</a>');
|
||||
assert.equal(html, '<a href="https://example.com/test-site?ref=hello-world">Hello world</a><a href="https://other.com/test/?ref=hello-world">Hello world</a>');
|
||||
assert.equal(html, '<a href="https://example.com/test-site?ref=blog.com">Hello world</a><a href="https://other.com/test/?ref=blog.com">Hello world</a>');
|
||||
});
|
||||
|
||||
it('does not add refs to internal links', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => true
|
||||
|
@ -147,7 +147,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add refs if disabled', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => false,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => false
|
||||
|
@ -159,7 +159,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add refs to anchors', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => false
|
||||
|
@ -171,7 +171,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('does not add refs to relative links', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => false
|
||||
|
@ -183,7 +183,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('keeps HTML if throws', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => {
|
||||
|
@ -197,7 +197,7 @@ describe('OutboundLinkTagger', function () {
|
|||
|
||||
it('keeps HTML comments', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteTitle: () => 'Hello world',
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true,
|
||||
urlUtils: {
|
||||
isSiteUrl: () => false
|
||||
|
@ -207,4 +207,42 @@ describe('OutboundLinkTagger', function () {
|
|||
assert.equal(html, '<!-- comment -->');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getDomainFromUrl', function () {
|
||||
it('returns the base domain from a URL', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteUrl: () => 'https://blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const returnValue = await service.getDomainFromUrl(new URL('https://blog.com'));
|
||||
assert.equal(returnValue, 'blog.com');
|
||||
});
|
||||
|
||||
it('strips www. from URL if present and returns the base domain', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteUrl: () => 'https://www.blog.com',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const returnValue = await service.getDomainFromUrl(new URL('https://www.blog.com'));
|
||||
assert.equal(returnValue, 'blog.com');
|
||||
});
|
||||
|
||||
it('includes the subdomain from URL (excluding www) and returns the base domain', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteUrl: () => 'https://test.ghost.io',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const returnValue = await service.getDomainFromUrl(new URL('https://test.ghost.io'));
|
||||
assert.equal(returnValue, 'test.ghost.io');
|
||||
});
|
||||
|
||||
it('removes the path, if there is one', async function () {
|
||||
const service = new OutboundLinkTagger({
|
||||
getSiteUrl: () => 'https://test.ghost.io',
|
||||
isEnabled: () => true
|
||||
});
|
||||
const returnValue = await service.getDomainFromUrl(new URL('https://test.ghost.io/test'));
|
||||
assert.equal(returnValue, 'test.ghost.io');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue