@strapi/admin 4.12.7 → 4.13.0-alpha.1

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 (180) hide show
  1. package/admin/src/components/NpsSurvey/hooks/useNpsSurveySettings.js +17 -0
  2. package/admin/src/components/NpsSurvey/index.js +365 -0
  3. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
  4. package/admin/src/content-manager/components/DynamicZone/components/DynamicZoneLabel.js +1 -1
  5. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +7 -2
  6. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/cleanData.js +6 -0
  7. package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
  8. package/admin/src/content-manager/components/Filter/Filter.js +54 -0
  9. package/admin/src/content-manager/components/Filter/index.js +1 -0
  10. package/admin/src/content-manager/components/InputUID/index.js +222 -216
  11. package/admin/src/content-manager/components/RelationInput/RelationInput.js +4 -0
  12. package/admin/src/content-manager/components/RepeatableComponent/index.js +32 -2
  13. package/admin/src/content-manager/components/Wysiwyg/Editor.js +432 -68
  14. package/admin/src/content-manager/components/Wysiwyg/WysiwygStyles.js +0 -7
  15. package/admin/src/content-manager/components/Wysiwyg/index.js +147 -152
  16. package/admin/src/content-manager/constants/attributes.js +3 -0
  17. package/admin/src/content-manager/hooks/useAllowedAttributes.js +43 -0
  18. package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
  19. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
  20. package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
  21. package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
  22. package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +80 -0
  23. package/admin/src/content-manager/pages/ListView/index.js +236 -67
  24. package/admin/src/content-manager/utils/getDisplayName.js +33 -0
  25. package/admin/src/content-manager/utils/index.js +1 -0
  26. package/admin/src/pages/Admin/index.js +3 -1
  27. package/admin/src/pages/AuthPage/components/Register/index.js +5 -0
  28. package/admin/src/pages/SettingsPage/components/SettingsNav/index.js +3 -3
  29. package/admin/src/pages/SettingsPage/index.js +16 -26
  30. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/index.js +69 -35
  31. package/admin/src/plugins.js +7 -8
  32. package/admin/src/translations/en.json +10 -1
  33. package/build/{1049.f76cb14b.chunk.js → 1049.ec69f5e0.chunk.js} +1 -1
  34. package/build/1227.9f37e1dc.chunk.js +1 -0
  35. package/build/{1386.879bcd90.chunk.js → 1386.ea73b677.chunk.js} +1 -1
  36. package/build/1504.eff012f7.chunk.js +95 -0
  37. package/build/{2225.c6244756.chunk.js → 2225.649fb7bc.chunk.js} +11 -11
  38. package/build/2237.b832ae6e.chunk.js +114 -0
  39. package/build/2379.1f98a31a.chunk.js +1 -0
  40. package/build/2395.0e5e8ded.chunk.js +26 -0
  41. package/build/2801.8e1aa82a.chunk.js +1 -0
  42. package/build/{3483.03c24f96.chunk.js → 3483.19381b40.chunk.js} +1 -1
  43. package/build/4174.f1f39e40.chunk.js +1 -0
  44. package/build/4546.a5946d22.chunk.js +1 -0
  45. package/build/4724.aea5c8c1.chunk.js +6 -0
  46. package/build/502.7bba43b1.chunk.js +1 -0
  47. package/build/6158.c3c13c20.chunk.js +1 -0
  48. package/build/7464.eb057bec.chunk.js +1 -0
  49. package/build/78.dcc6df5c.chunk.js +1 -0
  50. package/build/8276.be3ed581.chunk.js +26 -0
  51. package/build/{Admin-authenticatedApp.31497f74.chunk.js → Admin-authenticatedApp.36b0fe22.chunk.js} +2 -2
  52. package/build/{Admin_InternalErrorPage.f45f2462.chunk.js → Admin_InternalErrorPage.38155af3.chunk.js} +1 -1
  53. package/build/{Admin_homePage.ac9dfb86.chunk.js → Admin_homePage.6f128523.chunk.js} +1 -1
  54. package/build/{Admin_marketplace.c94239f6.chunk.js → Admin_marketplace.061a6e5a.chunk.js} +1 -1
  55. package/build/{Admin_pluginsPage.bbe79434.chunk.js → Admin_pluginsPage.16f837b8.chunk.js} +1 -1
  56. package/build/{Admin_profilePage.192edc52.chunk.js → Admin_profilePage.678bce24.chunk.js} +2 -2
  57. package/build/Admin_settingsPage.af7309e4.chunk.js +111 -0
  58. package/build/{Upload_ConfigureTheView.345ac1e0.chunk.js → Upload_ConfigureTheView.3fc1c100.chunk.js} +1 -1
  59. package/build/admin-app.d63bd229.chunk.js +36 -0
  60. package/build/{admin-edit-roles-page.6d567273.chunk.js → admin-edit-roles-page.38a6c863.chunk.js} +3 -3
  61. package/build/{admin-edit-users.acfd4128.chunk.js → admin-edit-users.545fc882.chunk.js} +2 -2
  62. package/build/{admin-roles-list.23ddff26.chunk.js → admin-roles-list.1e2e814d.chunk.js} +1 -1
  63. package/build/{admin-users.00e20017.chunk.js → admin-users.b8ea5677.chunk.js} +2 -2
  64. package/build/{api-tokens-create-page.46c2ea84.chunk.js → api-tokens-create-page.e0c15627.chunk.js} +1 -1
  65. package/build/{api-tokens-edit-page.58139df9.chunk.js → api-tokens-edit-page.9f2dce47.chunk.js} +1 -1
  66. package/build/{api-tokens-list-page.0af7d431.chunk.js → api-tokens-list-page.d747051c.chunk.js} +2 -2
  67. package/build/{audit-logs-settings-page.0f73ccf8.chunk.js → audit-logs-settings-page.96f9d608.chunk.js} +1 -1
  68. package/build/content-manager.2d676432.chunk.js +1097 -0
  69. package/build/{content-type-builder-list-view.bf9be456.chunk.js → content-type-builder-list-view.b71cf240.chunk.js} +1 -1
  70. package/build/{content-type-builder.cd999f6e.chunk.js → content-type-builder.e5669749.chunk.js} +2 -2
  71. package/build/email-settings-page.2809f0bf.chunk.js +11 -0
  72. package/build/en-json.e12fd5fc.chunk.js +1 -0
  73. package/build/{i18n-settings-page.47f78016.chunk.js → i18n-settings-page.5f716172.chunk.js} +1 -1
  74. package/build/index.html +1 -1
  75. package/build/main.c6c9e04c.js +2859 -0
  76. package/build/review-workflows-settings-create-view.4a156a19.chunk.js +1 -0
  77. package/build/review-workflows-settings-edit-view.ce984d1f.chunk.js +1 -0
  78. package/build/review-workflows-settings-list-view.419b8deb.chunk.js +56 -0
  79. package/build/{runtime~main.d515c521.js → runtime~main.5a10b789.js} +2 -2
  80. package/build/{sso-settings-page.12b6d8ae.chunk.js → sso-settings-page.45153df5.chunk.js} +1 -1
  81. package/build/{transfer-tokens-create-page.1597e6ab.chunk.js → transfer-tokens-create-page.ebba16d8.chunk.js} +1 -1
  82. package/build/{transfer-tokens-edit-page.8741529f.chunk.js → transfer-tokens-edit-page.d7bb2b3e.chunk.js} +1 -1
  83. package/build/{transfer-tokens-list-page.d6986b03.chunk.js → transfer-tokens-list-page.cfe1736c.chunk.js} +2 -2
  84. package/build/{upload-settings.7f93d4c0.chunk.js → upload-settings.cc5ad813.chunk.js} +1 -1
  85. package/build/{upload.37488080.chunk.js → upload.756efc28.chunk.js} +1 -1
  86. package/build/users-advanced-settings-page.818d84eb.chunk.js +9 -0
  87. package/build/users-email-settings-page.c1967c09.chunk.js +9 -0
  88. package/build/users-providers-settings-page.11893e08.chunk.js +14 -0
  89. package/build/users-roles-settings-page.2b051e6a.chunk.js +55 -0
  90. package/build/{webhook-edit-page.6cb479ff.chunk.js → webhook-edit-page.de45c635.chunk.js} +2 -2
  91. package/build/{webhook-list-page.65e1b5bb.chunk.js → webhook-list-page.ca91df8b.chunk.js} +1 -1
  92. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
  93. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
  94. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
  95. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
  96. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +147 -0
  97. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
  98. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +243 -0
  99. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
  100. package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
  101. package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
  102. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +21 -0
  103. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
  104. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  105. package/ee/admin/hooks/useAuthProviders.js +25 -0
  106. package/ee/admin/hooks/{useLicenseLimitNotification/index.js → useLicenseLimitNotification.js} +2 -4
  107. package/ee/admin/hooks/{useLicenseLimits/useLicenseLimits.js → useLicenseLimits.js} +4 -1
  108. package/ee/admin/pages/AuthPage/components/Login/index.js +8 -4
  109. package/ee/admin/pages/AuthPage/components/Providers/index.js +8 -5
  110. package/ee/admin/pages/HomePage/index.js +1 -1
  111. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/CreateView/CreateView.js +1 -1
  112. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/EditView/EditView.js +1 -1
  113. package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/pages/ListView/ListView.js +1 -1
  114. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/CreateAction/index.js +1 -1
  115. package/ee/admin/pages/SettingsPage/pages/Users/ListPage/index.js +1 -1
  116. package/ee/server/constants/workflows.js +1 -0
  117. package/ee/server/controllers/index.js +1 -0
  118. package/ee/server/controllers/workflows/assignees/index.js +44 -0
  119. package/ee/server/routes/review-workflows.js +17 -0
  120. package/ee/server/services/index.js +1 -0
  121. package/ee/server/services/review-workflows/assignees.js +54 -0
  122. package/ee/server/services/review-workflows/metrics/index.js +5 -0
  123. package/ee/server/services/review-workflows/review-workflows.js +20 -11
  124. package/ee/server/validation/review-workflows.js +8 -0
  125. package/index.js +2 -6
  126. package/package.json +9 -9
  127. package/scripts/build.js +15 -15
  128. package/scripts/create-dev-plugins-file.js +5 -38
  129. package/server/controllers/role.js +2 -0
  130. package/server/controllers/user.js +2 -0
  131. package/server/services/permission/permissions-manager/index.js +3 -1
  132. package/server/services/permission/permissions-manager/sanitize.js +19 -7
  133. package/server/services/permission/permissions-manager/validate.js +218 -0
  134. package/utils/create-cache-dir.js +62 -16
  135. package/utils/create-plugins-exclude-path.js +3 -23
  136. package/utils/get-plugins.js +110 -0
  137. package/utils/index.js +1 -1
  138. package/webpack.config.js +10 -13
  139. package/admin/src/content-manager/components/AttributeFilter/Filters.js +0 -58
  140. package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
  141. package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
  142. package/admin/src/content-manager/components/Wysiwyg/EditorStylesContainer.js +0 -344
  143. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/api.js +0 -23
  144. package/admin/src/pages/SettingsPage/pages/ApplicationInfosPage/utils/prefixAllUrls.js +0 -17
  145. package/admin/src/pages/SettingsPage/utils/createSectionsRoutes.js +0 -11
  146. package/admin/src/pages/SettingsPage/utils/getSectionsToDisplay.js +0 -5
  147. package/admin/src/pages/SettingsPage/utils/index.js +0 -2
  148. package/build/2379.f1641312.chunk.js +0 -1
  149. package/build/2395.46f8d0c1.chunk.js +0 -26
  150. package/build/2801.5cef5ec8.chunk.js +0 -1
  151. package/build/3929.5632f24d.chunk.js +0 -114
  152. package/build/3984.dda474f7.chunk.js +0 -1
  153. package/build/4546.7a3c0d03.chunk.js +0 -1
  154. package/build/502.8ae8ef60.chunk.js +0 -1
  155. package/build/5483.6dd2e776.chunk.js +0 -6
  156. package/build/5542.2415a393.chunk.js +0 -63
  157. package/build/6158.c974fd83.chunk.js +0 -1
  158. package/build/7464.3e64a1d5.chunk.js +0 -1
  159. package/build/8276.10a3f883.chunk.js +0 -26
  160. package/build/Admin_settingsPage.97cb9d41.chunk.js +0 -111
  161. package/build/admin-app.91898385.chunk.js +0 -36
  162. package/build/content-manager.b40f79c0.chunk.js +0 -1099
  163. package/build/email-settings-page.d494d1eb.chunk.js +0 -11
  164. package/build/en-json.08c05fcf.chunk.js +0 -1
  165. package/build/main.9dbe4579.js +0 -2859
  166. package/build/review-workflows-settings-create-view.cb08cfa2.chunk.js +0 -1
  167. package/build/review-workflows-settings-edit-view.3c7cbe63.chunk.js +0 -1
  168. package/build/review-workflows-settings-list-view.1611dc1f.chunk.js +0 -56
  169. package/build/users-advanced-settings-page.f0760eb8.chunk.js +0 -9
  170. package/build/users-email-settings-page.ff4b32f3.chunk.js +0 -9
  171. package/build/users-providers-settings-page.48de0306.chunk.js +0 -14
  172. package/build/users-roles-settings-page.9d9a1eff.chunk.js +0 -30
  173. package/ee/admin/hooks/index.js +0 -4
  174. package/ee/admin/hooks/useAuthProviders/index.js +0 -50
  175. package/ee/admin/hooks/useAuthProviders/reducer.js +0 -26
  176. package/ee/admin/hooks/useLicenseLimits/index.js +0 -1
  177. package/scripts/create-plugins-file.js +0 -92
  178. package/utils/get-plugins-path.js +0 -41
  179. /package/ee/admin/hooks/{useLicenseLimits/__mocks__/index.js → __mocks__/useLicenseLimits.js} +0 -0
  180. /package/server/services/permission/permissions-manager/{query-builers.js → query-builders.js} +0 -0
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+
3
+ const { subject: asSubject, detectSubjectType } = require('@casl/ability');
4
+ const { permittedFieldsOf } = require('@casl/ability/extra');
5
+ const {
6
+ defaults,
7
+ omit,
8
+ isArray,
9
+ isEmpty,
10
+ isNil,
11
+ flatMap,
12
+ some,
13
+ prop,
14
+ uniq,
15
+ intersection,
16
+ getOr,
17
+ isObject,
18
+ } = require('lodash/fp');
19
+
20
+ const {
21
+ contentTypes,
22
+ traverseEntity,
23
+ traverse,
24
+ validate,
25
+ pipeAsync,
26
+ errors: { ValidationError },
27
+ } = require('@strapi/utils');
28
+
29
+ const { throwPassword, throwDisallowedFields } = validate.visitors;
30
+ const { ADMIN_USER_ALLOWED_FIELDS } = require('../../../domain/user');
31
+
32
+ const { constants, isScalarAttribute, getNonVisibleAttributes, getWritableAttributes } =
33
+ contentTypes;
34
+ const {
35
+ ID_ATTRIBUTE,
36
+ CREATED_AT_ATTRIBUTE,
37
+ UPDATED_AT_ATTRIBUTE,
38
+ PUBLISHED_AT_ATTRIBUTE,
39
+ CREATED_BY_ATTRIBUTE,
40
+ UPDATED_BY_ATTRIBUTE,
41
+ } = constants;
42
+
43
+ const COMPONENT_FIELDS = ['__component'];
44
+
45
+ const STATIC_FIELDS = [ID_ATTRIBUTE];
46
+
47
+ const throwInvalidParam = ({ key }) => {
48
+ throw new ValidationError(`Invalid parameter ${key}`);
49
+ };
50
+
51
+ module.exports = ({ action, ability, model }) => {
52
+ const schema = strapi.getModel(model);
53
+
54
+ const createValidateQuery = (options = {}) => {
55
+ const { fields } = options;
56
+
57
+ // TODO: validate relations to admin users in all validators
58
+ const permittedFields = fields.shouldIncludeAll ? null : getQueryFields(fields.permitted);
59
+
60
+ const validateFilters = pipeAsync(
61
+ traverse.traverseQueryFilters(throwDisallowedFields(permittedFields), { schema }),
62
+ traverse.traverseQueryFilters(throwDisallowedAdminUserFields, { schema }),
63
+ traverse.traverseQueryFilters(throwPassword, { schema }),
64
+ traverse.traverseQueryFilters(
65
+ ({ key, value }) => {
66
+ if (isObject(value) && isEmpty(value)) {
67
+ throwInvalidParam({ key });
68
+ }
69
+ },
70
+ { schema }
71
+ )
72
+ );
73
+
74
+ const validateSort = pipeAsync(
75
+ traverse.traverseQuerySort(throwDisallowedFields(permittedFields), { schema }),
76
+ traverse.traverseQuerySort(throwDisallowedAdminUserFields, { schema }),
77
+ traverse.traverseQuerySort(throwPassword, { schema }),
78
+ traverse.traverseQuerySort(
79
+ ({ key, attribute, value }) => {
80
+ if (!isScalarAttribute(attribute) && isEmpty(value)) {
81
+ throwInvalidParam({ key });
82
+ }
83
+ },
84
+ { schema }
85
+ )
86
+ );
87
+
88
+ const validateFields = pipeAsync(
89
+ traverse.traverseQueryFields(throwDisallowedFields(permittedFields), { schema }),
90
+ traverse.traverseQueryFields(throwPassword, { schema })
91
+ );
92
+
93
+ return async (query) => {
94
+ if (query.filters) {
95
+ await validateFilters(query.filters);
96
+ }
97
+
98
+ if (query.sort) {
99
+ await validateSort(query.sort);
100
+ }
101
+
102
+ if (query.fields) {
103
+ await validateFields(query.fields);
104
+ }
105
+
106
+ return true;
107
+ };
108
+ };
109
+
110
+ const createValidateInput = (options = {}) => {
111
+ const { fields } = options;
112
+
113
+ const permittedFields = fields.shouldIncludeAll ? null : getInputFields(fields.permitted);
114
+
115
+ return pipeAsync(
116
+ // Remove fields hidden from the admin
117
+ traverseEntity(throwHiddenFields, { schema }),
118
+ // Remove not allowed fields (RBAC)
119
+ traverseEntity(throwDisallowedFields(permittedFields), { schema }),
120
+ // Remove roles from createdBy & updatedBy fields
121
+ omitCreatorRoles
122
+ );
123
+ };
124
+
125
+ const wrapValidate = (createValidateFunction) => {
126
+ const wrappedValidate = async (data, options = {}) => {
127
+ if (isArray(data)) {
128
+ return Promise.all(data.map((entity) => wrappedValidate(entity, options)));
129
+ }
130
+
131
+ const { subject, action: actionOverride } = getDefaultOptions(data, options);
132
+
133
+ const permittedFields = permittedFieldsOf(ability, actionOverride, subject, {
134
+ fieldsFrom: (rule) => rule.fields || [],
135
+ });
136
+
137
+ const hasAtLeastOneRegistered = some(
138
+ (fields) => !isNil(fields),
139
+ flatMap(prop('fields'), ability.rulesFor(actionOverride, detectSubjectType(subject)))
140
+ );
141
+ const shouldIncludeAllFields = isEmpty(permittedFields) && !hasAtLeastOneRegistered;
142
+
143
+ const validateOptions = {
144
+ ...options,
145
+ fields: {
146
+ shouldIncludeAll: shouldIncludeAllFields,
147
+ permitted: permittedFields,
148
+ hasAtLeastOneRegistered,
149
+ },
150
+ };
151
+
152
+ const validateFunction = createValidateFunction(validateOptions);
153
+
154
+ return validateFunction(data);
155
+ };
156
+
157
+ return wrappedValidate;
158
+ };
159
+
160
+ const getDefaultOptions = (data, options) => {
161
+ return defaults({ subject: asSubject(model, data), action }, options);
162
+ };
163
+
164
+ /**
165
+ * Omit creator fields' (createdBy & updatedBy) roles from the admin API responses
166
+ */
167
+ const omitCreatorRoles = omit([`${CREATED_BY_ATTRIBUTE}.roles`, `${UPDATED_BY_ATTRIBUTE}.roles`]);
168
+
169
+ /**
170
+ * Visitor used to remove hidden fields from the admin API responses
171
+ */
172
+ const throwHiddenFields = ({ key, schema }) => {
173
+ const isHidden = getOr(false, ['config', 'attributes', key, 'hidden'], schema);
174
+
175
+ if (isHidden) {
176
+ throwInvalidParam({ key });
177
+ }
178
+ };
179
+
180
+ /**
181
+ * Visitor used to omit disallowed fields from the admin users entities & avoid leaking sensitive information
182
+ */
183
+ const throwDisallowedAdminUserFields = ({ key, attribute, schema }) => {
184
+ if (schema.uid === 'admin::user' && attribute && !ADMIN_USER_ALLOWED_FIELDS.includes(key)) {
185
+ throwInvalidParam({ key });
186
+ }
187
+ };
188
+
189
+ const getInputFields = (fields = []) => {
190
+ const nonVisibleAttributes = getNonVisibleAttributes(schema);
191
+ const writableAttributes = getWritableAttributes(schema);
192
+
193
+ const nonVisibleWritableAttributes = intersection(nonVisibleAttributes, writableAttributes);
194
+
195
+ return uniq([
196
+ ...fields,
197
+ ...STATIC_FIELDS,
198
+ ...COMPONENT_FIELDS,
199
+ ...nonVisibleWritableAttributes,
200
+ ]);
201
+ };
202
+
203
+ const getQueryFields = (fields = []) => {
204
+ return uniq([
205
+ ...fields,
206
+ ...STATIC_FIELDS,
207
+ ...COMPONENT_FIELDS,
208
+ CREATED_AT_ATTRIBUTE,
209
+ UPDATED_AT_ATTRIBUTE,
210
+ PUBLISHED_AT_ATTRIBUTE,
211
+ ]);
212
+ };
213
+
214
+ return {
215
+ validateQuery: wrapValidate(createValidateQuery),
216
+ validateInput: wrapValidate(createValidateInput),
217
+ };
218
+ };
@@ -9,20 +9,29 @@ const getCustomAppConfigFile = require('./get-custom-app-config-file');
9
9
  const getPkgPath = (name) => path.dirname(require.resolve(`${name}/package.json`));
