apostrophe 3.61.0 → 3.62.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.
- package/CHANGELOG.md +26 -0
- package/modules/@apostrophecms/doc/index.js +2 -2
- package/modules/@apostrophecms/doc-type/index.js +8 -2
- package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +7 -1
- package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +12 -3
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManager.vue +8 -7
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerDisplay.vue +1 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerEditor.vue +5 -2
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaManagerSelections.vue +1 -5
- package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +4 -2
- package/modules/@apostrophecms/page/index.js +33 -9
- package/modules/@apostrophecms/page/ui/apos/components/AposPagesManager.vue +3 -1
- package/modules/@apostrophecms/page/ui/apos/logic/AposPagesManager.js +7 -0
- package/modules/@apostrophecms/page-type/index.js +14 -2
- package/modules/@apostrophecms/permission/index.js +60 -31
- package/modules/@apostrophecms/piece-type/index.js +18 -7
- package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -1
- package/modules/@apostrophecms/rich-text-widget/index.js +141 -1
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposTiptapLink.vue +38 -79
- package/modules/@apostrophecms/rich-text-widget/ui/apos/tiptap-extensions/Link.js +11 -0
- package/modules/@apostrophecms/schema/lib/addFieldTypes.js +19 -2
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArea.vue +1 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +3 -0
- package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +2 -1
- package/modules/@apostrophecms/schema/ui/apos/components/AposSchema.vue +1 -4
- package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +12 -8
- package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposCheckbox.vue +1 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTree.vue +7 -0
- package/modules/@apostrophecms/ui/ui/apos/components/AposTreeRows.vue +8 -0
- package/modules/@apostrophecms/ui/ui/apos/scss/global/_inputs.scss +2 -2
- package/package.json +2 -2
- package/test/pages-autocomplete.js +203 -0
- package/test/permissions.js +76 -0
- package/test/rich-text-widget.js +164 -0
- package/test/schema-queryBuilders.js +180 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.62.0 (2024-01-25)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Adds support for `type` query parameter for page autocomplete. This allows to filter the results by page type. Example: `/api/v1/@apostrophecms/page?autocomplete=something&type=my-page-type`.
|
|
8
|
+
* Add testing for the `float` schema field query builder.
|
|
9
|
+
* Add testing for the `integer` schema field query builder.
|
|
10
|
+
* Add support for link HTML attributes in the rich text widget via configurable fields `linkFields`, extendable on a project level (same as it's done for `fields`). Add an `htmlAttribute` property to the standard fields that map directly to an HTML attribute, except `href` (see special case below), and set it accordingly, even if it is the same as the field name. Setting `htmlAttribute: 'href'` is not allowed and will throw a schema validation exception (on application boot).
|
|
11
|
+
* Adds support in `can` and `criteria` methods for `create` and `delete`.
|
|
12
|
+
* Changes support for image upload from `canEdit` to `canCreate`.
|
|
13
|
+
* The media manager is compatible with per-doc permissions granted via the `@apostrophecms-pro/advanced-permission` module.
|
|
14
|
+
|
|
15
|
+
### Fixes
|
|
16
|
+
|
|
17
|
+
* Fix the `launder` and `finalize` methods of the `float` schema field query builder.
|
|
18
|
+
* Fix the `launder` and `finalize` methods of the `integer` schema field query builder.
|
|
19
|
+
* A user who has permission to `publish` a particular page should always be allowed to insert it into the
|
|
20
|
+
published version of the site even if they could not otherwise insert a child of the published
|
|
21
|
+
parent.
|
|
22
|
+
|
|
23
|
+
## 3.61.1 (2023-01-08)
|
|
24
|
+
|
|
25
|
+
### Fixes
|
|
26
|
+
|
|
27
|
+
* Pinned Vue dependency to 2.7.15. Released on December 24th, Vue 2.7.16 broke the rich text toolbar in Apostrophe.
|
|
28
|
+
|
|
3
29
|
## 3.61.0 (2023-12-21)
|
|
4
30
|
|
|
5
31
|
### Adds
|
|
@@ -857,8 +857,8 @@ module.exports = {
|
|
|
857
857
|
// Called by an `@apostrophecms/doc-type:insert` event handler to confirm that the user
|
|
858
858
|
// has the appropriate permissions for the doc's type and content.
|
|
859
859
|
testInsertPermissions(req, doc, options) {
|
|
860
|
-
if (
|
|
861
|
-
if (!self.apos.permission.can(req, '
|
|
860
|
+
if (options.permissions !== false) {
|
|
861
|
+
if (!self.apos.permission.can(req, 'create', doc)) {
|
|
862
862
|
throw self.apos.error('forbidden');
|
|
863
863
|
}
|
|
864
864
|
}
|
|
@@ -19,7 +19,9 @@ module.exports = {
|
|
|
19
19
|
relationshipSuggestionIcon: 'text-box-icon',
|
|
20
20
|
relationshipSuggestionFields: [ 'slug' ]
|
|
21
21
|
},
|
|
22
|
-
|
|
22
|
+
// Adding permissions for advanced permissions to allow modules to use it without
|
|
23
|
+
// being forced to check if the module is used with advanced permissions or not.
|
|
24
|
+
cascades: [ 'fields', 'permissions' ],
|
|
23
25
|
fields(self) {
|
|
24
26
|
return {
|
|
25
27
|
add: {
|
|
@@ -1499,8 +1501,10 @@ module.exports = {
|
|
|
1499
1501
|
label,
|
|
1500
1502
|
pluralLabel,
|
|
1501
1503
|
relatedDocument: self.options.relatedDocument,
|
|
1504
|
+
canCreate: self.apos.permission.can(req, 'create', self.name, 'draft'),
|
|
1502
1505
|
canEdit: self.apos.permission.can(req, 'edit', self.name, 'draft'),
|
|
1503
|
-
canPublish: self.apos.permission.can(req, 'publish', self.name)
|
|
1506
|
+
canPublish: self.apos.permission.can(req, 'publish', self.name),
|
|
1507
|
+
canArchive: self.apos.permission.can(req, 'delete', self.name)
|
|
1504
1508
|
};
|
|
1505
1509
|
browserOptions.canLocalize = browserOptions.canEdit &&
|
|
1506
1510
|
self.options.localized &&
|
|
@@ -1868,8 +1872,10 @@ module.exports = {
|
|
|
1868
1872
|
after(results) {
|
|
1869
1873
|
// In all cases we mark the docs with ._edit and ._publish if
|
|
1870
1874
|
// the req is permitted to do those things
|
|
1875
|
+
self.apos.permission.annotate(query.req, 'create', results);
|
|
1871
1876
|
self.apos.permission.annotate(query.req, 'edit', results);
|
|
1872
1877
|
self.apos.permission.annotate(query.req, 'publish', results);
|
|
1878
|
+
self.apos.permission.annotate(query.req, 'delete', results);
|
|
1873
1879
|
}
|
|
1874
1880
|
},
|
|
1875
1881
|
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
:current="docFields.data"
|
|
20
20
|
:published="published"
|
|
21
21
|
:show-edit="false"
|
|
22
|
+
:can-delete-draft="moduleOptions.canDeleteDraft"
|
|
22
23
|
@close="close"
|
|
23
24
|
/>
|
|
24
25
|
<AposButton
|
|
@@ -195,8 +196,13 @@ export default {
|
|
|
195
196
|
|
|
196
197
|
return this.moduleOptions.canPublish;
|
|
197
198
|
},
|
|
199
|
+
canCreate() {
|
|
200
|
+
return this.original &&
|
|
201
|
+
!this.original._id &&
|
|
202
|
+
this.moduleOptions.canCreate;
|
|
203
|
+
},
|
|
198
204
|
saveDisabled() {
|
|
199
|
-
if (!this.canEdit) {
|
|
205
|
+
if (!this.canCreate && !this.canEdit) {
|
|
200
206
|
return true;
|
|
201
207
|
}
|
|
202
208
|
if (this.restoreOnly) {
|
|
@@ -48,6 +48,12 @@ export default {
|
|
|
48
48
|
return true;
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
+
canDeleteDraft: {
|
|
52
|
+
type: Boolean,
|
|
53
|
+
default() {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
51
57
|
showCopy: {
|
|
52
58
|
type: Boolean,
|
|
53
59
|
default() {
|
|
@@ -245,7 +251,10 @@ export default {
|
|
|
245
251
|
if (!this.context._id) {
|
|
246
252
|
return false;
|
|
247
253
|
}
|
|
248
|
-
if (!this.
|
|
254
|
+
if (!this.context.lastPublishedAt && !this.canDeleteDraft && !this.context._delete) {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (this.context.lastPublishedAt && (!this.context.modified || !this.context._edit)) {
|
|
249
258
|
return false;
|
|
250
259
|
}
|
|
251
260
|
return (
|
|
@@ -262,12 +271,12 @@ export default {
|
|
|
262
271
|
},
|
|
263
272
|
canArchive() {
|
|
264
273
|
return (
|
|
265
|
-
this.
|
|
274
|
+
this.context._delete &&
|
|
266
275
|
this.context._id &&
|
|
267
276
|
!this.moduleOptions.singleton &&
|
|
268
277
|
!this.context.archived &&
|
|
269
278
|
!this.context.parked &&
|
|
270
|
-
((this.
|
|
279
|
+
(Boolean(this.canPublish && this.context.lastPublishedAt) || !this.manuallyPublished)
|
|
271
280
|
);
|
|
272
281
|
},
|
|
273
282
|
canUnpublish() {
|
|
@@ -71,7 +71,6 @@
|
|
|
71
71
|
:accept="accept"
|
|
72
72
|
:items="items"
|
|
73
73
|
:module-options="moduleOptions"
|
|
74
|
-
:can-edit="moduleOptions.canEdit"
|
|
75
74
|
@edit="updateEditing"
|
|
76
75
|
v-model="checked"
|
|
77
76
|
@select="select"
|
|
@@ -106,7 +105,6 @@
|
|
|
106
105
|
/>
|
|
107
106
|
<AposMediaManagerSelections
|
|
108
107
|
:items="selected"
|
|
109
|
-
:can-edit="moduleOptions.canEdit"
|
|
110
108
|
@clear="clearSelected" @edit="updateEditing"
|
|
111
109
|
v-show="!editing"
|
|
112
110
|
/>
|
|
@@ -248,7 +246,8 @@ export default {
|
|
|
248
246
|
async getMedia (options) {
|
|
249
247
|
const qs = {
|
|
250
248
|
...this.filterValues,
|
|
251
|
-
page: this.currentPage
|
|
249
|
+
page: this.currentPage,
|
|
250
|
+
viewContext: this.relationshipField ? 'relationship' : 'manage'
|
|
252
251
|
};
|
|
253
252
|
const filtered = !!Object.keys(this.filterValues).length;
|
|
254
253
|
if (this.moduleOptions && Array.isArray(this.moduleOptions.filters)) {
|
|
@@ -354,9 +353,7 @@ export default {
|
|
|
354
353
|
this.editing = undefined;
|
|
355
354
|
},
|
|
356
355
|
async updateEditing(id) {
|
|
357
|
-
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
356
|
+
const item = this.items.find(item => item._id === id);
|
|
360
357
|
// We only care about the current doc for this prompt,
|
|
361
358
|
// we are not in danger of discarding a selection when
|
|
362
359
|
// we switch images
|
|
@@ -371,7 +368,11 @@ export default {
|
|
|
371
368
|
return false;
|
|
372
369
|
}
|
|
373
370
|
}
|
|
374
|
-
|
|
371
|
+
if (!item?._edit) {
|
|
372
|
+
this.editing = null;
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
this.editing = item;
|
|
375
376
|
return true;
|
|
376
377
|
},
|
|
377
378
|
// select setters
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="apos-media-manager-display">
|
|
3
3
|
<div class="apos-media-manager-display__grid">
|
|
4
4
|
<AposMediaUploader
|
|
5
|
-
v-if="
|
|
5
|
+
v-if="moduleOptions.canCreate"
|
|
6
6
|
:disabled="maxReached"
|
|
7
7
|
:action="moduleOptions.action"
|
|
8
8
|
:accept="accept"
|
|
@@ -83,10 +83,6 @@ export default {
|
|
|
83
83
|
event: 'change'
|
|
84
84
|
},
|
|
85
85
|
props: {
|
|
86
|
-
canEdit: {
|
|
87
|
-
type: Boolean,
|
|
88
|
-
default: false
|
|
89
|
-
},
|
|
90
86
|
maxReached: {
|
|
91
87
|
type: Boolean,
|
|
92
88
|
default: false
|
|
@@ -171,7 +171,7 @@ export default {
|
|
|
171
171
|
action: 'localize'
|
|
172
172
|
});
|
|
173
173
|
}
|
|
174
|
-
if (this.activeMedia._id && !this.restoreOnly) {
|
|
174
|
+
if (this.activeMedia._id && this.activeMedia._delete && !this.restoreOnly) {
|
|
175
175
|
menu.push({
|
|
176
176
|
label: 'apostrophe:archiveImage',
|
|
177
177
|
action: 'archive',
|
|
@@ -209,7 +209,9 @@ export default {
|
|
|
209
209
|
return dayjs(this.activeMedia.attachment.createdAt).format(this.$t('apostrophe:dayjsMediaCreatedDateFormat'));
|
|
210
210
|
},
|
|
211
211
|
isArchived() {
|
|
212
|
-
|
|
212
|
+
// ?. necessary to avoid reference to null due to
|
|
213
|
+
// race condition when toggling selection off
|
|
214
|
+
return this.media?.archived;
|
|
213
215
|
}
|
|
214
216
|
},
|
|
215
217
|
watch: {
|
|
@@ -248,6 +250,7 @@ export default {
|
|
|
248
250
|
this[action]();
|
|
249
251
|
},
|
|
250
252
|
async updateActiveDoc(newMedia) {
|
|
253
|
+
newMedia = newMedia || {};
|
|
251
254
|
this.showReplace = false;
|
|
252
255
|
this.activeMedia = klona(newMedia);
|
|
253
256
|
this.restoreOnly = !!this.activeMedia.archived;
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
{{ item.title }}
|
|
31
31
|
</div>
|
|
32
32
|
<AposButton
|
|
33
|
-
v-if="
|
|
33
|
+
v-if="item._edit"
|
|
34
34
|
label="apostrophe:edit"
|
|
35
35
|
type="quiet"
|
|
36
36
|
:modifiers="['no-motion']"
|
|
@@ -49,10 +49,6 @@
|
|
|
49
49
|
<script>
|
|
50
50
|
export default {
|
|
51
51
|
props: {
|
|
52
|
-
canEdit: {
|
|
53
|
-
type: Boolean,
|
|
54
|
-
default: false
|
|
55
|
-
},
|
|
56
52
|
items: {
|
|
57
53
|
type: Array,
|
|
58
54
|
required: true
|
|
@@ -141,9 +141,11 @@ export default {
|
|
|
141
141
|
for (const file of files) {
|
|
142
142
|
try {
|
|
143
143
|
const img = await this.insertImage(file, emptyDoc);
|
|
144
|
-
|
|
144
|
+
if (img?._id) {
|
|
145
|
+
imageIds.push(img._id);
|
|
146
|
+
}
|
|
145
147
|
} catch (e) {
|
|
146
|
-
const msg = e.body && e.body.message ? e.body.message : this.$t('
|
|
148
|
+
const msg = e.body && e.body.message ? e.body.message : this.$t('apostrophe:uploadError');
|
|
147
149
|
await apos.notify(msg, {
|
|
148
150
|
type: 'danger',
|
|
149
151
|
icon: 'alert-circle-icon',
|
|
@@ -122,6 +122,9 @@ module.exports = {
|
|
|
122
122
|
// The user must have some page editing privileges to use it. The 10 best
|
|
123
123
|
// matches are returned as an object with a `results` property containing the
|
|
124
124
|
// array of pages.
|
|
125
|
+
// If ?type=x is present, only pages of that type are returned. This query
|
|
126
|
+
// parameter is only used in conjunction with ?autocomplete=x. It will be
|
|
127
|
+
// ignored otherwise.
|
|
125
128
|
//
|
|
126
129
|
// If querying for draft pages, you may add ?published=1 to attach a
|
|
127
130
|
// `_publishedDoc` property to each draft that also exists in a published form.
|
|
@@ -139,11 +142,22 @@ module.exports = {
|
|
|
139
142
|
if (!self.apos.permission.can(req, 'view', '@apostrophecms/any-page-type')) {
|
|
140
143
|
throw self.apos.error('forbidden');
|
|
141
144
|
}
|
|
145
|
+
|
|
146
|
+
const type = self.apos.launder.string(req.query.type);
|
|
147
|
+
if (type.length && !self.apos.permission.can(req, 'view', type)) {
|
|
148
|
+
throw self.apos.error('forbidden');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const query = self.getRestQuery(req).permission(false).limit(10).relationships(false)
|
|
152
|
+
.areas(false);
|
|
153
|
+
if (type.length) {
|
|
154
|
+
query.type(type);
|
|
155
|
+
}
|
|
156
|
+
|
|
142
157
|
return {
|
|
143
158
|
// For consistency with the pieces REST API we
|
|
144
159
|
// use a results property when returning a flat list
|
|
145
|
-
results: await
|
|
146
|
-
.areas(false).toArray()
|
|
160
|
+
results: await query.toArray()
|
|
147
161
|
};
|
|
148
162
|
}
|
|
149
163
|
|
|
@@ -275,7 +289,7 @@ module.exports = {
|
|
|
275
289
|
// If we're looking for a fresh page instance and aren't saving yet,
|
|
276
290
|
// simply get a new page doc and return it
|
|
277
291
|
const parentPage = await self.findForEditing(req, self.getIdCriteria(targetId))
|
|
278
|
-
.permission('
|
|
292
|
+
.permission('create', '@apostrophecms/any-page-type').toObject();
|
|
279
293
|
const newChild = self.newChild(parentPage);
|
|
280
294
|
newChild._previewable = true;
|
|
281
295
|
return newChild;
|
|
@@ -285,7 +299,7 @@ module.exports = {
|
|
|
285
299
|
const targetPage = await self
|
|
286
300
|
.findForEditing(req, self.getIdCriteria(targetId))
|
|
287
301
|
.ancestors(true)
|
|
288
|
-
.permission('
|
|
302
|
+
.permission('create')
|
|
289
303
|
.toObject();
|
|
290
304
|
|
|
291
305
|
if (!targetPage) {
|
|
@@ -311,7 +325,10 @@ module.exports = {
|
|
|
311
325
|
copyingId
|
|
312
326
|
});
|
|
313
327
|
await self.insert(req, targetPage._id, position, page, { lock: false });
|
|
314
|
-
return self.findOneForEditing(req, { _id: page._id }, {
|
|
328
|
+
return self.findOneForEditing(req, { _id: page._id }, {
|
|
329
|
+
attachments: true,
|
|
330
|
+
permission: false
|
|
331
|
+
});
|
|
315
332
|
});
|
|
316
333
|
},
|
|
317
334
|
// Consider using `PATCH` instead unless you're sure you have 100% up to date
|
|
@@ -391,6 +408,10 @@ module.exports = {
|
|
|
391
408
|
const page = await self.findOneForEditing(req, {
|
|
392
409
|
_id
|
|
393
410
|
});
|
|
411
|
+
|
|
412
|
+
if (!page) {
|
|
413
|
+
throw self.apos.error('notfound');
|
|
414
|
+
}
|
|
394
415
|
return self.delete(req, page);
|
|
395
416
|
},
|
|
396
417
|
// Patch some properties of the page.
|
|
@@ -828,7 +849,8 @@ database.`);
|
|
|
828
849
|
}
|
|
829
850
|
browserOptions.name = self.__meta.name;
|
|
830
851
|
browserOptions.canPublish = self.apos.permission.can(req, 'publish', '@apostrophecms/any-page-type');
|
|
831
|
-
browserOptions.
|
|
852
|
+
browserOptions.canCreate = self.apos.permission.can(req, 'create', '@apostrophecms/any-page-type', 'draft');
|
|
853
|
+
browserOptions.quickCreate = self.options.quickCreate && self.apos.permission.can(req, 'create', '@apostrophecms/any-page-type', 'draft');
|
|
832
854
|
browserOptions.localized = true;
|
|
833
855
|
browserOptions.autopublish = false;
|
|
834
856
|
// A list of all valid page types, including parked pages etc. This is
|
|
@@ -840,6 +862,8 @@ database.`);
|
|
|
840
862
|
Object.keys(self.apos.i18n.locales).length > 1 &&
|
|
841
863
|
Object.values(self.apos.i18n.locales).some(locale => locale._edit);
|
|
842
864
|
browserOptions.utilityOperations = self.utilityOperations;
|
|
865
|
+
browserOptions.canDeleteDraft = self.apos.permission.can(req, 'delete', '@apostrophecms/any-page-type', 'draft');
|
|
866
|
+
|
|
843
867
|
return browserOptions;
|
|
844
868
|
},
|
|
845
869
|
// Returns a query that finds pages the current user can edit
|
|
@@ -882,7 +906,7 @@ database.`);
|
|
|
882
906
|
if ((position === 'before') || (position === 'after')) {
|
|
883
907
|
parent = await self.findForEditing(req, {
|
|
884
908
|
path: self.getParentPath(target)
|
|
885
|
-
}).children({
|
|
909
|
+
}, { permission: 'create' }).children({
|
|
886
910
|
depth: 1,
|
|
887
911
|
archived: null,
|
|
888
912
|
orphan: null,
|
|
@@ -898,7 +922,7 @@ database.`);
|
|
|
898
922
|
throw self.apos.error('notfound');
|
|
899
923
|
}
|
|
900
924
|
if (options.permissions !== false) {
|
|
901
|
-
if (!parent.
|
|
925
|
+
if (!parent._create) {
|
|
902
926
|
throw self.apos.error('forbidden');
|
|
903
927
|
}
|
|
904
928
|
}
|
|
@@ -1095,7 +1119,7 @@ database.`);
|
|
|
1095
1119
|
// Move outside tree
|
|
1096
1120
|
throw self.apos.error('forbidden');
|
|
1097
1121
|
}
|
|
1098
|
-
if ((oldParent._id !== parent._id) && (parent.type !== '@apostrophecms/archive-page') && (!parent.
|
|
1122
|
+
if ((oldParent._id !== parent._id) && (parent.type !== '@apostrophecms/archive-page') && (!parent._create)) {
|
|
1099
1123
|
throw self.apos.error('forbidden');
|
|
1100
1124
|
}
|
|
1101
1125
|
if (moved.lastPublishedAt && !parent.lastPublishedAt) {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
:button="moreMenuButton"
|
|
33
33
|
/>
|
|
34
34
|
<AposButton
|
|
35
|
-
v-else type="primary"
|
|
35
|
+
v-else-if="canCreate" type="primary"
|
|
36
36
|
label="apostrophe:newPage" @click="create()"
|
|
37
37
|
/>
|
|
38
38
|
<AposButton
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
:icons="icons"
|
|
82
82
|
v-model="checked"
|
|
83
83
|
:options="treeOptions"
|
|
84
|
+
:module-options="moduleOptions"
|
|
84
85
|
@update="update"
|
|
85
86
|
/>
|
|
86
87
|
</template>
|
|
@@ -98,6 +99,7 @@ export default {
|
|
|
98
99
|
// Keep it for linting
|
|
99
100
|
emits: [ 'archive', 'search', 'safe-close', 'modal-result' ]
|
|
100
101
|
};
|
|
102
|
+
// TODO: check when child page is created and with what perm
|
|
101
103
|
</script>
|
|
102
104
|
|
|
103
105
|
<style lang="scss" scoped>
|
|
@@ -128,6 +128,13 @@ export default {
|
|
|
128
128
|
},
|
|
129
129
|
pageSetMenuSelectionIsLive() {
|
|
130
130
|
return this.pageSetMenuSelection === 'live';
|
|
131
|
+
},
|
|
132
|
+
canCreate() {
|
|
133
|
+
const page = this.pagesFlat.find(page => page.aposDocId === this.moduleOptions.page.aposDocId);
|
|
134
|
+
if (page) {
|
|
135
|
+
return page._create;
|
|
136
|
+
}
|
|
137
|
+
return this.moduleOptions.canCreate;
|
|
131
138
|
}
|
|
132
139
|
},
|
|
133
140
|
watch: {
|
|
@@ -104,7 +104,7 @@ module.exports = {
|
|
|
104
104
|
},
|
|
105
105
|
beforeMove: {
|
|
106
106
|
checkPermissions(req, doc) {
|
|
107
|
-
if (doc.lastPublishedAt && !self.apos.permission.can(req, 'publish',
|
|
107
|
+
if (doc.lastPublishedAt && !self.apos.permission.can(req, 'publish', doc)) {
|
|
108
108
|
throw self.apos.error('forbidden', 'Contributors may only move unpublished pages.');
|
|
109
109
|
}
|
|
110
110
|
}
|
|
@@ -352,6 +352,11 @@ module.exports = {
|
|
|
352
352
|
// Called for you when a page is published for the first time.
|
|
353
353
|
// You don't need to invoke this.
|
|
354
354
|
async insertPublishedOf(req, doc, published, options = {}) {
|
|
355
|
+
// Check publish permission up front because we won't check it
|
|
356
|
+
// in insert
|
|
357
|
+
if (!self.apos.permission.can(req, 'publish', doc)) {
|
|
358
|
+
throw self.apos.error('forbidden');
|
|
359
|
+
}
|
|
355
360
|
const _req = req.clone({
|
|
356
361
|
mode: 'published'
|
|
357
362
|
});
|
|
@@ -363,7 +368,14 @@ module.exports = {
|
|
|
363
368
|
lastTargetId.replace(':draft', ':published'),
|
|
364
369
|
lastPosition,
|
|
365
370
|
published,
|
|
366
|
-
|
|
371
|
+
{
|
|
372
|
+
...options,
|
|
373
|
+
// We already confirmed we are allowed to
|
|
374
|
+
// publish the draft, bypass checks that
|
|
375
|
+
// can get hung up on "create" permission
|
|
376
|
+
permissions: false
|
|
377
|
+
}
|
|
378
|
+
);
|
|
367
379
|
} else {
|
|
368
380
|
// Insert the home page
|
|
369
381
|
Object.assign(published, {
|
|
@@ -59,6 +59,7 @@ module.exports = {
|
|
|
59
59
|
if (role === 'admin') {
|
|
60
60
|
return true;
|
|
61
61
|
}
|
|
62
|
+
|
|
62
63
|
const type = docOrType && (docOrType.type || docOrType);
|
|
63
64
|
const doc = (docOrType && docOrType._id) ? docOrType : null;
|
|
64
65
|
const manager = type && self.apos.doc.getManager(type);
|
|
@@ -66,46 +67,73 @@ module.exports = {
|
|
|
66
67
|
self.apos.util.warn('A permission.can() call was made with a type that has no manager:', type);
|
|
67
68
|
return false;
|
|
68
69
|
}
|
|
70
|
+
|
|
69
71
|
if (action === 'view') {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
} else {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
} else if (action === 'view-draft') {
|
|
72
|
+
return canView();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (action === 'view-draft') {
|
|
78
76
|
// Checked at the middleware level to determine if req.mode should
|
|
79
77
|
// be allowed to be set to draft at all
|
|
80
78
|
return (role === 'contributor') || (role === 'editor');
|
|
81
|
-
}
|
|
82
|
-
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if ([ 'edit', 'create' ].includes(action)) {
|
|
82
|
+
return canEdit();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (action === 'publish') {
|
|
86
|
+
return canPublish();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (action === 'upload-attachment') {
|
|
90
|
+
return (role === 'contributor') || (role === 'editor');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (action === 'delete') {
|
|
94
|
+
return canDelete();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw self.apos.error('invalid', 'That action is not implemented');
|
|
98
|
+
|
|
99
|
+
function checkRoleConfig (permRole) {
|
|
100
|
+
return manager && manager.options[permRole] &&
|
|
101
|
+
(ranks[role] < ranks[manager.options[permRole]]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function canView() {
|
|
105
|
+
if (checkRoleConfig('viewRole')) {
|
|
83
106
|
return false;
|
|
84
|
-
} else if (mode === 'draft') {
|
|
85
|
-
return (role === 'contributor') || (role === 'editor');
|
|
86
|
-
} else {
|
|
87
|
-
return role === 'editor';
|
|
88
107
|
}
|
|
89
|
-
|
|
90
|
-
|
|
108
|
+
if ((typeof docOrType === 'object') && (docOrType.visibility !== 'public')) {
|
|
109
|
+
return (role === 'guest') || (role === 'contributor') || (role === 'editor');
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function canEdit() {
|
|
115
|
+
if (checkRoleConfig('editRole')) {
|
|
91
116
|
return false;
|
|
92
|
-
} else {
|
|
93
|
-
return role === 'editor';
|
|
94
117
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
118
|
+
if (mode === 'draft') {
|
|
119
|
+
return (role === 'contributor') || (role === 'editor');
|
|
120
|
+
}
|
|
121
|
+
return role === 'editor';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function canPublish() {
|
|
125
|
+
if (checkRoleConfig('publishRole')) {
|
|
99
126
|
return false;
|
|
100
127
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
return role === 'editor';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function canDelete() {
|
|
132
|
+
if (doc && (!doc.lastPublishedAt || doc.aposMode === 'draft')) {
|
|
133
|
+
return self.can(req, 'edit', doc, mode);
|
|
106
134
|
}
|
|
107
|
-
|
|
108
|
-
|
|
135
|
+
|
|
136
|
+
return self.can(req, 'publish', docOrType, mode);
|
|
109
137
|
}
|
|
110
138
|
},
|
|
111
139
|
|
|
@@ -117,6 +145,7 @@ module.exports = {
|
|
|
117
145
|
if (role === 'admin') {
|
|
118
146
|
return {};
|
|
119
147
|
}
|
|
148
|
+
|
|
120
149
|
const restrictedViewTypes = Object.keys(self.apos.doc.managers).filter(name => ranks[self.apos.doc.getManager(name).options.viewRole] > ranks[role]);
|
|
121
150
|
const restrictedEditTypes = Object.keys(self.apos.doc.managers).filter(name => ranks[self.apos.doc.getManager(name).options.editRole] > ranks[role]);
|
|
122
151
|
const restrictedPublishTypes = Object.keys(self.apos.doc.managers).filter(name => ranks[self.apos.doc.getManager(name).options.publishRole] > ranks[role]);
|
|
@@ -175,7 +204,7 @@ module.exports = {
|
|
|
175
204
|
|
|
176
205
|
return query;
|
|
177
206
|
}
|
|
178
|
-
} else if (
|
|
207
|
+
} else if ([ 'edit', 'create', 'delete' ].includes(action)) {
|
|
179
208
|
if (role === 'contributor') {
|
|
180
209
|
return {
|
|
181
210
|
aposMode: {
|
|
@@ -259,7 +288,7 @@ module.exports = {
|
|
|
259
288
|
permissions.push({
|
|
260
289
|
name: 'create',
|
|
261
290
|
label: 'apostrophe:create',
|
|
262
|
-
value: self.can(req, '
|
|
291
|
+
value: self.can(req, 'create', module.name)
|
|
263
292
|
});
|
|
264
293
|
}
|
|
265
294
|
permissions.push({
|