ghost 5.74.2 → 5.74.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/{tryghost-adapter-cache-memory-ttl-5.74.2.tgz → tryghost-adapter-cache-memory-ttl-5.74.4.tgz} +0 -0
- package/components/{tryghost-adapter-cache-redis-5.74.2.tgz → tryghost-adapter-cache-redis-5.74.4.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.74.2.tgz → tryghost-adapter-manager-5.74.4.tgz} +0 -0
- package/components/{tryghost-announcement-bar-settings-5.74.2.tgz → tryghost-announcement-bar-settings-5.74.4.tgz} +0 -0
- package/components/{tryghost-api-framework-5.74.2.tgz → tryghost-api-framework-5.74.4.tgz} +0 -0
- package/components/tryghost-api-version-compatibility-service-5.74.4.tgz +0 -0
- package/components/{tryghost-audience-feedback-5.74.2.tgz → tryghost-audience-feedback-5.74.4.tgz} +0 -0
- package/components/tryghost-bookshelf-repository-5.74.4.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.74.4.tgz +0 -0
- package/components/tryghost-collections-5.74.4.tgz +0 -0
- package/components/{tryghost-constants-5.74.2.tgz → tryghost-constants-5.74.4.tgz} +0 -0
- package/components/tryghost-custom-theme-settings-service-5.74.4.tgz +0 -0
- package/components/{tryghost-data-generator-5.74.2.tgz → tryghost-data-generator-5.74.4.tgz} +0 -0
- package/components/tryghost-domain-events-5.74.4.tgz +0 -0
- package/components/tryghost-donations-5.74.4.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.74.4.tgz +0 -0
- package/components/tryghost-email-addresses-5.74.4.tgz +0 -0
- package/components/{tryghost-email-analytics-provider-mailgun-5.74.2.tgz → tryghost-email-analytics-provider-mailgun-5.74.4.tgz} +0 -0
- package/components/tryghost-email-analytics-service-5.74.4.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.74.2.tgz → tryghost-email-content-generator-5.74.4.tgz} +0 -0
- package/components/{tryghost-email-events-5.74.2.tgz → tryghost-email-events-5.74.4.tgz} +0 -0
- package/components/tryghost-email-service-5.74.4.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.74.4.tgz +0 -0
- package/components/{tryghost-event-aware-cache-wrapper-5.74.2.tgz → tryghost-event-aware-cache-wrapper-5.74.4.tgz} +0 -0
- package/components/{tryghost-express-dynamic-redirects-5.74.2.tgz → tryghost-express-dynamic-redirects-5.74.4.tgz} +0 -0
- package/components/{tryghost-external-media-inliner-5.74.2.tgz → tryghost-external-media-inliner-5.74.4.tgz} +0 -0
- package/components/tryghost-extract-api-key-5.74.4.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.74.2.tgz → tryghost-html-to-plaintext-5.74.4.tgz} +0 -0
- package/components/tryghost-i18n-5.74.4.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.74.4.tgz +0 -0
- package/components/{tryghost-importer-revue-5.74.2.tgz → tryghost-importer-revue-5.74.4.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.74.4.tgz +0 -0
- package/components/{tryghost-job-manager-5.74.2.tgz → tryghost-job-manager-5.74.4.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.74.2.tgz → tryghost-link-redirects-5.74.4.tgz} +0 -0
- package/components/tryghost-link-replacer-5.74.4.tgz +0 -0
- package/components/{tryghost-link-tracking-5.74.2.tgz → tryghost-link-tracking-5.74.4.tgz} +0 -0
- package/components/{tryghost-magic-link-5.74.2.tgz → tryghost-magic-link-5.74.4.tgz} +0 -0
- package/components/tryghost-mail-events-5.74.4.tgz +0 -0
- package/components/{tryghost-mailgun-client-5.74.2.tgz → tryghost-mailgun-client-5.74.4.tgz} +0 -0
- package/components/tryghost-member-attribution-5.74.4.tgz +0 -0
- package/components/{tryghost-member-events-5.74.2.tgz → tryghost-member-events-5.74.4.tgz} +0 -0
- package/components/{tryghost-members-api-5.74.2.tgz → tryghost-members-api-5.74.4.tgz} +0 -0
- package/components/tryghost-members-csv-5.74.4.tgz +0 -0
- package/components/tryghost-members-events-service-5.74.4.tgz +0 -0
- package/components/{tryghost-members-importer-5.74.2.tgz → tryghost-members-importer-5.74.4.tgz} +0 -0
- package/components/tryghost-members-offers-5.74.4.tgz +0 -0
- package/components/tryghost-members-payments-5.74.4.tgz +0 -0
- package/components/{tryghost-members-ssr-5.74.2.tgz → tryghost-members-ssr-5.74.4.tgz} +0 -0
- package/components/{tryghost-members-stripe-service-5.74.2.tgz → tryghost-members-stripe-service-5.74.4.tgz} +0 -0
- package/components/{tryghost-mentions-email-report-5.74.2.tgz → tryghost-mentions-email-report-5.74.4.tgz} +0 -0
- package/components/{tryghost-milestones-5.74.2.tgz → tryghost-milestones-5.74.4.tgz} +0 -0
- package/components/tryghost-minifier-5.74.4.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.74.4.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.74.4.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.74.4.tgz +0 -0
- package/components/{tryghost-mw-error-handler-5.74.2.tgz → tryghost-mw-error-handler-5.74.4.tgz} +0 -0
- package/components/tryghost-mw-session-from-token-5.74.4.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.74.4.tgz +0 -0
- package/components/tryghost-mw-version-match-5.74.4.tgz +0 -0
- package/components/tryghost-mw-vhost-5.74.4.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.74.4.tgz +0 -0
- package/components/tryghost-oembed-service-5.74.4.tgz +0 -0
- package/components/{tryghost-package-json-5.74.2.tgz → tryghost-package-json-5.74.4.tgz} +0 -0
- package/components/{tryghost-post-events-5.74.2.tgz → tryghost-post-events-5.74.4.tgz} +0 -0
- package/components/tryghost-post-revisions-5.74.4.tgz +0 -0
- package/components/{tryghost-posts-service-5.74.2.tgz → tryghost-posts-service-5.74.4.tgz} +0 -0
- package/components/tryghost-recommendations-5.74.4.tgz +0 -0
- package/components/{tryghost-referrers-5.74.2.tgz → tryghost-referrers-5.74.4.tgz} +0 -0
- package/components/{tryghost-security-5.74.2.tgz → tryghost-security-5.74.4.tgz} +0 -0
- package/components/tryghost-session-service-5.74.4.tgz +0 -0
- package/components/{tryghost-settings-path-manager-5.74.2.tgz → tryghost-settings-path-manager-5.74.4.tgz} +0 -0
- package/components/{tryghost-slack-notifications-5.74.2.tgz → tryghost-slack-notifications-5.74.4.tgz} +0 -0
- package/components/tryghost-staff-service-5.74.4.tgz +0 -0
- package/components/tryghost-stats-service-5.74.4.tgz +0 -0
- package/components/{tryghost-tiers-5.74.2.tgz → tryghost-tiers-5.74.4.tgz} +0 -0
- package/components/{tryghost-update-check-service-5.74.2.tgz → tryghost-update-check-service-5.74.4.tgz} +0 -0
- package/components/tryghost-verification-trigger-5.74.4.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.74.4.tgz +0 -0
- package/components/{tryghost-webmentions-5.74.2.tgz → tryghost-webmentions-5.74.4.tgz} +0 -0
- package/core/boot.js +4 -0
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +6 -0
- package/core/built/admin/assets/admin-x-demo/index-2fa5a3c2.mjs +8291 -0
- package/core/built/admin/assets/admin-x-demo/modals-10ef986a.mjs +381 -0
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-f1193dcf.mjs → CodeEditorView-b41ea5f8.mjs} +76 -76
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-114d5aab.mjs → index-bc03188e.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-89bf7b3f.mjs → index-e5bc288c.mjs} +8140 -7624
- package/core/built/admin/assets/admin-x-settings/{modals-045fb940.mjs → modals-98e015fa.mjs} +8005 -8080
- package/core/built/admin/assets/{chunk.761.5cfbe0d0f8ec7aefe7bc.js → chunk.137.0297fba124bc9ba1bbc1.js} +691 -686
- package/core/built/admin/assets/{chunk.143.a97e9374518a7e548084.js → chunk.143.0c31227118654150d49c.js} +5 -5
- package/core/built/admin/assets/{chunk.178.cce4259fa74902c8483a.js → chunk.178.b2467b9ba07dbbc9016a.js} +4 -4
- package/core/built/admin/assets/ghost-0456ea5c74bd1b27239c11706d1acae9.css +1 -0
- package/core/built/admin/assets/{ghost-ad9952b86c1d886c49a92339adf702e3.js → ghost-7d0f91cd17901ac15ce0ae64a3091e2a.js} +225 -194
- package/core/built/admin/assets/ghost-dark-29863e517e1917a3b0df74979a6cf03f.css +1 -0
- package/core/built/admin/assets/koenig-lexical/index.css +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +7878 -7857
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +119 -119
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/img_url.js +7 -97
- package/core/frontend/meta/image-dimensions.js +34 -6
- package/core/frontend/utils/images.js +100 -0
- package/core/server/api/endpoints/users.js +34 -5
- package/core/server/data/schema/schema.js +1 -1
- package/core/server/models/base/plugins/events.js +20 -1
- package/core/server/services/email-address/EmailAddressServiceWrapper.js +39 -0
- package/core/server/services/email-address/index.js +3 -0
- package/core/server/services/email-service/EmailServiceWrapper.js +2 -0
- package/core/server/services/mail/GhostMailer.js +46 -9
- package/core/server/services/members/service.js +8 -1
- package/core/server/services/newsletters/NewslettersService.js +52 -19
- package/core/server/services/newsletters/index.js +3 -1
- package/core/server/services/offers/OfferBookshelfRepository.js +8 -1
- package/core/server/services/settings/settings-service.js +4 -0
- package/core/server/services/settings-helpers/SettingsHelpers.js +71 -2
- package/core/server/services/settings-helpers/index.js +2 -1
- package/core/shared/config/overrides.json +2 -1
- package/core/shared/labs.js +5 -1
- package/core/shared/sentry.js +56 -33
- package/package.json +150 -149
- package/yarn.lock +700 -790
- package/components/tryghost-api-version-compatibility-service-5.74.2.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.74.2.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.74.2.tgz +0 -0
- package/components/tryghost-collections-5.74.2.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.74.2.tgz +0 -0
- package/components/tryghost-domain-events-5.74.2.tgz +0 -0
- package/components/tryghost-donations-5.74.2.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.74.2.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.74.2.tgz +0 -0
- package/components/tryghost-email-service-5.74.2.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.74.2.tgz +0 -0
- package/components/tryghost-extract-api-key-5.74.2.tgz +0 -0
- package/components/tryghost-i18n-5.74.2.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.74.2.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.74.2.tgz +0 -0
- package/components/tryghost-link-replacer-5.74.2.tgz +0 -0
- package/components/tryghost-mail-events-5.74.2.tgz +0 -0
- package/components/tryghost-member-attribution-5.74.2.tgz +0 -0
- package/components/tryghost-members-csv-5.74.2.tgz +0 -0
- package/components/tryghost-members-events-service-5.74.2.tgz +0 -0
- package/components/tryghost-members-offers-5.74.2.tgz +0 -0
- package/components/tryghost-members-payments-5.74.2.tgz +0 -0
- package/components/tryghost-minifier-5.74.2.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.74.2.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.74.2.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.74.2.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.74.2.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.74.2.tgz +0 -0
- package/components/tryghost-mw-version-match-5.74.2.tgz +0 -0
- package/components/tryghost-mw-vhost-5.74.2.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.74.2.tgz +0 -0
- package/components/tryghost-oembed-service-5.74.2.tgz +0 -0
- package/components/tryghost-post-revisions-5.74.2.tgz +0 -0
- package/components/tryghost-recommendations-5.74.2.tgz +0 -0
- package/components/tryghost-session-service-5.74.2.tgz +0 -0
- package/components/tryghost-staff-service-5.74.2.tgz +0 -0
- package/components/tryghost-stats-service-5.74.2.tgz +0 -0
- package/components/tryghost-verification-trigger-5.74.2.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.74.2.tgz +0 -0
- package/core/built/admin/assets/ghost-ad224505f7a80c242cc1272d1755995d.css +0 -1
- package/core/built/admin/assets/ghost-dark-81e3ee175ed67abaafca3fa228c620a3.css +0 -1
- /package/core/built/admin/assets/{chunk.761.5cfbe0d0f8ec7aefe7bc.js.LICENSE.txt → chunk.137.0297fba124bc9ba1bbc1.js.LICENSE.txt} +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.74%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.74%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22a5d777fdf2%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2254a22f23e1%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22f76ec8c678%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-0456ea5c74bd1b27239c11706d1acae9.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-f6856af9fcd8ab6533f2a4339ececbc8.js"></script>
|
|
60
|
-
<script src="assets/chunk.
|
|
61
|
-
<script src="assets/chunk.143.
|
|
62
|
-
<script src="assets/ghost-
|
|
60
|
+
<script src="assets/chunk.137.0297fba124bc9ba1bbc1.js"></script>
|
|
61
|
+
<script src="assets/chunk.143.0c31227118654150d49c.js"></script>
|
|
62
|
+
<script src="assets/ghost-7d0f91cd17901ac15ce0ae64a3091e2a.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -7,19 +7,21 @@
|
|
|
7
7
|
// Returns the URL for the current object scope i.e. If inside a post scope will return image permalink
|
|
8
8
|
// `absolute` flag outputs absolute URL, else URL is relative.
|
|
9
9
|
const {urlUtils} = require('../services/proxy');
|
|
10
|
+
const {
|
|
11
|
+
detectInternalImage,
|
|
12
|
+
getImageWithSize,
|
|
13
|
+
getUnsplashImage,
|
|
14
|
+
detectUnsplashImage
|
|
15
|
+
} = require('../utils/images');
|
|
10
16
|
|
|
11
|
-
const url = require('url');
|
|
12
17
|
const _ = require('lodash');
|
|
13
18
|
const logging = require('@tryghost/logging');
|
|
14
19
|
const tpl = require('@tryghost/tpl');
|
|
15
|
-
const imageTransform = require('@tryghost/image-transform');
|
|
16
20
|
|
|
17
21
|
const messages = {
|
|
18
22
|
attrIsRequired: 'Attribute is required e.g. {{img_url feature_image}}'
|
|
19
23
|
};
|
|
20
24
|
|
|
21
|
-
const STATIC_IMAGE_URL_PREFIX = `${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
|
|
22
|
-
|
|
23
25
|
module.exports = function imgUrl(requestedImageUrl, options) {
|
|
24
26
|
// CASE: if no url is passed, e.g. `{{img_url}}` we show a warning
|
|
25
27
|
if (arguments.length < 2) {
|
|
@@ -46,7 +48,7 @@ module.exports = function imgUrl(requestedImageUrl, options) {
|
|
|
46
48
|
|
|
47
49
|
if (!isInternalImage) {
|
|
48
50
|
// Detect Unsplash width and format
|
|
49
|
-
const isUnsplashImage =
|
|
51
|
+
const isUnsplashImage = detectUnsplashImage(requestedImageUrl);
|
|
50
52
|
if (isUnsplashImage) {
|
|
51
53
|
try {
|
|
52
54
|
return getUnsplashImage(requestedImageUrl, sizeOptions);
|
|
@@ -99,95 +101,3 @@ function getImageSizeOptions(options) {
|
|
|
99
101
|
requestedFormat
|
|
100
102
|
};
|
|
101
103
|
}
|
|
102
|
-
|
|
103
|
-
function detectInternalImage(requestedImageUrl) {
|
|
104
|
-
const siteUrl = urlUtils.getSiteUrl();
|
|
105
|
-
const isAbsoluteImage = /https?:\/\//.test(requestedImageUrl);
|
|
106
|
-
const isAbsoluteInternalImage = isAbsoluteImage && requestedImageUrl.startsWith(siteUrl);
|
|
107
|
-
|
|
108
|
-
// CASE: imagePath is a "protocol relative" url e.g. "//www.gravatar.com/ava..."
|
|
109
|
-
// by resolving the the imagePath relative to the blog url, we can then
|
|
110
|
-
// detect if the imagePath is external, or internal.
|
|
111
|
-
const isRelativeInternalImage = !isAbsoluteImage && url.resolve(siteUrl, requestedImageUrl).startsWith(siteUrl);
|
|
112
|
-
|
|
113
|
-
return isAbsoluteInternalImage || isRelativeInternalImage;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function getUnsplashImage(imagePath, sizeOptions) {
|
|
117
|
-
const parsedUrl = new URL(imagePath);
|
|
118
|
-
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
|
119
|
-
|
|
120
|
-
if (requestedFormat) {
|
|
121
|
-
const supportedFormats = ['avif', 'gif', 'jpg', 'png', 'webp'];
|
|
122
|
-
if (supportedFormats.includes(requestedFormat)) {
|
|
123
|
-
parsedUrl.searchParams.set('fm', requestedFormat);
|
|
124
|
-
} else if (requestedFormat === 'jpeg') {
|
|
125
|
-
// Map to alias
|
|
126
|
-
parsedUrl.searchParams.set('fm', 'jpg');
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (!imageSizes || !imageSizes[requestedSize]) {
|
|
131
|
-
return parsedUrl.toString();
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const {width, height} = imageSizes[requestedSize];
|
|
135
|
-
|
|
136
|
-
if (!width && !height) {
|
|
137
|
-
return parsedUrl.toString();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
parsedUrl.searchParams.delete('w');
|
|
141
|
-
parsedUrl.searchParams.delete('h');
|
|
142
|
-
|
|
143
|
-
if (width) {
|
|
144
|
-
parsedUrl.searchParams.set('w', width);
|
|
145
|
-
}
|
|
146
|
-
if (height) {
|
|
147
|
-
parsedUrl.searchParams.set('h', height);
|
|
148
|
-
}
|
|
149
|
-
return parsedUrl.toString();
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
*
|
|
154
|
-
* @param {string} imagePath
|
|
155
|
-
* @param {Object} sizeOptions
|
|
156
|
-
* @param {string} sizeOptions.requestedSize
|
|
157
|
-
* @param {Object[]} sizeOptions.imageSizes
|
|
158
|
-
* @param {string} [sizeOptions.requestedFormat]
|
|
159
|
-
* @returns
|
|
160
|
-
*/
|
|
161
|
-
function getImageWithSize(imagePath, sizeOptions) {
|
|
162
|
-
const hasLeadingSlash = imagePath[0] === '/';
|
|
163
|
-
|
|
164
|
-
if (hasLeadingSlash) {
|
|
165
|
-
return '/' + getImageWithSize(imagePath.slice(1), sizeOptions);
|
|
166
|
-
}
|
|
167
|
-
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
|
168
|
-
|
|
169
|
-
if (!requestedSize) {
|
|
170
|
-
return imagePath;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (!imageSizes || !imageSizes[requestedSize]) {
|
|
174
|
-
return imagePath;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const {width, height} = imageSizes[requestedSize];
|
|
178
|
-
|
|
179
|
-
if (!width && !height) {
|
|
180
|
-
return imagePath;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const [imgBlogUrl, imageName] = imagePath.split(STATIC_IMAGE_URL_PREFIX);
|
|
184
|
-
|
|
185
|
-
const sizeDirectoryName = prefixIfPresent('w', width) + prefixIfPresent('h', height);
|
|
186
|
-
const formatPrefix = requestedFormat && imageTransform.canTransformToFormat(requestedFormat) ? `/format/${requestedFormat}` : '';
|
|
187
|
-
|
|
188
|
-
return [imgBlogUrl, STATIC_IMAGE_URL_PREFIX, `/size/${sizeDirectoryName}`, formatPrefix, imageName].join('');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function prefixIfPresent(prefix, string) {
|
|
192
|
-
return string ? prefix + string : '';
|
|
193
|
-
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
+
const {getImageWithSize} = require('../utils/images');
|
|
3
|
+
const config = require('../../shared/config');
|
|
2
4
|
const imageSizeCache = require('../../server/lib/image').cachedImageSizeFromUrl;
|
|
3
5
|
|
|
4
6
|
/**
|
|
@@ -9,23 +11,28 @@ const imageSizeCache = require('../../server/lib/image').cachedImageSizeFromUrl;
|
|
|
9
11
|
* called to receive image width and height
|
|
10
12
|
*/
|
|
11
13
|
async function getImageDimensions(metaData) {
|
|
14
|
+
const MAX_SOCIAL_IMG_WIDTH = config.get('imageOptimization:internalImageSizes:social-image:width') || 1200;
|
|
15
|
+
|
|
12
16
|
const fetch = {
|
|
13
17
|
coverImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.coverImage.url),
|
|
14
18
|
authorImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.authorImage.url),
|
|
15
19
|
ogImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.ogImage.url),
|
|
20
|
+
twitterImage: imageSizeCache.getCachedImageSizeFromUrl(metaData.twitterImage),
|
|
16
21
|
logo: imageSizeCache.getCachedImageSizeFromUrl(metaData.site.logo.url)
|
|
17
22
|
};
|
|
18
23
|
|
|
19
|
-
const [coverImage, authorImage, ogImage, logo] = await Promise.all([
|
|
24
|
+
const [coverImage, authorImage, ogImage, twitterImage, logo] = await Promise.all([
|
|
20
25
|
fetch.coverImage,
|
|
21
26
|
fetch.authorImage,
|
|
22
27
|
fetch.ogImage,
|
|
28
|
+
fetch.twitterImage,
|
|
23
29
|
fetch.logo
|
|
24
30
|
]);
|
|
25
31
|
const imageObj = {
|
|
26
32
|
coverImage,
|
|
27
33
|
authorImage,
|
|
28
34
|
ogImage,
|
|
35
|
+
twitterImage,
|
|
29
36
|
logo
|
|
30
37
|
};
|
|
31
38
|
|
|
@@ -53,12 +60,33 @@ async function getImageDimensions(metaData) {
|
|
|
53
60
|
});
|
|
54
61
|
}
|
|
55
62
|
} else {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
if (key.width > MAX_SOCIAL_IMG_WIDTH) {
|
|
64
|
+
const ratio = key.height / key.width;
|
|
65
|
+
key.width = MAX_SOCIAL_IMG_WIDTH;
|
|
66
|
+
key.height = Math.round(MAX_SOCIAL_IMG_WIDTH * ratio);
|
|
67
|
+
|
|
68
|
+
const sizeOptions = {
|
|
69
|
+
requestedSize: `social-image`,
|
|
70
|
+
imageSizes: config.get('imageOptimization:internalImageSizes')
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (typeof metaData[value] === 'string') {
|
|
74
|
+
const url = getImageWithSize(metaData[value], sizeOptions);
|
|
75
|
+
metaData[value] = url;
|
|
76
|
+
} else {
|
|
77
|
+
const url = getImageWithSize(metaData[value].url, sizeOptions);
|
|
78
|
+
_.assign(metaData[value], {url});
|
|
60
79
|
}
|
|
61
|
-
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (typeof metaData[value] === 'object') {
|
|
83
|
+
_.assign(metaData[value], {
|
|
84
|
+
dimensions: {
|
|
85
|
+
width: key.width,
|
|
86
|
+
height: key.height
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
62
90
|
}
|
|
63
91
|
}
|
|
64
92
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const url = require('url');
|
|
2
|
+
const imageTransform = require('@tryghost/image-transform');
|
|
3
|
+
const urlUtils = require('../../shared/url-utils');
|
|
4
|
+
|
|
5
|
+
module.exports.detectInternalImage = function detectInternalImage(requestedImageUrl) {
|
|
6
|
+
const siteUrl = urlUtils.getSiteUrl();
|
|
7
|
+
const isAbsoluteImage = /https?:\/\//.test(requestedImageUrl);
|
|
8
|
+
const isAbsoluteInternalImage = isAbsoluteImage && requestedImageUrl.startsWith(siteUrl);
|
|
9
|
+
|
|
10
|
+
// CASE: imagePath is a "protocol relative" url e.g. "//www.gravatar.com/ava..."
|
|
11
|
+
// by resolving the the imagePath relative to the blog url, we can then
|
|
12
|
+
// detect if the imagePath is external, or internal.
|
|
13
|
+
const isRelativeInternalImage = !isAbsoluteImage && url.resolve(siteUrl, requestedImageUrl).startsWith(siteUrl);
|
|
14
|
+
|
|
15
|
+
return isAbsoluteInternalImage || isRelativeInternalImage;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
module.exports.detectUnsplashImage = function detectUnsplashImage(requestedImageUrl) {
|
|
19
|
+
const isUnsplashImage = /images\.unsplash\.com/.test(requestedImageUrl);
|
|
20
|
+
return isUnsplashImage;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
module.exports.getUnsplashImage = function getUnsplashImage(imagePath, sizeOptions) {
|
|
24
|
+
const parsedUrl = new URL(imagePath);
|
|
25
|
+
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
|
26
|
+
|
|
27
|
+
if (requestedFormat) {
|
|
28
|
+
const supportedFormats = ['avif', 'gif', 'jpg', 'png', 'webp'];
|
|
29
|
+
if (supportedFormats.includes(requestedFormat)) {
|
|
30
|
+
parsedUrl.searchParams.set('fm', requestedFormat);
|
|
31
|
+
} else if (requestedFormat === 'jpeg') {
|
|
32
|
+
// Map to alias
|
|
33
|
+
parsedUrl.searchParams.set('fm', 'jpg');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!imageSizes || !imageSizes[requestedSize]) {
|
|
38
|
+
return parsedUrl.toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const {width, height} = imageSizes[requestedSize];
|
|
42
|
+
|
|
43
|
+
if (!width && !height) {
|
|
44
|
+
return parsedUrl.toString();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
parsedUrl.searchParams.delete('w');
|
|
48
|
+
parsedUrl.searchParams.delete('h');
|
|
49
|
+
|
|
50
|
+
if (width) {
|
|
51
|
+
parsedUrl.searchParams.set('w', width);
|
|
52
|
+
}
|
|
53
|
+
if (height) {
|
|
54
|
+
parsedUrl.searchParams.set('h', height);
|
|
55
|
+
}
|
|
56
|
+
return parsedUrl.toString();
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
*
|
|
61
|
+
* @param {string} imagePath
|
|
62
|
+
* @param {Object} sizeOptions
|
|
63
|
+
* @param {string} sizeOptions.requestedSize
|
|
64
|
+
* @param {Object[]} sizeOptions.imageSizes
|
|
65
|
+
* @param {string} [sizeOptions.requestedFormat]
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
module.exports.getImageWithSize = function getImageWithSize(imagePath, sizeOptions) {
|
|
69
|
+
const hasLeadingSlash = imagePath[0] === '/';
|
|
70
|
+
|
|
71
|
+
if (hasLeadingSlash) {
|
|
72
|
+
return '/' + getImageWithSize(imagePath.slice(1), sizeOptions);
|
|
73
|
+
}
|
|
74
|
+
const {requestedSize, imageSizes, requestedFormat} = sizeOptions;
|
|
75
|
+
|
|
76
|
+
if (!requestedSize) {
|
|
77
|
+
return imagePath;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!imageSizes || !imageSizes[requestedSize]) {
|
|
81
|
+
return imagePath;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const {width, height} = imageSizes[requestedSize];
|
|
85
|
+
|
|
86
|
+
if (!width && !height) {
|
|
87
|
+
return imagePath;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const [imgBlogUrl, imageName] = imagePath.split(urlUtils.STATIC_IMAGE_URL_PREFIX);
|
|
91
|
+
|
|
92
|
+
const sizeDirectoryName = prefixIfPresent('w', width) + prefixIfPresent('h', height);
|
|
93
|
+
const formatPrefix = requestedFormat && imageTransform.canTransformToFormat(requestedFormat) ? `/format/${requestedFormat}` : '';
|
|
94
|
+
|
|
95
|
+
return [imgBlogUrl, urlUtils.STATIC_IMAGE_URL_PREFIX, `/size/${sizeDirectoryName}`, formatPrefix, imageName].join('');
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function prefixIfPresent(prefix, string) {
|
|
99
|
+
return string ? prefix + string : '';
|
|
100
|
+
}
|
|
@@ -40,6 +40,39 @@ async function fetchOrCreatePersonalToken(userId) {
|
|
|
40
40
|
return token;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function shouldInvalidateCacheAfterChange(model) {
|
|
44
|
+
// Model attributes that should trigger cache invalidation when changed
|
|
45
|
+
// (because they affect the frontend)
|
|
46
|
+
const publicAttrs = [
|
|
47
|
+
'name',
|
|
48
|
+
'slug',
|
|
49
|
+
'profile_image',
|
|
50
|
+
'cover_image',
|
|
51
|
+
'bio',
|
|
52
|
+
'website',
|
|
53
|
+
'location',
|
|
54
|
+
'facebook',
|
|
55
|
+
'twitter',
|
|
56
|
+
'status',
|
|
57
|
+
'visibility',
|
|
58
|
+
'meta_title',
|
|
59
|
+
'meta_description'
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
if (model.wasChanged() === false) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Check if any of the changed attributes are public
|
|
67
|
+
for (const attr of Object.keys(model._changed)) {
|
|
68
|
+
if (publicAttrs.includes(attr) === true) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
43
76
|
module.exports = {
|
|
44
77
|
docName: 'users',
|
|
45
78
|
|
|
@@ -137,11 +170,7 @@ module.exports = {
|
|
|
137
170
|
}));
|
|
138
171
|
}
|
|
139
172
|
|
|
140
|
-
|
|
141
|
-
this.headers.cacheInvalidate = true;
|
|
142
|
-
} else {
|
|
143
|
-
this.headers.cacheInvalidate = false;
|
|
144
|
-
}
|
|
173
|
+
this.headers.cacheInvalidate = shouldInvalidateCacheAfterChange(model);
|
|
145
174
|
|
|
146
175
|
return model;
|
|
147
176
|
});
|
|
@@ -17,7 +17,7 @@ module.exports = {
|
|
|
17
17
|
slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
|
|
18
18
|
sender_name: {type: 'string', maxlength: 191, nullable: true},
|
|
19
19
|
sender_email: {type: 'string', maxlength: 191, nullable: true},
|
|
20
|
-
sender_reply_to: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'newsletter'
|
|
20
|
+
sender_reply_to: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'newsletter'},
|
|
21
21
|
status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'active', validations: {isIn: [['active', 'archived']]}},
|
|
22
22
|
visibility: {
|
|
23
23
|
type: 'string',
|
|
@@ -7,6 +7,10 @@ const schema = require('../../../data/schema');
|
|
|
7
7
|
// This wires up our model event system
|
|
8
8
|
const events = require('../../../lib/common/events');
|
|
9
9
|
|
|
10
|
+
// Run tests or development with NUMERIC_IDS=1 to enable numeric object IDs
|
|
11
|
+
let forceNumericObjectIds = process.env.NODE_ENV !== 'production' && !!process.env.NUMERIC_IDS;
|
|
12
|
+
let numberGenerator = 0;
|
|
13
|
+
|
|
10
14
|
module.exports = function (Bookshelf) {
|
|
11
15
|
Bookshelf.Model = Bookshelf.Model.extend({
|
|
12
16
|
initializeEvents: function () {
|
|
@@ -39,7 +43,7 @@ module.exports = function (Bookshelf) {
|
|
|
39
43
|
* no auto increment
|
|
40
44
|
*/
|
|
41
45
|
setId: function setId() {
|
|
42
|
-
this.set('id',
|
|
46
|
+
this.set('id', Bookshelf.Model.generateId());
|
|
43
47
|
},
|
|
44
48
|
|
|
45
49
|
/**
|
|
@@ -268,5 +272,20 @@ module.exports = function (Bookshelf) {
|
|
|
268
272
|
|
|
269
273
|
this.addAction(model, 'deleted', options);
|
|
270
274
|
}
|
|
275
|
+
}, {
|
|
276
|
+
generateId: function generateId() {
|
|
277
|
+
if (forceNumericObjectIds) {
|
|
278
|
+
numberGenerator = numberGenerator + 1;
|
|
279
|
+
const counter = numberGenerator.toString();
|
|
280
|
+
|
|
281
|
+
// 77777777 here are to make sure generated ids's are larger than naturally generated ones
|
|
282
|
+
const base = '777777770000000000000000';
|
|
283
|
+
const id = base.substring(0, base.length - counter.length) + counter;
|
|
284
|
+
|
|
285
|
+
//// This always generates a valid object ID that is fully numeric
|
|
286
|
+
return id;
|
|
287
|
+
}
|
|
288
|
+
return ObjectId().toHexString();
|
|
289
|
+
}
|
|
271
290
|
});
|
|
272
291
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class EmailAddressServiceWrapper {
|
|
2
|
+
/**
|
|
3
|
+
* @type {import('@tryghost/email-addresses').EmailAddressService}
|
|
4
|
+
*/
|
|
5
|
+
service;
|
|
6
|
+
|
|
7
|
+
init() {
|
|
8
|
+
if (this.service) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const labs = require('../../../shared/labs');
|
|
13
|
+
const config = require('../../../shared/config');
|
|
14
|
+
const settingsHelpers = require('../settings-helpers');
|
|
15
|
+
const validator = require('@tryghost/validator');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
EmailAddressService
|
|
19
|
+
} = require('@tryghost/email-addresses');
|
|
20
|
+
|
|
21
|
+
this.service = new EmailAddressService({
|
|
22
|
+
labs,
|
|
23
|
+
getManagedEmailEnabled: () => {
|
|
24
|
+
return config.get('hostSettings:managedEmail:enabled') ?? false;
|
|
25
|
+
},
|
|
26
|
+
getSendingDomain: () => {
|
|
27
|
+
return config.get('hostSettings:managedEmail:sendingDomain') || null;
|
|
28
|
+
},
|
|
29
|
+
getDefaultEmail: () => {
|
|
30
|
+
return settingsHelpers.getDefaultEmail();
|
|
31
|
+
},
|
|
32
|
+
isValidEmailAddress: (emailAddress) => {
|
|
33
|
+
return validator.isEmail(emailAddress);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = EmailAddressServiceWrapper;
|
|
@@ -26,6 +26,7 @@ class EmailServiceWrapper {
|
|
|
26
26
|
const membersRepository = membersService.api.members;
|
|
27
27
|
const limitService = require('../limits');
|
|
28
28
|
const labs = require('../../../shared/labs');
|
|
29
|
+
const emailAddressService = require('../email-address');
|
|
29
30
|
|
|
30
31
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
31
32
|
const lexicalLib = require('../../lib/lexical');
|
|
@@ -70,6 +71,7 @@ class EmailServiceWrapper {
|
|
|
70
71
|
memberAttributionService: memberAttribution.service,
|
|
71
72
|
audienceFeedbackService: audienceFeedback.service,
|
|
72
73
|
outboundLinkTagger: memberAttribution.outboundLinkTagger,
|
|
74
|
+
emailAddressService: emailAddressService.service,
|
|
73
75
|
labs,
|
|
74
76
|
models: {Post}
|
|
75
77
|
});
|
|
@@ -8,6 +8,8 @@ const tpl = require('@tryghost/tpl');
|
|
|
8
8
|
const settingsCache = require('../../../shared/settings-cache');
|
|
9
9
|
const urlUtils = require('../../../shared/url-utils');
|
|
10
10
|
const metrics = require('@tryghost/metrics');
|
|
11
|
+
const settingsHelpers = require('../settings-helpers');
|
|
12
|
+
const emailAddress = require('../email-address');
|
|
11
13
|
const messages = {
|
|
12
14
|
title: 'Ghost at {domain}',
|
|
13
15
|
checkEmailConfigInstructions: 'Please see {url} for instructions on configuring email.',
|
|
@@ -16,29 +18,59 @@ const messages = {
|
|
|
16
18
|
reason: ' Reason: {reason}.',
|
|
17
19
|
messageSent: 'Message sent. Double check inbox and spam folder!'
|
|
18
20
|
};
|
|
21
|
+
const {EmailAddressParser} = require('@tryghost/email-addresses');
|
|
22
|
+
const logging = require('@tryghost/logging');
|
|
19
23
|
|
|
20
24
|
function getDomain() {
|
|
21
25
|
const domain = urlUtils.urlFor('home', true).match(new RegExp('^https?://([^/:?#]+)(?:[/:?#]|$)', 'i'));
|
|
22
26
|
return domain && domain[1];
|
|
23
27
|
}
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
/**
|
|
30
|
+
* @param {string} requestedFromAddress
|
|
31
|
+
* @param {string} requestedReplyToAddress
|
|
32
|
+
* @returns {{from: string, replyTo?: string|null}}
|
|
33
|
+
*/
|
|
34
|
+
function getFromAddress(requestedFromAddress, requestedReplyToAddress) {
|
|
35
|
+
if (settingsHelpers.useNewEmailAddresses()) {
|
|
36
|
+
if (!requestedFromAddress) {
|
|
37
|
+
// Use the default config
|
|
38
|
+
requestedFromAddress = emailAddress.service.defaultFromEmail;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Clean up email addresses (checks whether sending is allowed + email address is valid)
|
|
42
|
+
const addresses = emailAddress.service.getAddressFromString(requestedFromAddress, requestedReplyToAddress);
|
|
43
|
+
|
|
44
|
+
// fill in missing name if not set
|
|
45
|
+
const defaultSiteTitle = settingsCache.get('title') ? settingsCache.get('title') : tpl(messages.title, {domain: getDomain()});
|
|
46
|
+
if (!addresses.from.name) {
|
|
47
|
+
addresses.from.name = defaultSiteTitle;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
from: EmailAddressParser.stringify(addresses.from),
|
|
52
|
+
replyTo: addresses.replyTo ? EmailAddressParser.stringify(addresses.replyTo) : null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
26
55
|
const configAddress = config.get('mail') && config.get('mail').from;
|
|
27
56
|
|
|
28
57
|
const address = requestedFromAddress || configAddress;
|
|
29
58
|
// If we don't have a from address at all
|
|
30
59
|
if (!address) {
|
|
31
60
|
// Default to noreply@[blog.url]
|
|
32
|
-
return getFromAddress(`noreply@${getDomain()}
|
|
61
|
+
return getFromAddress(`noreply@${getDomain()}`, requestedReplyToAddress);
|
|
33
62
|
}
|
|
34
63
|
|
|
35
64
|
// If we do have a from address, and it's just an email
|
|
36
65
|
if (validator.isEmail(address, {require_tld: false})) {
|
|
37
66
|
const defaultSiteTitle = settingsCache.get('title') ? settingsCache.get('title').replace(/"/g, '\\"') : tpl(messages.title, {domain: getDomain()});
|
|
38
|
-
return
|
|
67
|
+
return {
|
|
68
|
+
from: `"${defaultSiteTitle}" <${address}>`
|
|
69
|
+
};
|
|
39
70
|
}
|
|
40
71
|
|
|
41
|
-
|
|
72
|
+
logging.warn(`Invalid from address used for sending emails: ${address}`);
|
|
73
|
+
return {from: address};
|
|
42
74
|
}
|
|
43
75
|
|
|
44
76
|
/**
|
|
@@ -47,16 +79,21 @@ function getFromAddress(requestedFromAddress) {
|
|
|
47
79
|
* @param {Object} message
|
|
48
80
|
* @param {boolean} [message.forceTextContent] - force text content
|
|
49
81
|
* @param {string} [message.from] - sender email address
|
|
82
|
+
* @param {string} [message.replyTo]
|
|
50
83
|
* @returns {Object}
|
|
51
84
|
*/
|
|
52
85
|
function createMessage(message) {
|
|
53
86
|
const encoding = 'base64';
|
|
54
87
|
const generateTextFromHTML = !message.forceTextContent;
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
|
|
89
|
+
const addresses = getFromAddress(message.from, message.replyTo);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
...message,
|
|
93
|
+
...addresses,
|
|
57
94
|
generateTextFromHTML,
|
|
58
95
|
encoding
|
|
59
|
-
}
|
|
96
|
+
};
|
|
60
97
|
}
|
|
61
98
|
|
|
62
99
|
function createMailError({message, err, ignoreDefaultMessage} = {message: ''}) {
|
|
@@ -154,13 +191,13 @@ module.exports = class GhostMailer {
|
|
|
154
191
|
return tpl(messages.messageSent);
|
|
155
192
|
}
|
|
156
193
|
|
|
157
|
-
if (response.pending.length > 0) {
|
|
194
|
+
if (response.pending && response.pending.length > 0) {
|
|
158
195
|
throw createMailError({
|
|
159
196
|
message: tpl(messages.reason, {reason: 'Email has been temporarily rejected'})
|
|
160
197
|
});
|
|
161
198
|
}
|
|
162
199
|
|
|
163
|
-
if (response.errors.length > 0) {
|
|
200
|
+
if (response.errors && response.errors.length > 0) {
|
|
164
201
|
throw createMailError({
|
|
165
202
|
message: tpl(messages.reason, {reason: response.errors[0].message})
|
|
166
203
|
});
|
|
@@ -89,7 +89,13 @@ const initVerificationTrigger = () => {
|
|
|
89
89
|
isVerificationRequired: () => settingsCache.get('email_verification_required') === true,
|
|
90
90
|
sendVerificationEmail: async ({subject, message, amountTriggered}) => {
|
|
91
91
|
const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
|
|
92
|
-
|
|
92
|
+
let fromAddress = config.get('user_email');
|
|
93
|
+
let replyTo = undefined;
|
|
94
|
+
|
|
95
|
+
if (settingsHelpers.useNewEmailAddresses()) {
|
|
96
|
+
replyTo = fromAddress;
|
|
97
|
+
fromAddress = settingsHelpers.getNoReplyAddress();
|
|
98
|
+
}
|
|
93
99
|
|
|
94
100
|
if (escalationAddress) {
|
|
95
101
|
await ghostMailer.send({
|
|
@@ -100,6 +106,7 @@ const initVerificationTrigger = () => {
|
|
|
100
106
|
}),
|
|
101
107
|
forceTextContent: true,
|
|
102
108
|
from: fromAddress,
|
|
109
|
+
replyTo,
|
|
103
110
|
to: escalationAddress
|
|
104
111
|
});
|
|
105
112
|
}
|