ghost 4.24.0 → 4.25.0

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.
Files changed (137) hide show
  1. package/.eslintrc.js +39 -0
  2. package/content/themes/casper/assets/built/casper.js +1 -1
  3. package/content/themes/casper/assets/built/casper.js.map +1 -1
  4. package/content/themes/casper/assets/built/global.css +1 -1
  5. package/content/themes/casper/assets/built/global.css.map +1 -1
  6. package/content/themes/casper/assets/built/screen.css +1 -1
  7. package/content/themes/casper/assets/built/screen.css.map +1 -1
  8. package/content/themes/casper/assets/css/global.css +6 -1
  9. package/content/themes/casper/assets/css/screen.css +32 -216
  10. package/content/themes/casper/default.hbs +2 -2
  11. package/content/themes/casper/package.json +3 -2
  12. package/content/themes/casper/post.hbs +1 -1
  13. package/content/themes/casper/yarn.lock +173 -123
  14. package/core/boot.js +2 -2
  15. package/core/built/assets/ghost-dark-d690e732e17ffc794e2e59c1467ca282.css +1 -0
  16. package/core/built/assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css +1 -0
  17. package/core/built/assets/{ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js → ghost.min-bc72f685c1c9adc9885925c1412435a5.js} +502 -577
  18. package/core/built/assets/icons/audio-upload.svg +8 -0
  19. package/core/built/assets/{vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js → vendor.min-d1234c632a54502777c34e50752fa3fc.js} +2598 -2137
  20. package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
  21. package/core/frontend/apps/amp/lib/views/amp.hbs +8 -0
  22. package/core/frontend/apps/private-blogging/index.js +1 -1
  23. package/core/frontend/services/apps/index.js +1 -1
  24. package/core/frontend/services/apps/loader.js +3 -3
  25. package/core/frontend/services/helpers/handlebars.js +1 -1
  26. package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
  27. package/core/frontend/services/theme-engine/middleware/index.js +6 -0
  28. package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
  29. package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
  30. package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
  31. package/core/frontend/src/cards/css/button.css +4 -0
  32. package/core/frontend/src/cards/css/callout.css +10 -10
  33. package/core/frontend/web/middleware/error-handler.js +93 -0
  34. package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
  35. package/core/frontend/web/middleware/index.js +1 -0
  36. package/core/frontend/web/site.js +1 -1
  37. package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
  38. package/core/server/adapters/storage/LocalStorageBase.js +2 -2
  39. package/core/server/api/canary/db.js +2 -2
  40. package/core/server/api/canary/media.js +3 -2
  41. package/core/server/api/canary/oembed.js +16 -1
  42. package/core/server/api/canary/session.js +1 -1
  43. package/core/server/api/canary/slugs.js +1 -1
  44. package/core/server/api/canary/utils/permissions.js +2 -2
  45. package/core/server/api/canary/utils/serializers/output/config.js +2 -6
  46. package/core/server/api/v2/db.js +2 -2
  47. package/core/server/api/v2/session.js +1 -1
  48. package/core/server/api/v2/slugs.js +1 -1
  49. package/core/server/api/v2/utils/permissions.js +2 -2
  50. package/core/server/api/v3/db.js +2 -2
  51. package/core/server/api/v3/session.js +1 -1
  52. package/core/server/api/v3/slugs.js +1 -1
  53. package/core/server/api/v3/utils/permissions.js +2 -2
  54. package/core/server/data/db/state-manager.js +4 -4
  55. package/core/server/data/exporter/export-filename.js +1 -1
  56. package/core/server/data/importer/handlers/json.js +1 -1
  57. package/core/server/data/importer/import-manager.js +1 -1
  58. package/core/server/data/importer/importers/data/base.js +1 -1
  59. package/core/server/data/migrations/utils.js +2 -2
  60. package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
  61. package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
  62. package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
  63. package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
  64. package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
  65. package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
  66. package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
  67. package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
  68. package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
  69. package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
  70. package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
  71. package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
  72. package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
  73. package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
  74. package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
  75. package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
  76. package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
  77. package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
  78. package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
  79. package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
  80. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
  81. package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +1 -0
  82. package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
  83. package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
  84. package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
  85. package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
  86. package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
  87. package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
  88. package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
  89. package/core/server/data/schema/commands.js +2 -2
  90. package/core/server/ghost-server.js +2 -2
  91. package/core/server/lib/image/image-size.js +2 -2
  92. package/core/server/models/base/listeners.js +2 -2
  93. package/core/server/models/member-email-change-event.js +2 -2
  94. package/core/server/models/member-login-event.js +2 -2
  95. package/core/server/models/member-paid-subscription-event.js +3 -3
  96. package/core/server/models/member-payment-event.js +3 -3
  97. package/core/server/models/member-product-event.js +6 -6
  98. package/core/server/models/member-status-event.js +5 -3
  99. package/core/server/models/member-subscribe-event.js +9 -3
  100. package/core/server/models/relations/authors.js +1 -1
  101. package/core/server/models/settings.js +1 -1
  102. package/core/server/services/auth/passwordreset.js +1 -1
  103. package/core/server/services/auth/setup.js +1 -1
  104. package/core/server/services/mega/mega.js +6 -4
  105. package/core/server/services/mega/template.js +12 -12
  106. package/core/server/services/members/api.js +22 -0
  107. package/core/server/services/members/config.js +1 -1
  108. package/core/server/services/members/emails/signup-paid.js +168 -0
  109. package/core/server/services/members/service.js +6 -2
  110. package/core/server/services/members/stripe-connect.js +4 -2
  111. package/core/server/services/nft-oembed.js +6 -1
  112. package/core/server/services/oembed.js +6 -2
  113. package/core/server/services/permissions/can-this.js +1 -1
  114. package/core/server/services/redirects/api.js +2 -2
  115. package/core/server/services/route-settings/default-settings-manager.js +1 -1
  116. package/core/server/services/route-settings/route-settings.js +4 -12
  117. package/core/server/services/route-settings/settings-loader.js +4 -4
  118. package/core/server/services/route-settings/yaml-parser.js +1 -1
  119. package/core/server/services/slack.js +1 -1
  120. package/core/server/services/themes/storage.js +2 -2
  121. package/core/server/services/twitter-embed.js +80 -0
  122. package/core/server/services/xmlrpc.js +2 -2
  123. package/core/server/web/admin/views/default-prod.html +4 -4
  124. package/core/server/web/admin/views/default.html +4 -4
  125. package/core/server/web/api/canary/admin/middleware.js +1 -1
  126. package/core/server/web/api/v2/admin/middleware.js +1 -1
  127. package/core/server/web/api/v3/admin/middleware.js +1 -1
  128. package/core/server/web/shared/middleware/error-handler.js +28 -150
  129. package/core/shared/config/defaults.json +7 -1
  130. package/core/shared/labs.js +3 -4
  131. package/core/shared/sentry.js +1 -1
  132. package/package.json +11 -11
  133. package/yarn.lock +426 -731
  134. package/content/themes/casper/assets/js/gallery-card.js +0 -24
  135. package/core/built/assets/ghost-dark-e7b57ab951512c5719aee89b16b9a448.css +0 -1
  136. package/core/built/assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css +0 -1
  137. package/core/frontend/services/theme-engine/middleware.js +0 -209
