Skip to content
This repository was archived by the owner on Aug 8, 2023. It is now read-only.

Commit 7a1008b

Browse files
committed
Add v2.7.0
1 parent 78dd55e commit 7a1008b

31 files changed

+226
-111
lines changed

core/built/assets/ghost-dark-2503e67d91cc80999d1ac53a90bf2eb1.css core/built/assets/ghost-dark-6884e3b1bf79cfe16cac7e31c066890f.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/built/assets/ghost.min-1b13c62f5c1d56fd1d1f0ebc951a3d26.css core/built/assets/ghost.min-60c6f40170e0422153cd017eb77d10a7.css

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/built/assets/ghost.min-2faf9f5c52906b91112aade6dd90ca58.js core/built/assets/ghost.min-96bc2e85ece023413226dfd3e4856109.js

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/built/assets/icons/store.svg

+1-1
Loading

core/built/assets/img/mailchimp.svg

+1
Loading
Loading
Loading
Loading
Loading
Loading
Loading

core/server/api/v2/configuration.js

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
const Promise = require('bluebird');
2+
const {isPlainObject} = require('lodash');
3+
const urlService = require('../../services/url');
4+
const models = require('../../models');
5+
const config = require('../../config');
6+
const labs = require('../../services/labs');
7+
const settingsCache = require('../../services/settings/cache');
8+
const ghostVersion = require('../../lib/ghost-version');
9+
10+
function fetchAvailableTimezones() {
11+
const timezones = require('../../data/timezones.json');
12+
return timezones;
13+
}
14+
15+
function getAboutConfig() {
16+
return {
17+
version: ghostVersion.full,
18+
environment: config.get('env'),
19+
database: config.get('database').client,
20+
mail: isPlainObject(config.get('mail')) ? config.get('mail').transport : ''
21+
};
22+
}
23+
24+
function getBaseConfig() {
25+
return {
26+
useGravatar: !config.isPrivacyDisabled('useGravatar'),
27+
publicAPI: labs.isSet('publicAPI'),
28+
blogUrl: urlService.utils.urlFor('home', true),
29+
blogTitle: settingsCache.get('title'),
30+
clientExtensions: config.get('clientExtensions'),
31+
enableDeveloperExperiments: config.get('enableDeveloperExperiments')
32+
};
33+
}
34+
35+
module.exports = {
36+
docName: 'configuration',
37+
read: {
38+
permissions: false,
39+
data: [
40+
'key'
41+
],
42+
query({data}) {
43+
if (!data.key) {
44+
return models.Client.findOne({slug: 'ghost-admin'})
45+
.then((ghostAdmin) => {
46+
const configuration = getBaseConfig();
47+
48+
configuration.clientId = ghostAdmin.get('slug');
49+
configuration.clientSecret = ghostAdmin.get('secret');
50+
51+
return configuration;
52+
});
53+
}
54+
55+
if (data.key === 'about') {
56+
return Promise.resolve(getAboutConfig());
57+
}
58+
59+
// Timezone endpoint
60+
if (data.key === 'timezones') {
61+
return Promise.resolve(fetchAvailableTimezones());
62+
}
63+
64+
return Promise.resolve();
65+
}
66+
}
67+
};

