apostrophe 3.23.0 → 3.24.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.
@@ -2,12 +2,12 @@
2
2
 
3
3
  name: Tests
4
4
 
5
- # Controls when the action will run.
5
+ # Controls when the action will run.
6
6
  on:
7
7
  push:
8
- branches: [ '*' ]
8
+ branches: ["main"]
9
9
  pull_request:
10
- branches: [ '*' ]
10
+ branches: ["*"]
11
11
 
12
12
  # Allows you to run this workflow manually from the Actions tab
13
13
  workflow_dispatch:
@@ -25,21 +25,21 @@ jobs:
25
25
 
26
26
  # Steps represent a sequence of tasks that will be executed as part of the job
27
27
  steps:
28
- - name: Git checkout
29
- uses: actions/checkout@v2
28
+ - name: Git checkout
29
+ uses: actions/checkout@v2
30
30
 
31
- - name: Use Node.js ${{ matrix.node-version }}
32
- uses: actions/setup-node@v1
33
- with:
34
- node-version: ${{ matrix.node-version }}
31
+ - name: Use Node.js ${{ matrix.node-version }}
32
+ uses: actions/setup-node@v1
33
+ with:
34
+ node-version: ${{ matrix.node-version }}
35
35
 
36
- - name: Start MongoDB
37
- uses: supercharge/mongodb-github-action@1.3.0
38
- with:
39
- mongodb-version: ${{ matrix.mongodb-version }}
36
+ - name: Start MongoDB
37
+ uses: supercharge/mongodb-github-action@1.3.0
38
+ with:
39
+ mongodb-version: ${{ matrix.mongodb-version }}
40
40
 
41
- - run: npm install
41
+ - run: npm install
42
42
 