@@ -128,7 +128,7 @@ CanThisResult.prototype.beginCheck = function (context) {
128
128
  context = parseContext(context);
129
129
 
130
130
  if (actionsMap.empty()) {
131
- throw new errors.GhostError({message: tpl(messages.noActionsMapFoundError)});
131
+ throw new errors.InternalServerError({message: tpl(messages.noActionsMapFoundError)});
132
132
  }
133
133
 
134
134
  // Kick off loading of user permissions if necessary
@@ -34,7 +34,7 @@ const readRedirectsFile = async (redirectsPath) => {
34
34
  return '';
35
35
  }
36
36
 
37
- if (errors.utils.isIgnitionError(err)) {
37
+ if (errors.utils.isGhostError(err)) {
38
38
  throw err;
39
39
  }
40
40
 
@@ -162,7 +162,7 @@ class CustomRedirectsAPI {
162
162
  }
163
163
  }
164
164
  } catch (err) {
165
- if (errors.utils.isIgnitionError(err)) {
165
+ if (errors.utils.isGhostError(err)) {
166
166
  logging.error(err);
167
167
  } else {
168
168
  logging.error(new errors.IncorrectUsageError({
@@ -48,7 +48,7 @@ class DefaultSettingsManager {
48
48
  });
49
49
  }).catch((error) => {
50
50
  // CASE: we might have a permission error, as we can't access the directory
51
- throw new errors.GhostError({
51
+ throw new errors.InternalServerError({
52
52
  message: tpl(messages.ensureSettings, {
53
53
  path: this.destinationFolderPath
54
54
  }),
@@ -9,7 +9,7 @@ const tpl = require('@tryghost/tpl');
9
9
  const bridge = require('../../../bridge');
10
10
 
11
11
  const messages = {
12
- loadError: 'Could not load {filename} file.'
12
+ loadError: 'Could not load routes.yaml file.'
13
13
  };
14
14
 
15
15
  /**
@@ -38,14 +38,6 @@ class RouteSettings {
38
38
  * @private
39
39
  */
40
40
  this.defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
41
- /**
42
- * @private
43
- */
44
- this.filename = 'routes';
45
- /**
46
- * @private
47
- */
48
- this.ext = 'yaml';
49
41
 
50
42
  this.settingsLoader = settingsLoader;
51
43
  this.settingsPath = settingsPath;
@@ -90,7 +82,7 @@ class RouteSettings {
90
82
  return Promise.resolve([]);
91
83
  }
92
84
 
93
- if (errors.utils.isIgnitionError(err)) {
85
+ if (errors.utils.isGhostError(err)) {
94
86
  throw err;
95
87
  }
96
88
 
@@ -115,7 +107,7 @@ class RouteSettings {
115
107
  };
116
108
 
117
109
  try {
118
- bridge.reloadFrontend();
110
+ await bridge.reloadFrontend();
119
111
  } catch (err) {
120
112
  return bringBackValidRoutes()
121
113
  .finally(() => {
@@ -134,7 +126,7 @@ class RouteSettings {
134
126
  if (!urlService.hasFinished()) {
135
127
  if (tries > 5) {
136
128
  throw new errors.InternalServerError({
137
- message: tpl(messages.loadError, {filename: `${this.filename}.${this.ext}`})
129
+ message: tpl(messages.loadError)
138
130
  });
139
131
  }
140
132
 
@@ -36,11 +36,11 @@ class SettingsLoader {
36
36
 
37
37
  return validate(object);
38
38
  } catch (err) {
39
- if (errors.utils.isIgnitionError(err)) {
39
+ if (errors.utils.isGhostError(err)) {
40
40
  throw err;
41
41
  }
42
42
 
43
- throw new errors.GhostError({
43
+ throw new errors.InternalServerError({
44
44
  message: tpl(messages.settingsLoaderError, {
45
45
  setting: 'routes',
46
46
  path: this.settingFilePath
@@ -66,11 +66,11 @@ class SettingsLoader {
66
66
 
67
67
  return validate(object);
68
68
  } catch (err) {
69
- if (errors.utils.isIgnitionError(err)) {
69
+ if (errors.utils.isGhostError(err)) {
70
70
  throw err;
71
71
  }
72
72
 
73
- throw new errors.GhostError({
73
+ throw new errors.InternalServerError({
74
74
  message: tpl(messages.settingsLoaderError, {
75
75
  setting: 'routes',
76
76
  path: this.settingFilePath
@@ -35,7 +35,7 @@ module.exports = function parseYaml(file) {
35
35
 
36
36
  return parsed;
37
37
  } catch (error) {
38
- if (errors.utils.isIgnitionError(error)) {
38
+ if (errors.utils.isGhostError(error)) {
39
39
  throw error;
40
40
  }
41
41
 
@@ -136,7 +136,7 @@ function ping(post) {
136
136
  'Content-type': 'application/json'
137
137
  }
138
138
  }).catch(function (err) {
139
- logging.error(new errors.GhostError({
139
+ logging.error(new errors.InternalServerError({
140
140
  err: err,
141
141
  context: tpl(messages.requestFailedError, {service: 'slack'}),
142
142
  help: tpl(messages.requestFailedHelp, {url: 'https://ghost.org/docs/'})
@@ -112,7 +112,7 @@ module.exports = {
112
112
  if (checkedTheme) {
113
113
  fs.remove(checkedTheme.path)
114
114
  .catch((err) => {
115
- logging.error(new errors.GhostError({err: err}));
115
+ logging.error(new errors.InternalServerError({err: err}));
116
116
  });
117
117
  }
118
118
 
@@ -120,7 +120,7 @@ module.exports = {
120
120
  getStorage()
121
121
  .delete(backupName)
122
122
  .catch((err) => {
123
- logging.error(new errors.GhostError({err: err}));
123
+ logging.error(new errors.InternalServerError({err: err}));
124
124
  });
125
125
  }
126
126
  },
@@ -0,0 +1,80 @@
1
+ const {extract} = require('oembed-parser');
2
+
3
+ /**
4
+ * @typedef {import('./oembed').ICustomProvider} ICustomProvider
5
+ * @typedef {import('./oembed').IExternalRequest} IExternalRequest
6
+ */
7
+
8
+ const TWITTER_PATH_REGEX = /\/status\/(\d+)/;
9
+
10
+ /**
11
+ * @implements ICustomProvider
12
+ */
13
+ class TwitterOEmbedProvider {
14
+ /**
15
+ * @param {object} dependencies
16
+ */
17
+ constructor(dependencies) {
18
+ this.dependencies = dependencies;
19
+ }
20
+
21
+ /**
22
+ * @param {URL} url
23
+ * @returns {Promise<boolean>}
24
+ */
25
+ async canSupportRequest(url) {
26
+ return url.host === 'twitter.com' && TWITTER_PATH_REGEX.test(url.pathname);
27
+ }
28
+
29
+ /**
30
+ * @param {URL} url
31
+ * @param {IExternalRequest} externalRequest
32
+ *
33
+ * @returns {Promise<object>}
34
+ */
35
+ async getOEmbedData(url, externalRequest) {
36
+ const [match, tweetId] = url.pathname.match(TWITTER_PATH_REGEX);
37
+ if (!match) {
38
+ return null;
39
+ }
40
+
41
+ /** @type {object} */
42
+ const oembedData = await extract(url.href);
43
+
44
+ if (this.dependencies.config.bearerToken) {
45
+ const query = {
46
+ expansions: ['attachments.poll_ids', 'attachments.media_keys', 'author_id', 'entities.mentions.username', 'geo.place_id', 'in_reply_to_user_id', 'referenced_tweets.id', 'referenced_tweets.id.author_id'],
47
+ 'media.fields': ['duration_ms', 'height', 'media_key', 'preview_image_url', 'type', 'url', 'width', 'public_metrics', 'alt_text'],
48
+ 'place.fields': ['contained_within', 'country', 'country_code', 'full_name', 'geo', 'id', 'name', 'place_type'],
49
+ 'poll.fields': ['duration_minutes', 'end_datetime', 'id', 'options', 'voting_status'],
50
+ 'tweet.fields': ['attachments', 'author_id', 'context_annotations', 'conversation_id', 'created_at', 'entities', 'geo', 'id', 'in_reply_to_user_id', 'lang', 'public_metrics', 'possibly_sensitive', 'referenced_tweets', 'reply_settings', 'source', 'text', 'withheld'],
51
+ 'user.fields': ['created_at', 'description', 'entities', 'id', 'location', 'name', 'pinned_tweet_id', 'profile_image_url', 'protected', 'public_metrics', 'url', 'username', 'verified', 'withheld']
52
+ };
53
+
54
+ const queryString = Object.keys(query).map((key) => {
55
+ return `${key}=${query[key].join(',')}`;
56
+ }).join('&');
57
+
58
+ try {
59
+ const result = await externalRequest(`https://api.twitter.com/2/tweets/${tweetId}?${queryString}`, {
60
+ responseType: 'json',
61
+ headers: {
62
+ Authorization: `Bearer ${this.dependencies.config.bearerToken}`
63
+ }
64
+ });
65
+
66
+ const body = JSON.parse(result.body);
67
+
68
+ oembedData.tweet_data = body.data;
69
+ } catch (err) {
70
+ this.dependencies.logging.error(err);
71
+ }
72
+ }
73
+
74
+ oembedData.type = 'twitter';
75
+
76
+ return oembedData;
77
+ }
78
+ }
79
+
80
+ module.exports = TwitterOEmbedProvider;
@@ -87,7 +87,7 @@ function ping(post) {
87
87
  if (!goodResponse.test(res.body)) {
88
88
  const matches = res.body.match(errorMessage);
89
89
  const message = matches ? matches[1] : res.body;
90
- throw new errors.GhostError({message});
90
+ throw new errors.InternalServerError({message});
91
91
  }
92
92
  })
93
93
  .catch(function (err) {
@@ -100,7 +100,7 @@ function ping(post) {
100
100
  help: tpl(messages.requestFailedHelp, {url: 'https://ghost.org/docs/'})
101
101
  });
102
102
  } else {
103
- error = new errors.GhostError({
103
+ error = new errors.InternalServerError({
104
104
  err: err,
105
105
  message: err.message,
106
106
  context: tpl(messages.requestFailedError, {service: 'xmlrpc'}),
@@ -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%22rootURL%22%3A%22%2F%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%224.24%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%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%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%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%224.25%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%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%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -41,7 +41,7 @@
41
41
 
42
42
 
43
43
  <link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
44
- <link rel="stylesheet" href="assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css" title="light">
44
+ <link rel="stylesheet" href="assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css" title="light">
45
45
 
46
46
 
47
47
 
@@ -59,8 +59,8 @@
59
59
  <div id="ember-basic-dropdown-wormhole"></div>
60
60
 
61
61
 
62
- <script src="assets/vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js"></script>
63
- <script src="assets/ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js"></script>
62
+ <script src="assets/vendor.min-d1234c632a54502777c34e50752fa3fc.js"></script>
63
+ <script src="assets/ghost.min-bc72f685c1c9adc9885925c1412435a5.js"></script>
64
64
 
65
65
  </body>
66
66
  </html>
@@ -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%22rootURL%22%3A%22%2F%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%224.24%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%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%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%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%224.25%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22emberKeyboard%22%3A%7B%22disableInputsInitializer%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%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -41,7 +41,7 @@
41
41
 
42
42
 
43
43
  <link rel="stylesheet" href="assets/vendor.min-987af30228885bce50f05c4723fe6f53.css">
44
- <link rel="stylesheet" href="assets/ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css" title="light">
44
+ <link rel="stylesheet" href="assets/ghost.min-043bb7480a0810109b130f13b2a4235e.css" title="light">
45
45
 
46
46
 
47
47
 
@@ -59,8 +59,8 @@
59
59
  <div id="ember-basic-dropdown-wormhole"></div>
60
60
 
61
61
 
62
- <script src="assets/vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js"></script>
63
- <script src="assets/ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js"></script>
62
+ <script src="assets/vendor.min-d1234c632a54502777c34e50752fa3fc.js"></script>
63
+ <script src="assets/ghost.min-bc72f685c1c9adc9885925c1412435a5.js"></script>
64
64
 
65
65
  </body>
66
66
  </html>
@@ -44,7 +44,7 @@ const notImplemented = function (req, res, next) {
44
44
  }
45
45
  }
46
46
 
47
- next(new errors.GhostError({
47
+ next(new errors.InternalServerError({
48
48
  errorType: 'NotImplementedError',
49
49
  message: tpl(messages.notImplemented),
50
50
  statusCode: '501'
@@ -40,7 +40,7 @@ const notImplemented = function (req, res, next) {
40
40
  }
41
41
  }
42
42
 
43
- next(new errors.GhostError({
43
+ next(new errors.InternalServerError({
44
44
  errorType: 'NotImplementedError',
45
45
  message: tpl(messages.notImplemented),
46
46
  statusCode: '501'
@@ -44,7 +44,7 @@ const notImplemented = function (req, res, next) {
44
44
  }
45
45
  }
46
46
 
47
- next(new errors.GhostError({
47
+ next(new errors.InternalServerError({
48
48
  errorType: 'NotImplementedError',
49
49
  message: tpl(messages.notImplemented),
50
50
  statusCode: '501'
@@ -1,16 +1,10 @@
1
- const hbs = require('express-hbs');
2
1
  const _ = require('lodash');
3
2
  const debug = require('@tryghost/debug')('error-handler');
4
3
  const errors = require('@tryghost/errors');
5
4
  const tpl = require('@tryghost/tpl');
6
- const config = require('../../../../shared/config');
7
- const helpers = require('../../../../frontend/services/routing/helpers');
8
5
  const sentry = require('../../../../shared/sentry');
9
6
 
10
7
  const messages = {
11
- oopsErrorTemplateHasError: 'Oops, seems there is an error in the error template.',
12
- encounteredError: 'Encountered the error: ',
13
- whilstTryingToRender: 'whilst trying to render an error page for the error: ',
14
8
  pageNotFound: 'Page not found',
15
9
  resourceNotFound: 'Resource not found',
16
10
  actions: {
@@ -45,22 +39,7 @@ const messages = {
45
39
  }
46
40
  };
47
41
 
48
- const escapeExpression = hbs.Utils.escapeExpression;
49
- const _private = {};
50
- const errorHandler = {};
51
-
52
- /**
53
- * This is a bare minimum setup, which allows us to render the error page
54
- * It uses the {{asset}} helper, and nothing more
55
- */
56
- _private.createHbsEngine = () => {
57
- const engine = hbs.create();
58
- engine.registerHelper('asset', require('../../../../frontend/helpers/asset'));
59
-
60
- return engine.express4();
61
- };
62
-
63
- _private.updateStack = (err) => {
42
+ const updateStack = (err) => {
64
43
  let stackbits = err.stack.split(/\n/g);
65
44
 
66
45
  // We build this up backwards, so we always insert at position 1
@@ -88,25 +67,30 @@ _private.updateStack = (err) => {
88
67
 
89
68
  /**
90
69
  * Get an error ready to be shown the the user
91
- *
92
- * @TODO: support multiple errors within one single error, see https://github.com/TryGhost/Ghost/issues/7116#issuecomment-252231809
93
70
  */
94
- _private.prepareError = (err, req, res, next) => {
71
+ module.exports.prepareError = (err, req, res, next) => {
95
72
  debug(err);
96
73
 
97
74
  if (Array.isArray(err)) {
98
75
  err = err[0];
99
76
  }
100
77
 
101
- if (!errors.utils.isIgnitionError(err)) {
78
+ if (!errors.utils.isGhostError(err)) {
102
79
  // We need a special case for 404 errors
103
- // @TODO look at adding this to the GhostError class
104
80
  if (err.statusCode && err.statusCode === 404) {
105
81
  err = new errors.NotFoundError({
106
82
  err: err
107
83
  });
84
+ } else if (err.stack.match(/node_modules\/handlebars\//)) {
85
+ // Temporary handling of theme errors from handlebars
86
+ // @TODO remove this when #10496 is solved properly
87
+ err = new errors.IncorrectUsageError({
88
+ err: err,
89
+ message: err.message,
90
+ statusCode: err.statusCode
91
+ });
108
92
  } else {
109
- err = new errors.GhostError({
93
+ err = new errors.InternalServerError({
110
94
  err: err,
111
95
  message: err.message,
112
96
  statusCode: err.statusCode
@@ -120,7 +104,7 @@ _private.prepareError = (err, req, res, next) => {
120
104
  // alternative for res.status();
121
105
  res.statusCode = err.statusCode;
122
106
 
123
- err.stack = _private.updateStack(err);
107
+ err.stack = updateStack(err);
124
108
 
125
109
  // never cache errors
126
110
  res.set({
@@ -130,7 +114,7 @@ _private.prepareError = (err, req, res, next) => {
130
114
  next(err);
131
115
  };
132
116
 
133
- _private.JSONErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
117
+ const jsonErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
134
118
  res.json({
135
119
  errors: [{
136
120
  message: err.message,
@@ -143,8 +127,8 @@ _private.JSONErrorRenderer = (err, req, res, next) => { // eslint-disable-line n
143
127
  });
144
128
  };
145
129
 
146
- _private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
147
- const userError = _private.prepareUserMessage(err, req);
130
+ const jsonErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line no-unused-vars
131
+ const userError = prepareUserMessage(err, req);
148
132
 
149
133
  res.json({
150
134
  errors: [{
@@ -160,7 +144,7 @@ _private.JSONErrorRendererV2 = (err, req, res, next) => { // eslint-disable-line
160
144
  });
161
145
  };
162
146
 
163
- _private.prepareUserMessage = (err, res) => {
147
+ const prepareUserMessage = (err, res) => {
164
148
  const userError = {
165
149
  message: err.message,
166
150
  context: err.context
@@ -206,141 +190,35 @@ _private.prepareUserMessage = (err, res) => {
206
190
  return userError;
207
191
  };
208
192
 
209
- _private.ErrorFallbackMessage = err => `<h1>${tpl(messages.oopsErrorTemplateHasError)}</h1>
210
- <p>${tpl(messages.encounteredError)}</p>
211
- <pre>${escapeExpression(err.message || err)}</pre>
212
- <br ><p>${tpl(messages.whilstTryingToRender)}</p>
213
- ${err.statusCode} <pre>${escapeExpression(err.message || err)}</pre>`;
214
-
215
- _private.ThemeErrorRenderer = (err, req, res, next) => {
216
- // If the error code is explicitly set to STATIC_FILE_NOT_FOUND,
217
- // Skip trying to render an HTML error, and move on to the basic error renderer
218
- // We do this because customised 404 templates could reference the image that's missing
219
- // A better long term solution might be to do this based on extension
220
- if (err.code === 'STATIC_FILE_NOT_FOUND') {
221
- return next(err);
222
- }
223
-
224
- // Renderer begin
225
- // Format Data
226
- const data = {
227
- message: err.message,
228
- // @deprecated Remove in Ghost 5.0
229
- code: err.statusCode,
230
- statusCode: err.statusCode,
231
- errorDetails: err.errorDetails || []
232
- };
233
-
234
- // Template
235
- // @TODO: very dirty !!!!!!
236
- helpers.templates.setTemplate(req, res);
237
-
238
- // It can be that something went wrong with the theme or otherwise loading handlebars
239
- // This ensures that no matter what res.render will work here
240
- // @TODO: split the error handler for assets, admin & theme to refactor this away
241
- if (_.isEmpty(req.app.engines)) {
242
- res._template = 'error';
243
- req.app.engine('hbs', _private.createHbsEngine());
244
- req.app.set('view engine', 'hbs');
245
- req.app.set('views', config.get('paths').defaultViews);
246
- }
247
-
248
- // @TODO use renderer here?!
249
- // Render Call - featuring an error handler for what happens if rendering fails
250
- res.render(res._template, data, (_err, html) => {
251
- if (!_err) {
252
- return res.send(html);
253
- }
254
-
255
- // re-attach new error e.g. error template has syntax error or misusage
256
- req.err = _err;
257
-
258
- // And then try to explain things to the user...
259
- // Cheat and output the error using handlebars escapeExpression
260
- return res.status(500).send(_private.ErrorFallbackMessage(_err));
261
- });
262
- };
263
-
264
- /**
265
- * Borrowed heavily from finalHandler
266
- */
267
-
268
- const DOUBLE_SPACE_REGEXP = /\x20{2}/g;
269
- const NEWLINE_REGEXP = /\n/g;
270
-
271
- function createHtmlDocument(status, message) {
272
- let body = escapeExpression(message)
273
- .replace(NEWLINE_REGEXP, '<br>')
274
- .replace(DOUBLE_SPACE_REGEXP, ' &nbsp;');
275
-
276
- return `<!DOCTYPE html>\n
277
- <html lang="en">\n
278
- <head>\n
279
- <meta charset="utf-8">\n
280
- <title>${status} Error</title>\n
281
- </head>\n
282
- <body>\n
283
- <pre>${status} ${body}</pre>\n
284
- </body>\n
285
- </html>\n`;
286
- }
287
-
288
- _private.HTMLErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
289
- return res.send(createHtmlDocument(res.statusCode, err.stack));
290
- };
291
-
292
- _private.BasicErrorRenderer = (err, req, res, next) => { // eslint-disable-line no-unused-vars
293
- return res.send(res.statusCode + ' ' + err.stack);
294
- };
295
-
296
- errorHandler.resourceNotFound = (req, res, next) => {
297
- // TODO, handle unknown resources & methods differently, so that we can also produce
298
- // 405 Method Not Allowed
193
+ module.exports.resourceNotFound = (req, res, next) => {
299
194
  next(new errors.NotFoundError({message: tpl(messages.resourceNotFound)}));
300
195
  };
301
196
 
302
- errorHandler.pageNotFound = (req, res, next) => {
197
+ module.exports.pageNotFound = (req, res, next) => {
303
198
  next(new errors.NotFoundError({message: tpl(messages.pageNotFound)}));
304
199
  };
305
200
 
306
- errorHandler.handleJSONResponse = [
201
+ module.exports.handleJSONResponse = [
307
202
  // Make sure the error can be served
308
- _private.prepareError,
203
+ module.exports.prepareError,
309
204
  // Handle the error in Sentry
310
205
  sentry.errorHandler,
311
206
  // Render the error using JSON format
312
- _private.JSONErrorRenderer
207
+ jsonErrorRenderer
313
208
  ];
314
209
 
315
- errorHandler.handleJSONResponseV2 = [
210
+ module.exports.handleJSONResponseV2 = [
316
211
  // Make sure the error can be served
317
- _private.prepareError,
212
+ module.exports.prepareError,
318
213
  // Handle the error in Sentry
319
214
  sentry.errorHandler,
320
215
  // Render the error using JSON format
321
- _private.JSONErrorRendererV2
216
+ jsonErrorRendererV2
322
217
  ];
323
218
 
324
- errorHandler.handleHTMLResponse = [
219
+ module.exports.handleHTMLResponse = [
325
220
  // Make sure the error can be served
326
- _private.prepareError,
221
+ module.exports.prepareError,
327
222
  // Handle the error in Sentry
328
- sentry.errorHandler,
329
- // Render the error using HTML format
330
- _private.HTMLErrorRenderer,
331
- // Fall back to basic if HTML is not explicitly accepted
332
- _private.BasicErrorRenderer
223
+ sentry.errorHandler
333
224
  ];
334
-
335
- errorHandler.handleThemeResponse = [
336
- // Make sure the error can be served
337
- _private.prepareError,
338
- // Handle the error in Sentry
339
- sentry.errorHandler,
340
- // Render the error using theme template
341
- _private.ThemeErrorRenderer,
342
- // Fall back to basic if HTML is not explicitly accepted
343
- _private.BasicErrorRenderer
344
- ];
345
-
346
- module.exports = errorHandler;
@@ -131,7 +131,13 @@
131
131
  "version": "1.12"
132
132
  },
133
133
  "tenor": {
134
- "apiKey": null,
134
+ "publicReadOnlyApiKey": null,
135
135
  "contentFilter": "off"
136
+ },
137
+ "opensea": {
138
+ "privateReadOnlyApiKey": null
139
+ },
140
+ "twitter": {
141
+ "privateReadOnlyToken": null
136
142
  }
137
143
  }
@@ -15,7 +15,8 @@ const messages = {
15
15
 
16
16
  // flags in this list always return `true`, allows quick global enable prior to full flag removal
17
17
  const GA_FEATURES = [
18
- 'customThemeSettings'
18
+ 'customThemeSettings',
19
+ 'nftCard'
19
20
  ];
20
21
 
21
22
  // NOTE: this allowlist is meant to be used to filter out any unexpected
@@ -33,11 +34,8 @@ const ALPHA_FEATURES = [
33
34
  'mediaAPI',
34
35
  'filesAPI',
35
36
  'membersAutoLogin',
36
- 'buttonCard',
37
37
  'calloutCard',
38
- 'nftCard',
39
38
  'accordionCard',
40
- 'gifsCard',
41
39
  'fileCard',
42
40
  'audioCard',
43
41
  'videoCard',
@@ -107,6 +105,7 @@ module.exports.enabledHelper = function enabledHelper(options, callback) {
107
105
  });
108
106
  errDetails.help = tpl(options.errorHelp || messages.errorHelp, {url: options.helpUrl});
109
107
 
108
+ // eslint-disable-next-line no-restricted-syntax
110
109
  logging.error(new errors.DisabledFeatureError(errDetails));
111
110
 
112
111
  const {SafeString} = require('express-hbs');