core/server/api/v2/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,9 @@ module.exports = {
8181

8282
get authors() {
8383
return shared.pipeline(require('./authors'), localUtils);
84+
},
85+
86+
get configuration() {
87+
return shared.pipeline(require('./configuration'), localUtils);
8488
}
8589
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:configuration');
2+
3+
module.exports = {
4+
all(configuration, apiConfig, frame) {
5+
frame.response = {
6+
configuration: configuration ? [configuration] : []
7+
};
8+
9+
debug(frame.response);
10+
}
11+
};

core/server/api/v2/utils/serializers/output/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,9 @@ module.exports = {
6565

6666
get authors() {
6767
return require('./authors');
68+
},
69+
70+
get configuration() {
71+
return require('./configuration');
6872
}
6973
};

core/server/data/importer/importers/data/base.js

+5
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ class Base {
189189

190190
// CASE: you import null, fallback to owner
191191
if (!obj[key]) {
192+
// Exception: If the imported post is a draft published_by will be null. Not a userReferenceProblem.
193+
if (key === 'published_by' && obj.status === 'draft') {
194+
return;
195+
}
196+
192197
if (!userReferenceProblems[obj.id]) {
193198
userReferenceProblems[obj.id] = {obj: _.cloneDeep(obj), keys: []};
194199
}

core/server/lib/fs/read-csv.js

-5
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ module.exports = function readCSV(options) {
2626
result[columnsToExtract[0].name] = value[headers[0]];
2727
return result;
2828
});
29-
30-
// Add first row
31-
result = {};
32-
result[columnsToExtract[0].name] = headers[0];
33-
results = [result].concat(results);
3429
} else {
3530
// If there are multiple columns in csv file
3631
// try to match headers using lookup value

core/server/models/relations/authors.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -314,22 +314,26 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
314314
return isCorrectOwner;
315315
}
316316

317-
function isCurrentOwner() {
318-
return context.user === postModel.related('authors').models[0].id;
317+
function isPrimaryAuthor() {
318+
return (context.user === postModel.related('authors').models[0].id);
319+
}
320+
321+
function isCoAuthor() {
322+
return postModel.related('authors').models.map(author => author.id).includes(context.user);
319323
}
320324

321325
if (isContributor && isEdit) {
322-
hasUserPermission = !isChanging('author_id') && !isChangingAuthors() && isCurrentOwner();
326+
hasUserPermission = !isChanging('author_id') && !isChangingAuthors() && isCoAuthor();
323327
} else if (isContributor && isAdd) {
324328
hasUserPermission = isOwner();
325329
} else if (isContributor && isDestroy) {
326-
hasUserPermission = isCurrentOwner();
330+
hasUserPermission = isPrimaryAuthor();
327331
} else if (isAuthor && isEdit) {
328-
hasUserPermission = isCurrentOwner() && !isChanging('author_id') && !isChangingAuthors();
332+
hasUserPermission = isCoAuthor() && !isChanging('author_id') && !isChangingAuthors();
329333
} else if (isAuthor && isAdd) {
330334
hasUserPermission = isOwner();
331335
} else if (postModel) {
332-
hasUserPermission = hasUserPermission || isCurrentOwner();
336+
hasUserPermission = hasUserPermission || isPrimaryAuthor();
333337
}
334338

335339
if (hasUserPermission && hasAppPermission) {

core/server/services/auth/authenticate.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ const authenticate = {
103103
},
104104

105105
// ### v2 API auth middleware
106-
authenticateAdminAPI: [session.safeGetSession, session.getUser],
106+
authenticateAdminApi: [session.safeGetSession, session.getUser],
107107
authenticateContentApi: [apiKeyAuth.content.authenticateContentApiKey, members.authenticateMembersToken]
108108
};
109109

core/server/services/auth/authorize.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const authorize = {
3737
};
3838
},
3939

40-
authorizeAdminAPI: [session.ensureUser],
40+
authorizeAdminApi: [session.ensureUser],
4141
authorizeContentApi(req, res, next) {
4242
const hasApiKey = req.api_key && req.api_key.id;
4343
const hasMember = req.member;

core/server/services/routing/CollectionRouter.js

+3
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ class CollectionRouter extends ParentRouter {
7373
// REGISTER: context middleware for entries
7474
this.router().use(this._prepareEntryContext.bind(this));
7575

76+
// REGISTER: page/post resource redirects
77+
this.router().param('slug', this._respectDominantRouter.bind(this));
78+
7679
// REGISTER: permalinks e.g. /:slug/, /podcast/:slug
7780
this.mountRoute(this.permalinks.getValue({withUrlOptions: true}), controllers.entry);
7881

core/server/services/routing/ParentRouter.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ class ParentRouter extends EventEmitter {
7878
if (targetRoute) {
7979
debug('_respectDominantRouter');
8080

81-
const matchPath = this.permalinks.getValue().replace(':slug', '[a-zA-Z0-9-_]+');
81+
// CASE: transform /tag/:slug/ -> /tag/[a-zA-Z0-9-_]+/ to able to find url pieces to append
82+
// e.g. /tag/bacon/page/2/ -> 'page/2' (to append)
83+
// e.g. /bacon/welcome/ -> '' (nothing to append)
84+
const matchPath = this.permalinks.getValue().replace(/:\w+/g, '[a-zA-Z0-9-_]+');
8285
const toAppend = req.url.replace(new RegExp(matchPath), '');
8386

8487
return urlService.utils.redirect301(res, url.format({

core/server/services/routing/controllers/static.js

+10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ function processQuery(query, locals) {
77
const api = require('../../../api')[locals.apiVersion];
88
query = _.cloneDeep(query);
99

10+
// CASE: If you define a single data key for a static route (e.g. data: page.team), this static route will represent
11+
// the target resource. That means this static route has to behave the same way than the original resource url.
12+
// e.g. the meta data package needs access to the full resource including relations.
13+
// We override the `include` property for now, because the full data set is required anyway.
14+
if (_.get(query, 'resource') === 'posts') {
15+
_.extend(query.options, {
16+
include: 'author,authors,tags'
17+
});
18+
}
19+
1020
// Return a promise for the api query
1121
return (api[query.alias] || api[query.resource])[query.type](query.options);
1222
}

core/server/services/settings/validate.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ _private.validateData = function validateData(object) {
7575
const requiredQueryFields = ['type', 'resource'];
7676
const allowedQueryValues = {
7777
type: ['read', 'browse'],
78-
resource: _.map(RESOURCE_CONFIG.QUERY, 'resource')
78+
resource: _.union(_.map(RESOURCE_CONFIG.QUERY, 'resource'), _.map(RESOURCE_CONFIG.QUERY, 'alias'))
7979
};
80-
const allowedQueryOptions = ['limit', 'order', 'filter', 'include', 'slug', 'visibility', 'status'];
80+
const allowedQueryOptions = ['limit', 'order', 'filter', 'include', 'slug', 'visibility', 'status', 'page'];
8181
const allowedRouterOptions = ['redirect', 'slug'];
8282
const defaultRouterOptions = {
8383
redirect: true
@@ -146,7 +146,12 @@ _private.validateData = function validateData(object) {
146146
data.query[key][option] = object.data[key][option];
147147
});
148148

149-
const DEFAULT_RESOURCE = _.find(RESOURCE_CONFIG.QUERY, {resource: data.query[key].resource});
149+
const DEFAULT_RESOURCE = _.find(RESOURCE_CONFIG.QUERY, {alias: data.query[key].resource}) || _.find(RESOURCE_CONFIG.QUERY, {resource: data.query[key].resource});
150+
151+
// CASE: you define resource:pages and the alias is "pages". We need to load the internal alias/resource structure, otherwise we break api versions.
152+
data.query[key].alias = DEFAULT_RESOURCE.alias;
153+
data.query[key].resource = DEFAULT_RESOURCE.resource;
154+
150155
data.query[key] = _.defaults(data.query[key], _.omit(DEFAULT_RESOURCE, 'options'));
151156

152157
data.query[key].options = _.pick(object.data[key], allowedQueryOptions);

core/server/web/admin/views/default-prod.html

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<title>Ghost Admin</title>
99

1010

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/%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%3Atrue%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%7D%2C%22APP%22%3A%7B%22version%22%3A%222.6%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%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/%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%3Atrue%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%7D%2C%22APP%22%3A%7B%22version%22%3A%222.7%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%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" />
1212

1313
<meta name="HandheldFriendly" content="True" />
1414
<meta name="MobileOptimized" content="320" />
@@ -34,7 +34,7 @@
3434

3535

3636
<link rel="stylesheet" href="assets/vendor.min-e71cb5c677f51d517625c8c87005a74a.css">
37-
<link rel="stylesheet" href="assets/ghost.min-1b13c62f5c1d56fd1d1f0ebc951a3d26.css" title="light">
37+
<link rel="stylesheet" href="assets/ghost.min-60c6f40170e0422153cd017eb77d10a7.css" title="light">
3838

3939

4040

@@ -53,7 +53,7 @@
5353

5454

5555
<script src="assets/vendor.min-a282d655f9b0929434826d02f2158e38.js"></script>
56-
<script src="assets/ghost.min-2faf9f5c52906b91112aade6dd90ca58.js"></script>
56+
<script src="assets/ghost.min-96bc2e85ece023413226dfd3e4856109.js"></script>
5757

5858
</body>
5959
</html>

0 commit comments

Comments
 (0)