43
- - run: npm test
44
- env:
45
- CI: true
43
+ - run: npm test
44
+ env:
45
+ CI: true
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.24.0 (2022-07-06)
4
+
5
+ ### Adds
6
+
7
+ * Handle `private: true` locale option in i18n module, preventing logged out users from accessing the content of a private locale.
8
+
9
+ ### Fixes
10
+
11
+ * Fix missing title translation in the "Array Editor" component.
12
+ * Add `follow: true` flag to `glob` functions (with `**` pattern) to allow registering symlink files and folders for nested modules
13
+ * Fix disabled context menu for relationship fields editing ([#3820](https://github.com/apostrophecms/apostrophe/issues/3820))
14
+ * In getReq method form the task module, extract the right `role` property from the options object.
15
+ * Fix `def:` option in `array` fields, in order to be able to see the default items in the array editor modal
16
+
3
17
  ## 3.23.0 (2022-06-22)
4
18
 
5
19
  ### Adds
package/index.js CHANGED
@@ -371,7 +371,7 @@ async function apostrophe(options, telemetry, rootSpan) {
371
371
  if (!options.nestedModuleSubdirs) {
372
372
  return;
373
373
  }
374
- const configs = glob.sync(self.localModules + '/**/modules.js');
374
+ const configs = glob.sync(self.localModules + '/**/modules.js', { follow: true });
375
375
  _.each(configs, function(config) {
376
376
  try {
377
377
  _.merge(self.options.modules, require(config));
package/lib/locales.js ADDED
@@ -0,0 +1,43 @@
1
+ const { stripIndent } = require('common-tags');
2
+
3
+ module.exports = {
4
+ // Make sure they are adequately distinguished by
5
+ // hostname and prefix
6
+ verifyLocales(locales, baseUrl) {
7
+ const taken = {};
8
+ let hostnamesCount = 0;
9
+ for (const [ name, options ] of Object.entries(locales)) {
10
+ const hostname = options.hostname || '__none';
11
+ const prefix = options.prefix || '__none';
12
+ const key = `${hostname}:${prefix}`;
13
+
14
+ hostnamesCount += options.hostname ? 1 : 0;
15
+
16
+ if (taken[key]) {
17
+ throw new Error(stripIndent`
18
+ The locale "${name}" cannot be distinguished from earlier locales.
19
+ Make sure it is uniquely distinguished by its hostname option,
20
+ prefix option or a combination of the two.
21
+ One locale per site may be a default with neither hostname nor prefix,
22
+ and one locale per hostname may be a default for that hostname without a prefix.
23
+ `);
24
+ }
25
+ taken[key] = true;
26
+ }
27
+
28
+ if (
29
+ hostnamesCount > 0 &&
30
+ hostnamesCount < Object.keys(locales).length &&
31
+ !baseUrl
32
+ ) {
33
+ throw new Error(stripIndent`
34
+ If some of your locales have hostnames, then they all must have
35
+ hostnames, or your top-level baseUrl option must be set.
36
+
37
+ In development, you can set baseUrl to http://localhost:3000
38
+ for testing purposes. In production it should always be set
39
+ to a real base URL for the site.
40
+ `);
41
+ }
42
+ }
43
+ };
@@ -60,7 +60,7 @@ module.exports = function(options) {
60
60
  // Fetching a list of index.js files on the first call and then searching it each time for
61
61
  // one that refers to the right type name shaves as much as 60 seconds off the startup
62
62
  // time in a large project, compared to using the glob cache feature
63
- self._indexes = glob.sync(self.options.localModules + '/**/index.js');
63
+ self._indexes = glob.sync(self.options.localModules + '/**/index.js', { follow: true });
64
64
  }
65
65
  const matches = self._indexes.filter(function(index) {
66
66
  // Double-check that we're not confusing "@apostrophecms/asset" with "asset" by
@@ -11,6 +11,7 @@ const _ = require('lodash');
11
11
  const { stripIndent } = require('common-tags');
12
12
  const ExpressSessionCookie = require('express-session/session/cookie');
13
13
  const path = require('path');
14
+ const { verifyLocales } = require('../../../lib/locales');
14
15
 
15
16
  const apostropheI18nDebugPlugin = {
16
17
  type: 'postProcessor',
@@ -218,7 +219,8 @@ module.exports = {
218
219
  } else {
219
220
  locale = self.matchLocale(req);
220
221
  }
221
- const localeOptions = self.locales[locale];
222
+ const locales = self.filterPrivateLocales(req, self.locales);
223
+ const localeOptions = locales[locale];
222
224
  if (localeOptions.prefix) {
223
225
  // Remove locale prefix so URL parsing can proceed normally from here
224
226
  if (req.path === localeOptions.prefix) {
@@ -464,8 +466,9 @@ module.exports = {
464
466
  // possible the default locale is returned.
465
467
  matchLocale(req) {
466
468
  const hostname = req.hostname;
469
+ const locales = self.filterPrivateLocales(req, self.locales);
467
470
  let best = false;
468
- for (const [ name, options ] of Object.entries(self.locales)) {
471
+ for (const [ name, options ] of Object.entries(locales)) {
469
472
  const matchedHostname = options.hostname
470
473
  ? (hostname === options.hostname.split(':')[0]) : null;
471
474
  const matchedPrefix = options.prefix
@@ -568,34 +571,7 @@ module.exports = {
568
571
  label: 'English'
569
572
  }
570
573
  };
571
- const taken = {};
572
- let hostnamesCount = 0;
573
- for (const [ name, options ] of Object.entries(locales)) {
574
- const key = (options.hostname || '__none') + ':' + (options.prefix || '__none');
575
- hostnamesCount += (options.hostname ? 1 : 0);
576
- if (taken[key]) {
577
- throw new Error(stripIndent`
578
- @apostrophecms/i18n: the locale ${name} cannot be distinguished from
579
- earlier locales. Make sure it is uniquely distinguished by its hostname
580
- option, prefix option or a combination of the two. One locale per site
581
- may be a default with neither hostname nor prefix, and one locale per
582
- hostname may be a default for that hostname without a prefix.
583
- `);
584
- }
585
- taken[key] = true;
586
- }
587
- if ((hostnamesCount > 0) && (hostnamesCount < Object.keys(locales).length) && (!self.apos.options.baseUrl)) {
588
- throw new Error(stripIndent`
589
- If some of your locales have hostnames, then they all must have
590
- hostnames, or your top-level baseUrl option must be set.
591
-
592
- In development, you can set baseUrl to http://localhost:3000
593
- for testing purposes. In production it should always be set
594
- to a real base URL for the site.
595
- `);
596
- }
597
- // Make sure they are adequately distinguished by
598
- // hostname and prefix
574
+ verifyLocales(locales, self.apos.options.baseUrl);
599
575
  return locales;
600
576
  },
601
577
  sanitizeLocaleName(locale) {
@@ -656,6 +632,16 @@ module.exports = {
656
632
  }
657
633
  return res.redirect(corresponding._url);
658
634
  };
635
+ },
636
+ // Exclude private locales when logged out
637
+ filterPrivateLocales(req, locales) {
638
+ return req.user
639
+ ? locales
640
+ : Object.fromEntries(
641
+ Object
642
+ .entries(locales)
643
+ .filter(([ name, options ]) => options.private !== true)
644
+ );
659
645
  }
660
646
  };
661
647
  }
@@ -2,7 +2,10 @@ const _ = require('lodash');
2
2
 
3
3
  module.exports = {
4
4
  extend: '@apostrophecms/doc-type',
5
- options: { name: '@apostrophecms/polymorphic' },
5
+ options: {
6
+ name: '@apostrophecms/polymorphic',
7
+ showPermissions: false
8
+ },
6
9
  routes(self) {
7
10
  return {
8
11
  post: {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <AposModal
3
3
  class="apos-array-editor" :modal="modal"
4
- :modal-title="`Edit ${field.label}`"
4
+ :modal-title="modalTitle"
5
5
  @inactive="modal.active = false" @show-modal="modal.showModal = true"
6
6
  @esc="confirmAndCancel" @no-modal="$emit('safe-close')"
7
7
  >
@@ -113,6 +113,12 @@ export default {
113
113
  },
114
114
  emits: [ 'modal-result', 'safe-close' ],
115
115
  data() {
116
+ // Automatically add `_id` to default items
117
+ const items = this.items.map(item => ({
118
+ ...item,
119
+ _id: item._id || cuid()
120
+ }));
121
+
116
122
  return {
117
123
  currentId: null,
118
124
  currentDoc: null,
@@ -121,12 +127,16 @@ export default {
121
127
  type: 'overlay',
122
128
  showModal: false
123
129
  },
130
+ modalTitle: {
131
+ key: 'apostrophe:editType',
132
+ type: this.$t(this.field.label)
133
+ },
124
134
  titleFieldChoices: null,
125
135
  // If we don't clone, then we're making
126
136
  // permanent modifications whether the user
127
137
  // clicks save or not
128
- next: klona(this.items),
129
- original: klona(this.items),
138
+ next: klona(items),
139
+ original: klona(items),
130
140
  triggerValidation: false,
131
141
  minError: false,
132
142
  maxError: false,
@@ -287,9 +287,11 @@ module.exports = {
287
287
  };
288
288
  addCloneMethod(req);
289
289
  req.res.__ = req.__;
290
- const { _role, ...properties } = options || {};
290
+ const { role: _role, ...properties } = options || {};
291
+
291
292
  Object.assign(req, properties);
292
293
  self.apos.i18n.setPrefixUrls(req);
294
+
293
295
  return req;
294
296
 
295
297
  function addCloneMethod(req) {
@@ -34,7 +34,7 @@
34
34
  @item-clicked="$emit('item-clicked', item)"
35
35
  menu-placement="bottom-start"
36
36
  menu-offset="40, 10"
37
- disabled="disabled"
37
+ :disabled="disabled"
38
38
  />
39
39
  <AposButton
40
40
  class="apos-slat__editor-btn"
@@ -48,7 +48,7 @@
48
48
  :icon-only="true"
49
49
  :modifiers="['inline']"
50
50
  @click="$emit('item-clicked', item)"
51
- disabled="disabled"
51
+ :disabled="disabled"
52
52
  />
53
53
  <a
54
54
  class="apos-slat__control apos-slat__control--view"
@@ -169,7 +169,7 @@ export default {
169
169
  },
170
170
  computed: {
171
171
  itemSize() {
172
- const size = this.item.length.size;
172
+ const size = this.item.length?.size;
173
173
  if (size < 1000000) {
174
174
  return `${(size / 1000).toFixed(0)}KB`;
175
175
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "3.23.0",
3
+ "version": "3.24.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test/caches.js CHANGED
@@ -22,6 +22,11 @@ describe('Caches', function() {
22
22
  it('should allow us to store capuchin', async function() {
23
23
  await apos.cache.set('test', 'capuchin', { message: 'eek eek' });
24
24
  });
25
+ it('second cache can contain capuchin with a different value', async function() {
26
+ await apos.cache.set('test2', 'capuchin', { message: 'ook ook' });
27
+ assert.strictEqual((await apos.cache.get('test', 'capuchin')).message, 'eek eek');
28
+ assert.strictEqual((await apos.cache.get('test2', 'capuchin')).message, 'ook ook');
29
+ });
25
30
  it('should now contain capuchin', async function() {
26
31
  const monkey = await apos.cache.get('test', 'capuchin');
27
32
  assert(monkey);
@@ -33,4 +38,19 @@ describe('Caches', function() {
33
38
  it('should not contain capuchin anymore', async function() {
34
39
  assert(!(await apos.cache.get('test', 'capuchin')));
35
40
  });
41
+ it('but test2 cache still does contain capuchin', async function() {
42
+ assert.strictEqual((await apos.cache.get('test2', 'capuchin')).message, 'ook ook');
43
+ });
44
+ it('unique key index does block double insert in same namespace', async function() {
45
+ try {
46
+ await apos.cache.cacheCollection.insert({
47
+ name: 'test2',
48
+ key: 'capuchin'
49
+ });
50
+ // That's bad, we should be blocked
51
+ assert(false);
52
+ } catch (e) {
53
+ // That's good, we were blocked
54
+ }
55
+ });
36
56
  });
package/test/pages.js CHANGED
@@ -1229,10 +1229,11 @@ describe('Pages', function() {
1229
1229
  try {
1230
1230
  const publicUrl = generatePublicUrl(shareResponse);
1231
1231
  await apos.http.get(publicUrl, { fullResponse: true });
1232
- throw new Error('should have thrown 404 error');
1233
1232
  } catch (error) {
1234
1233
  assert(error.status === 404);
1234
+ return;
1235
1235
  }
1236
+ throw new Error('should have thrown 404 error');
1236
1237
  });
1237
1238
  });
1238
1239
  });
package/test/pieces.js CHANGED
@@ -1818,10 +1818,11 @@ describe('Pieces', function() {
1818
1818
  try {
1819
1819
  const publicUrl = generatePublicUrl(shareResponse);
1820
1820
  await apos.http.get(publicUrl, { fullResponse: true });
1821
- throw new Error('should have thrown 404 error');
1822
1821
  } catch (error) {
1823
1822
  assert(error.status === 404);
1823
+ return;
1824
1824
  }
1825
+ throw new Error('should have thrown 404 error');
1825
1826
  });
1826
1827
  });
1827
1828
  });
@@ -21,6 +21,10 @@ describe('static i18n', function() {
21
21
  en: {},
22
22
  fr: {
23
23
  prefix: '/fr'
24
+ },
25
+ es: {
26
+ prefix: '/es',
27
+ private: true
24
28
  }
25
29
  }
26
30
  }
@@ -88,4 +92,14 @@ describe('static i18n', function() {
88
92
  assert.strictEqual(browserData.i18n.en.custom.customTestOne, 'Custom Test One From Subtype');
89
93
  });
90
94
 
95
+ it('should return a 404 HTTP error code when a logged out user tries to access to a content in a private locale', async function() {
96
+ try {
97
+ await apos.http.get('/es');
98
+ } catch (error) {
99
+ assert(error.status === 404);
100
+ return;
101
+ }
102
+ throw new Error('should have thrown 404 error');
103
+ });
104
+
91
105
  });