10
10
 
11
11
  async function createPluginsJs(plugins, dest) {
12
- const pluginsArray = plugins.map(({ pathToPlugin, name }) => {
12
+ const pluginsArray = plugins.map(({ pathToPlugin, name, info }) => {
13
13
  const shortName = _.camelCase(name);
14
14
 
15
+ let realPath = '';
16
+
15
17
  /**
16
- * path.join, on windows, it uses backslashes to resolve path.
17
- * The problem is that Webpack does not windows paths
18
- * With this tool, we need to rely on "/" and not "\".
19
- * This is the reason why '..\\..\\..\\node_modules\\@strapi\\plugin-content-type-builder/strapi-admin.js' was not working.
20
- * The regexp at line 105 aims to replace the windows backslashes by standard slash so that webpack can deal with them.
21
- * Backslash looks to work only for absolute paths with webpack => https://webpack.js.org/concepts/module-resolution/#absolute-paths
18
+ * We're using a module here so we want to keep using the module resolution procedure.
22
19
  */
23
- const realPath = path
24
- .join(path.relative(path.resolve(dest, 'admin', 'src'), pathToPlugin), 'strapi-admin.js')
25
- .replace(/\\/g, '/');
20
+ if (info?.packageName || info?.required) {
21
+ /**
22
+ * path.join, on windows, it uses backslashes to resolve path.
23
+ * The problem is that Webpack does not windows paths
24
+ * With this tool, we need to rely on "/" and not "\".
25
+ * This is the reason why '..\\..\\..\\node_modules\\@strapi\\plugin-content-type-builder/strapi-admin.js' was not working.
26
+ * The regexp at line 105 aims to replace the windows backslashes by standard slash so that webpack can deal with them.
27
+ * Backslash looks to work only for absolute paths with webpack => https://webpack.js.org/concepts/module-resolution/#absolute-paths
28
+ */
29
+ realPath = path.join(pathToPlugin, 'strapi-admin').replace(/\\/g, '/');
30
+ } else {
31
+ realPath = path
32
+ .join(path.relative(path.resolve(dest, 'admin', 'src'), pathToPlugin), 'strapi-admin')
33
+ .replace(/\\/g, '/');
34
+ }
26
35
 
27
36
  return {
28
37
  name,
@@ -76,12 +85,49 @@ async function createCacheDir({ appDir, plugins }) {
76
85
  'tsconfig.json'
77
86
  );
78
87
 
79
- const pluginsWithFront = Object.keys(plugins)
80
- .filter((pluginName) => {
81
- const pluginInfo = plugins[pluginName];
82
- return fs.existsSync(path.resolve(pluginInfo.pathToPlugin, 'strapi-admin.js'));
88
+ const pluginsWithFront = Object.entries(plugins)
89
+ .filter(([, plugin]) => {
90
+ /**
91
+ * There are two ways a plugin should be imported, either it's local to the strapi app,
92
+ * or it's an actual npm module that's installed and resolved via node_modules.
93
+ *
94
+ * We first check if the plugin is local to the strapi app, using a regular `resolve` because
95
+ * the pathToPlugin will be relative i.e. `/Users/my-name/strapi-app/src/plugins/my-plugin`.
96
+ *
97
+ * If the file doesn't exist well then it's probably a node_module, so instead we use `require.resolve`
98
+ * which will resolve the path to the module in node_modules. If it fails with the specific code `MODULE_NOT_FOUND`
99
+ * then it doesn't have an admin part to the package.
100
+ *
101
+ * NOTE: we should try to move to `./package.json[exports]` map with bundling of our own plugins,
102
+ * because these entry files are written in commonjs restricting features e.g. tree-shaking.
103
+ */
104
+ try {
105
+ const isLocalPluginWithLegacyAdminFile = fs.existsSync(
106
+ path.resolve(`${plugin.pathToPlugin}/strapi-admin.js`)
107
+ );
108
+
109
+ if (!isLocalPluginWithLegacyAdminFile) {
110
+ const isModulewithLegacyAdminFile = require.resolve(
111
+ `${plugin.pathToPlugin}/strapi-admin.js`
112
+ );
113
+
114
+ return isModulewithLegacyAdminFile;
115
+ }
116
+
117
+ return isLocalPluginWithLegacyAdminFile;
118
+ } catch (err) {
119
+ if (err.code === 'MODULE_NOT_FOUND') {
120
+ /**
121
+ * the plugin does not contain FE code, so we
122
+ * don't want to import it anyway
123
+ */
124
+ return false;
125
+ }
126
+
127
+ throw err;
128
+ }
83
129
  })
84
- .map((name) => ({ name, ...plugins[name] }));
130
+ .map(([name, plugin]) => ({ name, ...plugin }));
85
131
 
86
132
  // create .cache dir
87
133
  await fs.emptyDir(cacheDir);
@@ -128,4 +174,4 @@ async function createCacheDir({ appDir, plugins }) {
128
174
  }
129
175
  }
130
176
 
131
- module.exports = createCacheDir;
177
+ module.exports = { createCacheDir, createPluginsJs };
@@ -1,40 +1,20 @@
1
1
  'use strict';
2
2
 
3
- const { sep, join } = require('path');
4
-
5
3
  const NODE_MODULES = 'node_modules';
6
4
  /**
7
5
  * @param {string[]} pluginsPath – an array of paths to the plugins from the user's directory
8
6
  * @returns {RegExp} a regex that will exclude _all_ node_modules except for the plugins in the pluginsPath array.
9
7
  */
10
8
  const createPluginsExcludePath = (pluginsPath = []) => {
11
- /**
12
- * converts the full path to just the plugin path
13
- * e.g. `/Users/username/strapi/node_modules/@scope/plugin-name`
14
- * to `@scope/plugin-name`
15
- */
16
- const tsxPlugins = pluginsPath.reduce((acc, curr) => {
17
- const dirPaths = curr.split(sep);
18
-
19
- const nodeModulePathIndex = dirPaths.findIndex((val) => val === NODE_MODULES);
20
-
21
- if (nodeModulePathIndex > 0) {
22
- const pluginNodeModulePath = dirPaths.slice(nodeModulePathIndex + 1);
23
- return [...acc, join(...pluginNodeModulePath)];
24
- }
25
-
26
- return acc;
27
- }, []);
28
-
29
9
  /**
30
10
  * If there aren't any plugins in the node_modules array, just return the node_modules regex
31
11
  * without complicating it.
32
12
  */
33
- if (tsxPlugins.length === 0) {
13
+ if (pluginsPath.length === 0) {
34
14
  return /node_modules/;
35
15
  }
36
16
 
37
- return new RegExp(`${NODE_MODULES}/(?!(${tsxPlugins.join('|')}))`);
17
+ return new RegExp(`${NODE_MODULES}/(?!(${pluginsPath.join('|')}))`);
38
18
  };
39
19
 
40
- module.exports = createPluginsExcludePath;
20
+ module.exports = { createPluginsExcludePath };
@@ -0,0 +1,110 @@
1
+ 'use strict';
2
+
3
+ const { join, resolve, sep, posix } = require('path');
4
+ const fs = require('fs');
5
+ // eslint-disable-next-line import/no-extraneous-dependencies
6
+ const glob = require('glob');
7
+
8
+ const getPlugins = (pluginsAllowlist) => {
9
+ const rootPath = resolve(__dirname, '..', join('..', '..', '..', 'packages'));
10
+ /**
11
+ * So `glob` only supports '/' as a path separator, so we need to replace
12
+ * the path separator for the current OS with '/'. e.g. on windows it's `\`.
13
+ *
14
+ * see https://github.com/isaacs/node-glob/#windows for more information
15
+ *
16
+ * and see https://github.com/isaacs/node-glob/issues/467#issuecomment-1114240501 for the recommended fix.
17
+ */
18
+ let corePath = join(rootPath, 'core', '*');
19
+ let pluginsPath = join(rootPath, 'plugins', '*');
20
+
21
+ if (process.platform === 'win32') {
22
+ corePath = corePath.split(sep).join(posix.sep);
23
+ pluginsPath = pluginsPath.split(sep).join(posix.sep);
24
+ }
25
+
26
+ const corePackageDirs = glob.sync(corePath);
27
+ const pluginsPackageDirs = glob.sync(pluginsPath);
28
+
29
+ const plugins = [...corePackageDirs, ...pluginsPackageDirs]
30
+ .map((directory) => {
31
+ const isCoreAdmin = directory.includes('packages/core/admin');
32
+
33
+ if (isCoreAdmin) {
34
+ return null;
35
+ }
36
+
37
+ const { name, strapi } = require(join(directory, 'package.json'));
38
+
39
+ /**
40
+ * this will remove any of our packages that are
41
+ * not actually plugins for the application
42
+ */
43
+ if (!strapi) {
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * we want the name of the node_module
49
+ */
50
+ return {
51
+ pathToPlugin: name,
52
+ name: strapi.name,
53
+ info: { ...strapi, packageName: name },
54
+ directory,
55
+ };
56
+ })
57
+ .filter((plugin) => {
58
+ if (!plugin) {
59
+ return false;
60
+ }
61
+
62
+ /**
63
+ * There are two ways a plugin should be imported, either it's local to the strapi app,
64
+ * or it's an actual npm module that's installed and resolved via node_modules.
65
+ *
66
+ * We first check if the plugin is local to the strapi app, using a regular `resolve` because
67
+ * the pathToPlugin will be relative i.e. `/Users/my-name/strapi-app/src/plugins/my-plugin`.
68
+ *
69
+ * If the file doesn't exist well then it's probably a node_module, so instead we use `require.resolve`
70
+ * which will resolve the path to the module in node_modules. If it fails with the specific code `MODULE_NOT_FOUND`
71
+ * then it doesn't have an admin part to the package.
72
+ *
73
+ * NOTE: we should try to move to `./package.json[exports]` map with bundling of our own plugins,
74
+ * because these entry files are written in commonjs restricting features e.g. tree-shaking.
75
+ */
76
+ try {
77
+ const isLocalPluginWithLegacyAdminFile = fs.existsSync(
78
+ resolve(`${plugin.pathToPlugin}/strapi-admin.js`)
79
+ );
80
+
81
+ if (!isLocalPluginWithLegacyAdminFile) {
82
+ const isModulewithLegacyAdminFile = require.resolve(
83
+ `${plugin.pathToPlugin}/strapi-admin.js`
84
+ );
85
+
86
+ return isModulewithLegacyAdminFile;
87
+ }
88
+
89
+ return isLocalPluginWithLegacyAdminFile;
90
+ } catch (err) {
91
+ if (err.code === 'MODULE_NOT_FOUND') {
92
+ /**
93
+ * the plugin does not contain FE code, so we
94
+ * don't want to import it anyway
95
+ */
96
+ return false;
97
+ }
98
+
99
+ throw err;
100
+ }
101
+ });
102
+
103
+ if (Array.isArray(pluginsAllowlist)) {
104
+ return plugins.filter((plugin) => pluginsAllowlist.includes(plugin.pathToPlugin));
105
+ }
106
+
107
+ return plugins;
108
+ };
109
+
110
+ module.exports = { getPlugins };
package/utils/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const createCacheDir = require('./create-cache-dir');
3
+ const { createCacheDir } = require('./create-cache-dir');
4
4
  const getCustomWebpackConfig = require('./get-custom-webpack-config');
5
5
  const shouldBuildAdmin = require('./should-build-admin');
6
6
  const watchAdminFiles = require('./watch-admin-files');
package/webpack.config.js CHANGED
@@ -4,24 +4,23 @@ const path = require('path');
4
4
  const webpack = require('webpack');
5
5
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
6
6
  const ForkTsCheckerPlugin = require('fork-ts-checker-webpack-plugin');
7
+ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
7
8
  const HtmlWebpackPlugin = require('html-webpack-plugin');
8
9
  const { ESBuildMinifyPlugin } = require('esbuild-loader');
9
10
  const WebpackBar = require('webpackbar');
10
- const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
11
11
  const browserslist = require('browserslist');
12
12
  const browserslistToEsbuild = require('browserslist-to-esbuild');
13
13
 
14
14
  const alias = require('./webpack.alias');
15
15
  const getClientEnvironment = require('./env');
16
- const createPluginsExcludePath = require('./utils/create-plugins-exclude-path');
16
+ const { createPluginsExcludePath } = require('./utils/create-plugins-exclude-path');
17
17
 
18
18
  module.exports = ({
19
- cacheDir,
20
19
  dest,
21
20
  entry,
22
21
  env,
23
22
  optimize,
24
- pluginsPath,
23
+ plugins,
25
24
  options = {
26
25
  backend: 'http://localhost:1337',
27
26
  adminPath: '/admin/',
@@ -45,7 +44,11 @@ module.exports = ({
45
44
  ]
46
45
  : [];
47
46
 
48
- const excludeRegex = createPluginsExcludePath(pluginsPath);
47
+ const nodeModulePluginPaths = Object.values(plugins)
48
+ .filter((plugin) => plugin.info?.packageName || plugin.info?.required)
49
+ .map((plugin) => plugin.pathToPlugin);
50
+
51
+ const excludeRegex = createPluginsExcludePath(nodeModulePluginPaths);
49
52
 
50
53
  // Ensure we use the config in this directory, even if run with a different
51
54
  // working directory
@@ -84,7 +87,6 @@ module.exports = ({
84
87
  {
85
88
  test: /\.tsx?$/,
86
89
  loader: require.resolve('esbuild-loader'),
87
- include: [cacheDir, ...pluginsPath],
88
90
  exclude: excludeRegex,
89
91
  options: {
90
92
  loader: 'tsx',
@@ -92,8 +94,8 @@ module.exports = ({
92
94
  },
93
95
  },
94
96
  {
95
- test: /\.m?jsx?$/,
96
- include: [cacheDir, ...pluginsPath],
97
+ test: /\.(js|jsx|mjs)$/,
98
+ exclude: excludeRegex,
97
99
  use: {
98
100
  loader: require.resolve('esbuild-loader'),
99
101
  options: {
@@ -150,10 +152,7 @@ module.exports = ({
150
152
  },
151
153
  resolve: {
152
154
  alias,
153
- symlinks: false,
154
155
  extensions: ['.js', '.jsx', '.react.js', '.ts', '.tsx'],
155
- mainFields: ['browser', 'module', 'jsnext:main', 'main'],
156
- modules: ['node_modules', path.resolve(__dirname, 'node_modules')],
157
156
  },
158
157
  plugins: [
159
158
  new HtmlWebpackPlugin({
@@ -167,9 +166,7 @@ module.exports = ({
167
166
  configFile: tsConfigFilePath,
168
167
  },
169
168
  }),
170
-
171
169
  !isProduction && process.env.REACT_REFRESH !== 'false' && new ReactRefreshWebpackPlugin(),
172
-
173
170
  ...webpackPlugins,
174
171
  ].filter(Boolean),
175
172
  };
@@ -1,58 +0,0 @@
1
- import React, { useRef, useState } from 'react';
2
-
3
- import { Box, Button } from '@strapi/design-system';
4
- import { FilterListURLQuery, FilterPopoverURLQuery, useTracking } from '@strapi/helper-plugin';
5
- import { Filter } from '@strapi/icons';
6
- import PropTypes from 'prop-types';
7
- import { useIntl } from 'react-intl';
8
-
9
- const Filters = ({ displayedFilters }) => {
10
- const [isVisible, setIsVisible] = useState(false);
11
- const { formatMessage } = useIntl();
12
- const buttonRef = useRef();
13
- const { trackUsage } = useTracking();
14
-
15
- const handleToggle = () => {
16
- if (!isVisible) {
17
- trackUsage('willFilterEntries');
18
- }
19
- setIsVisible((prev) => !prev);
20
- };
21
-
22
- return (
23
- <>
24
- <Box paddingTop={1} paddingBottom={1}>
25
- <Button
26
- variant="tertiary"
27
- ref={buttonRef}
28
- startIcon={<Filter />}
29
- onClick={handleToggle}
30
- size="S"
31
- >
32
- {formatMessage({ id: 'app.utils.filters', defaultMessage: 'Filters' })}
33
- </Button>
34
- {isVisible && (
35
- <FilterPopoverURLQuery
36
- displayedFilters={displayedFilters}
37
- isVisible={isVisible}
38
- onToggle={handleToggle}
39
- source={buttonRef}
40
- />
41
- )}
42
- </Box>
43
- <FilterListURLQuery filtersSchema={displayedFilters} />
44
- </>
45
- );
46
- };
47
-
48
- Filters.propTypes = {
49
- displayedFilters: PropTypes.arrayOf(
50
- PropTypes.shape({
51
- name: PropTypes.string.isRequired,
52
- metadatas: PropTypes.shape({ label: PropTypes.string }),
53
- fieldSchema: PropTypes.shape({ type: PropTypes.string }),
54
- })
55
- ).isRequired,
56
- };
57
-
58
- export default Filters;
@@ -1,42 +0,0 @@
1
- import { findMatchingPermissions, useRBACProvider } from '@strapi/helper-plugin';
2
- import get from 'lodash/get';
3
-
4
- const NOT_ALLOWED_FILTERS = ['json', 'component', 'media', 'richtext', 'dynamiczone', 'password'];
5
- const TIMESTAMPS = ['createdAt', 'updatedAt'];
6
-
7
- const useAllowedAttributes = (contentType, slug) => {
8
- const { allPermissions } = useRBACProvider();
9
-
10
- const readPermissionsForSlug = findMatchingPermissions(allPermissions, [
11
- {
12
- action: 'plugin::content-manager.explorer.read',
13
- subject: slug,
14
- },
15
- ]);
16
-
17
- const readPermissionForAttr = get(readPermissionsForSlug, ['0', 'properties', 'fields'], []);
18
- const attributesArray = Object.keys(get(contentType, ['attributes']), {});
19
- const allowedAttributes = attributesArray
20
- .filter((attr) => {
21
- const current = get(contentType, ['attributes', attr], {});
22
-
23
- if (!current.type) {
24
- return false;
25
- }
26
-
27
- if (NOT_ALLOWED_FILTERS.includes(current.type)) {
28
- return false;
29
- }
30
-
31
- if (!readPermissionForAttr.includes(attr) && attr !== 'id' && !TIMESTAMPS.includes(attr)) {
32
- return false;
33
- }
34
-
35
- return true;
36
- })
37
- .sort();
38
-
39
- return allowedAttributes;
40
- };
41
-
42
- export default useAllowedAttributes;