@strapi/content-manager 0.0.0-experimental.e60ec1829240dae21c1e1d29076681c322288813 → 0.0.0-experimental.e9122b401c96877b6707775c4f893660eab93ae3

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/LICENSE +18 -3
  2. package/dist/_chunks/{CardDragPreview-DSVYodBX.js → CardDragPreview-C0QyJgRA.js} +10 -14
  3. package/dist/_chunks/CardDragPreview-C0QyJgRA.js.map +1 -0
  4. package/dist/_chunks/{CardDragPreview-ikSG4M46.mjs → CardDragPreview-DOxamsuj.mjs} +7 -9
  5. package/dist/_chunks/CardDragPreview-DOxamsuj.mjs.map +1 -0
  6. package/dist/_chunks/{ComponentConfigurationPage-BPvzFjM7.mjs → ComponentConfigurationPage-CpBFh6_r.mjs} +3 -3
  7. package/dist/_chunks/{ComponentConfigurationPage-BPvzFjM7.mjs.map → ComponentConfigurationPage-CpBFh6_r.mjs.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage-DjWJdz6Y.js → ComponentConfigurationPage-_zF8p6CY.js} +3 -3
  9. package/dist/_chunks/{ComponentConfigurationPage-DjWJdz6Y.js.map → ComponentConfigurationPage-_zF8p6CY.js.map} +1 -1
  10. package/dist/_chunks/{ComponentIcon-BBQsYCVn.js → ComponentIcon-BXdiCGQp.js} +8 -2
  11. package/dist/_chunks/ComponentIcon-BXdiCGQp.js.map +1 -0
  12. package/dist/_chunks/{ComponentIcon-BOFnK76n.mjs → ComponentIcon-u4bIXTFY.mjs} +9 -3
  13. package/dist/_chunks/ComponentIcon-u4bIXTFY.mjs.map +1 -0
  14. package/dist/_chunks/{EditConfigurationPage-DacbqQ_f.mjs → EditConfigurationPage-CE_yavTi.mjs} +3 -3
  15. package/dist/_chunks/{EditConfigurationPage-DacbqQ_f.mjs.map → EditConfigurationPage-CE_yavTi.mjs.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-Dmv83RlS.js → EditConfigurationPage-_aG2DJSU.js} +3 -3
  17. package/dist/_chunks/{EditConfigurationPage-Dmv83RlS.js.map → EditConfigurationPage-_aG2DJSU.js.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-DDS6H9HO.mjs → EditViewPage-DeTn7rAF.mjs} +59 -48
  19. package/dist/_chunks/EditViewPage-DeTn7rAF.mjs.map +1 -0
  20. package/dist/_chunks/{EditViewPage-DvNpQkam.js → EditViewPage-G9uNzwYL.js} +58 -49
  21. package/dist/_chunks/EditViewPage-G9uNzwYL.js.map +1 -0
  22. package/dist/_chunks/{Field-6gvGdPBV.mjs → Field-CnCKhI1R.mjs} +995 -795
  23. package/dist/_chunks/Field-CnCKhI1R.mjs.map +1 -0
  24. package/dist/_chunks/{Field-DmVKIAOo.js → Field-DDHUWEfV.js} +1041 -842
  25. package/dist/_chunks/Field-DDHUWEfV.js.map +1 -0
  26. package/dist/_chunks/{Form-CPZC9vWa.js → Form-DYETaKUX.js} +65 -45
  27. package/dist/_chunks/Form-DYETaKUX.js.map +1 -0
  28. package/dist/_chunks/{Form-DW6K1IH-.mjs → Form-IvVVwqRL.mjs} +65 -44
  29. package/dist/_chunks/Form-IvVVwqRL.mjs.map +1 -0
  30. package/dist/_chunks/{History-Dmr9fmUA.mjs → History-BMunT-do.mjs} +148 -54
  31. package/dist/_chunks/History-BMunT-do.mjs.map +1 -0
  32. package/dist/_chunks/{History-DeAPlvtv.js → History-CnZDctSO.js} +149 -56
  33. package/dist/_chunks/History-CnZDctSO.js.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-DPCwW5Vr.js → ListConfigurationPage-BynalOp8.js} +67 -58
  35. package/dist/_chunks/ListConfigurationPage-BynalOp8.js.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-DhwvYcNv.mjs → ListConfigurationPage-CDqkCxgV.mjs} +63 -53
  37. package/dist/_chunks/ListConfigurationPage-CDqkCxgV.mjs.map +1 -0
  38. package/dist/_chunks/{ListViewPage-5ySZ-VUs.js → ListViewPage-I88Ouzoq.js} +126 -137
  39. package/dist/_chunks/ListViewPage-I88Ouzoq.js.map +1 -0
  40. package/dist/_chunks/{ListViewPage-BtAwuYLE.mjs → ListViewPage-_5gS-DOF.mjs} +123 -134
  41. package/dist/_chunks/ListViewPage-_5gS-DOF.mjs.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-DOC_yWOf.js → NoContentTypePage-BaWQ7HsA.js} +3 -3
  43. package/dist/_chunks/NoContentTypePage-BaWQ7HsA.js.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-DSPxnxxp.mjs → NoContentTypePage-Dht-55hr.mjs} +3 -3
  45. package/dist/_chunks/NoContentTypePage-Dht-55hr.mjs.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-UWDC-1Tw.mjs → NoPermissionsPage-Bs8D5W_v.mjs} +2 -2
  47. package/dist/_chunks/{NoPermissionsPage-UWDC-1Tw.mjs.map → NoPermissionsPage-Bs8D5W_v.mjs.map} +1 -1
  48. package/dist/_chunks/{NoPermissionsPage-Dwu8rRJu.js → NoPermissionsPage-DCVUh5at.js} +2 -2
  49. package/dist/_chunks/{NoPermissionsPage-Dwu8rRJu.js.map → NoPermissionsPage-DCVUh5at.js.map} +1 -1
  50. package/dist/_chunks/{Relations-CgWtgnPe.js → Relations-BPgFQeGj.js} +71 -62
  51. package/dist/_chunks/Relations-BPgFQeGj.js.map +1 -0
  52. package/dist/_chunks/{Relations-J8cscLlR.mjs → Relations-Chdt5qWc.mjs} +67 -57
  53. package/dist/_chunks/Relations-Chdt5qWc.mjs.map +1 -0
  54. package/dist/_chunks/{en-C-V1_90f.js → en-BVzUkPxZ.js} +16 -8
  55. package/dist/_chunks/{en-C-V1_90f.js.map → en-BVzUkPxZ.js.map} +1 -1
  56. package/dist/_chunks/{en-MBPul9Su.mjs → en-CPTj6CjC.mjs} +16 -8
  57. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-CPTj6CjC.mjs.map} +1 -1
  58. package/dist/_chunks/{index-C6AH2hEl.js → index-BhbLFX4l.js} +1752 -903
  59. package/dist/_chunks/index-BhbLFX4l.js.map +1 -0
  60. package/dist/_chunks/{index-CwRRo1V9.mjs → index-D4UGPFZC.mjs} +1778 -928
  61. package/dist/_chunks/index-D4UGPFZC.mjs.map +1 -0
  62. package/dist/_chunks/{layout-B_SXLhqf.js → layout-CYA7s0qO.js} +45 -29
  63. package/dist/_chunks/layout-CYA7s0qO.js.map +1 -0
  64. package/dist/_chunks/{layout-jIDzX0Fp.mjs → layout-D4HI4_PS.mjs} +45 -27
  65. package/dist/_chunks/layout-D4HI4_PS.mjs.map +1 -0
  66. package/dist/_chunks/{relations-CuvIgCqI.mjs → relations-1pXaYpBK.mjs} +2 -2
  67. package/dist/_chunks/{relations-CuvIgCqI.mjs.map → relations-1pXaYpBK.mjs.map} +1 -1
  68. package/dist/_chunks/{relations-iBMa_OFG.js → relations-DDZ9OxNo.js} +2 -2
  69. package/dist/_chunks/{relations-iBMa_OFG.js.map → relations-DDZ9OxNo.js.map} +1 -1
  70. package/dist/_chunks/useDebounce-CtcjDB3L.js +28 -0
  71. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  72. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  73. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  74. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  75. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  76. package/dist/admin/index.js +2 -1
  77. package/dist/admin/index.js.map +1 -1
  78. package/dist/admin/index.mjs +8 -7
  79. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  80. package/dist/admin/src/content-manager.d.ts +3 -3
  81. package/dist/admin/src/exports.d.ts +1 -0
  82. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +1 -1
  83. package/dist/admin/src/history/index.d.ts +3 -0
  84. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  85. package/dist/admin/src/hooks/useDocument.d.ts +5 -8
  86. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  87. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  88. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  89. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  90. package/dist/admin/src/index.d.ts +1 -0
  91. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +11 -4
  92. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  93. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  94. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  95. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  96. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  97. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +30 -18
  98. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  99. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  100. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  101. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  102. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +16 -53
  103. package/dist/admin/src/pages/EditView/components/Header.d.ts +10 -11
  104. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  105. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  106. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  107. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  108. package/dist/admin/src/services/api.d.ts +2 -3
  109. package/dist/admin/src/services/components.d.ts +2 -2
  110. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  111. package/dist/admin/src/services/documents.d.ts +31 -17
  112. package/dist/admin/src/services/init.d.ts +2 -2
  113. package/dist/admin/src/services/relations.d.ts +3 -3
  114. package/dist/admin/src/services/uid.d.ts +3 -3
  115. package/dist/admin/src/utils/api.d.ts +4 -18
  116. package/dist/admin/src/utils/validation.d.ts +5 -7
  117. package/dist/server/index.js +436 -281
  118. package/dist/server/index.js.map +1 -1
  119. package/dist/server/index.mjs +444 -289
  120. package/dist/server/index.mjs.map +1 -1
  121. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  122. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  123. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  124. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  125. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  126. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  127. package/dist/server/src/controllers/validation/dimensions.d.ts +11 -0
  128. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  129. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  130. package/dist/server/src/history/services/history.d.ts.map +1 -1
  131. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  132. package/dist/server/src/history/services/utils.d.ts +2 -1
  133. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  134. package/dist/server/src/index.d.ts +18 -39
  135. package/dist/server/src/index.d.ts.map +1 -1
  136. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  137. package/dist/server/src/services/document-manager.d.ts +13 -12
  138. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  139. package/dist/server/src/services/document-metadata.d.ts +8 -29
  140. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  141. package/dist/server/src/services/index.d.ts +18 -39
  142. package/dist/server/src/services/index.d.ts.map +1 -1
  143. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  144. package/dist/server/src/services/utils/populate.d.ts +8 -1
  145. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  146. package/dist/shared/contracts/collection-types.d.ts +17 -7
  147. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  148. package/dist/shared/contracts/relations.d.ts +2 -2
  149. package/dist/shared/contracts/relations.d.ts.map +1 -1
  150. package/package.json +14 -15
  151. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  152. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  153. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  154. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  155. package/dist/_chunks/EditViewPage-DDS6H9HO.mjs.map +0 -1
  156. package/dist/_chunks/EditViewPage-DvNpQkam.js.map +0 -1
  157. package/dist/_chunks/Field-6gvGdPBV.mjs.map +0 -1
  158. package/dist/_chunks/Field-DmVKIAOo.js.map +0 -1
  159. package/dist/_chunks/Form-CPZC9vWa.js.map +0 -1
  160. package/dist/_chunks/Form-DW6K1IH-.mjs.map +0 -1
  161. package/dist/_chunks/History-DeAPlvtv.js.map +0 -1
  162. package/dist/_chunks/History-Dmr9fmUA.mjs.map +0 -1
  163. package/dist/_chunks/ListConfigurationPage-DPCwW5Vr.js.map +0 -1
  164. package/dist/_chunks/ListConfigurationPage-DhwvYcNv.mjs.map +0 -1
  165. package/dist/_chunks/ListViewPage-5ySZ-VUs.js.map +0 -1
  166. package/dist/_chunks/ListViewPage-BtAwuYLE.mjs.map +0 -1
  167. package/dist/_chunks/NoContentTypePage-DOC_yWOf.js.map +0 -1
  168. package/dist/_chunks/NoContentTypePage-DSPxnxxp.mjs.map +0 -1
  169. package/dist/_chunks/Relations-CgWtgnPe.js.map +0 -1
  170. package/dist/_chunks/Relations-J8cscLlR.mjs.map +0 -1
  171. package/dist/_chunks/index-C6AH2hEl.js.map +0 -1
  172. package/dist/_chunks/index-CwRRo1V9.mjs.map +0 -1
  173. package/dist/_chunks/layout-B_SXLhqf.js.map +0 -1
  174. package/dist/_chunks/layout-jIDzX0Fp.mjs.map +0 -1
  175. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  176. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  177. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  178. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  179. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  180. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
- import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, sanitize } from "@strapi/utils";
2
- import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
1
+ import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, traverseEntity, pagination } from "@strapi/utils";
2
+ import { pick, omit, difference, castArray, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy } from "lodash/fp";
3
3
  import "@strapi/types";
4
4
  import * as yup from "yup";
5
5
  import { scheduleJob } from "node-schedule";
@@ -54,7 +54,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
54
54
  return ctx.forbidden();
55
55
  }
56
56
  const query = await permissionChecker2.sanitizeQuery(ctx.query);
57
- const { results, pagination } = await getService(strapi2, "history").findVersionsPage({
57
+ const { results, pagination: pagination2 } = await getService(strapi2, "history").findVersionsPage({
58
58
  query: {
59
59
  ...query,
60
60
  ...getValidPagination({ page: query.page, pageSize: query.pageSize })
@@ -73,7 +73,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
73
73
  );
74
74
  return {
75
75
  data: sanitizedResults,
76
- meta: { pagination }
76
+ meta: { pagination: pagination2 }
77
77
  };
78
78
  },
79
79
  async restoreVersion(ctx) {
@@ -173,7 +173,9 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
173
173
  return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
174
174
  };
175
175
  const localesService = strapi2.plugin("i18n")?.service("locales");
176
+ const i18nContentTypeService = strapi2.plugin("i18n")?.service("content-types");
176
177
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
178
+ const isLocalizedContentType = (model) => i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
177
179
  const getLocaleDictionary = async () => {
178
180
  if (!localesService)
179
181
  return {};
@@ -200,20 +202,25 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
200
202
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
201
203
  return documentMetadataService.getStatus(document, meta.availableStatus);
202
204
  };
203
- const getDeepPopulate2 = (uid2) => {
205
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
204
206
  const model = strapi2.getModel(uid2);
205
207
  const attributes = Object.entries(model.attributes);
208
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
206
209
  return attributes.reduce((acc, [attributeName, attribute]) => {
207
210
  switch (attribute.type) {
208
211
  case "relation": {
212
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
213
+ if (isMorphRelation) {
214
+ break;
215
+ }
209
216
  const isVisible2 = contentTypes$1.isVisibleAttribute(model, attributeName);
210
217
  if (isVisible2) {
211
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
218
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
212
219
  }
213
220
  break;
214
221
  }
215
222
  case "media": {
216
- acc[attributeName] = { fields: ["id"] };
223
+ acc[attributeName] = { [fieldSelector]: ["id"] };
217
224
  break;
218
225
  }
219
226
  case "component": {
@@ -286,6 +293,7 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
286
293
  getRelationRestoreValue,
287
294
  getMediaRestoreValue,
288
295
  getDefaultLocale,
296
+ isLocalizedContentType,
289
297
  getLocaleDictionary,
290
298
  getRetentionDays,
291
299
  getVersionStatus,
@@ -308,8 +316,14 @@ const createHistoryService = ({ strapi: strapi2 }) => {
308
316
  });
309
317
  },
310
318
  async findVersionsPage(params) {
311
- const locale = params.query.locale || await serviceUtils.getDefaultLocale();
312
- const [{ results, pagination }, localeDictionary] = await Promise.all([
319
+ const model = strapi2.getModel(params.query.contentType);
320
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
321
+ const defaultLocale = await serviceUtils.getDefaultLocale();
322
+ let locale = null;
323
+ if (isLocalizedContentType) {
324
+ locale = params.query.locale || defaultLocale;
325
+ }
326
+ const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
313
327
  query.findPage({
314
328
  ...params.query,
315
329
  where: {
@@ -408,7 +422,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
408
422
  );
409
423
  return {
410
424
  results: formattedResults,
411
- pagination
425
+ pagination: pagination2
412
426
  };
413
427
  },
414
428
  async restoreVersion(versionId) {
@@ -464,13 +478,47 @@ const createHistoryService = ({ strapi: strapi2 }) => {
464
478
  }
465
479
  };
466
480
  };
481
+ const shouldCreateHistoryVersion = (context) => {
482
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
483
+ return false;
484
+ }
485
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
486
+ return false;
487
+ }
488
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
489
+ return false;
490
+ }
491
+ if (!context.contentType.uid.startsWith("api::")) {
492
+ return false;
493
+ }
494
+ return true;
495
+ };
496
+ const getSchemas = (uid2) => {
497
+ const attributesSchema = strapi.getModel(uid2).attributes;
498
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
499
+ (currentComponentSchemas, key) => {
500
+ const fieldSchema = attributesSchema[key];
501
+ if (fieldSchema.type === "component") {
502
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
503
+ return {
504
+ ...currentComponentSchemas,
505
+ [fieldSchema.component]: componentSchema
506
+ };
507
+ }
508
+ return currentComponentSchemas;
509
+ },
510
+ {}
511
+ );
512
+ return {
513
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
514
+ componentsSchemas
515
+ };
516
+ };
467
517
  const createLifecyclesService = ({ strapi: strapi2 }) => {
468
518
  const state = {
469
519
  deleteExpiredJob: null,
470
520
  isInitialized: false
471
521
  };
472
- const query = strapi2.db.query(HISTORY_VERSION_UID);
473
- const historyService = getService(strapi2, "history");
474
522
  const serviceUtils = createServiceUtils({ strapi: strapi2 });
475
523
  return {
476
524
  async bootstrap() {
@@ -478,60 +526,53 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
478
526
  return;
479
527
  }
480
528
  strapi2.documents.use(async (context, next) => {
481
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
482
- return next();
483
- }
484
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
485
- return next();
486
- }
487
- const contentTypeUid = context.contentType.uid;
488
- if (!contentTypeUid.startsWith("api::")) {
489
- return next();
490
- }
491
529
  const result = await next();
492
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
530
+ if (!shouldCreateHistoryVersion(context)) {
531
+ return result;
532
+ }
533
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
493
534
  const defaultLocale = await serviceUtils.getDefaultLocale();
494
- const locale = documentContext.locale || defaultLocale;
495
- const document = await strapi2.documents(contentTypeUid).findOne({
496
- documentId: documentContext.documentId,
497
- locale,
498
- populate: serviceUtils.getDeepPopulate(contentTypeUid)
535
+ const locales = castArray(context.params?.locale || defaultLocale);
536
+ if (!locales.length) {
537
+ return result;
538
+ }
539
+ const uid2 = context.contentType.uid;
540
+ const schemas = getSchemas(uid2);
541
+ const model = strapi2.getModel(uid2);
542
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
543
+ const localeEntries = await strapi2.db.query(uid2).findMany({
544
+ where: {
545
+ documentId,
546
+ ...isLocalizedContentType ? { locale: { $in: locales } } : {},
547
+ ...contentTypes$1.hasDraftAndPublish(strapi2.contentTypes[uid2]) ? { publishedAt: null } : {}
548
+ },
549
+ populate: serviceUtils.getDeepPopulate(
550
+ uid2,
551
+ true
552
+ /* use database syntax */
553
+ )
499
554
  });
500
- const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
501
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
502
- const componentsSchemas = Object.keys(
503
- attributesSchema
504
- ).reduce((currentComponentSchemas, key) => {
505
- const fieldSchema = attributesSchema[key];
506
- if (fieldSchema.type === "component") {
507
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
508
- return {
509
- ...currentComponentSchemas,
510
- [fieldSchema.component]: componentSchema
511
- };
512
- }
513
- return currentComponentSchemas;
514
- }, {});
515
555
  await strapi2.db.transaction(async ({ onCommit }) => {
516
- onCommit(() => {
517
- historyService.createVersion({
518
- contentType: contentTypeUid,
519
- data: omit(FIELDS_TO_IGNORE, document),
520
- schema: omit(FIELDS_TO_IGNORE, attributesSchema),
521
- componentsSchemas,
522
- relatedDocumentId: documentContext.documentId,
523
- locale,
524
- status
525
- });
556
+ onCommit(async () => {
557
+ for (const entry of localeEntries) {
558
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
559
+ await getService(strapi2, "history").createVersion({
560
+ contentType: uid2,
561
+ data: omit(FIELDS_TO_IGNORE, entry),
562
+ relatedDocumentId: documentId,
563
+ locale: entry.locale,
564
+ status,
565
+ ...schemas
566
+ });
567
+ }
526
568
  });
527
569
  });
528
570
  return result;
529
571
  });
530
- const retentionDays = serviceUtils.getRetentionDays();
531
572
  state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
532
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
573
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
533
574
  const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
534
- query.deleteMany({
575
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
535
576
  where: {
536
577
  created_at: {
537
578
  $lt: expirationDate.toISOString()
@@ -1163,6 +1204,11 @@ const { createPolicy } = policy;
1163
1204
  const hasPermissions = createPolicy({
1164
1205
  name: "plugin::content-manager.hasPermissions",
1165
1206
  validator: validateHasPermissionsInput,
1207
+ /**
1208
+ * NOTE: Action aliases are currently not checked at this level (policy).
1209
+ * This is currently the intended behavior to avoid changing the behavior of API related permissions.
1210
+ * If you want to add support for it, please create a dedicated RFC with a list of potential side effect this could have.
1211
+ */
1166
1212
  handler(ctx, config = {}) {
1167
1213
  const { actions = [], hasAtLeastOne = false } = config;
1168
1214
  const { userAbility } = ctx.state;
@@ -1452,7 +1498,7 @@ const { PaginationError, ValidationError } = errors;
1452
1498
  const TYPES = ["singleType", "collectionType"];
1453
1499
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1454
1500
  const bulkActionInputSchema = yup$1.object({
1455
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1501
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1456
1502
  }).required();
1457
1503
  const generateUIDInputSchema = yup$1.object({
1458
1504
  contentTypeUID: yup$1.string().required(),
@@ -1551,15 +1597,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1551
1597
  }
1552
1598
  }, body);
1553
1599
  };
1554
- const getDocumentLocaleAndStatus = (request) => {
1555
- const { locale, status, ...rest } = request || {};
1556
- if (!isNil$1(locale) && typeof locale !== "string") {
1557
- throw new errors.ValidationError(`Invalid locale: ${locale}`);
1558
- }
1559
- if (!isNil$1(status) && !["draft", "published"].includes(status)) {
1560
- throw new errors.ValidationError(`Invalid status: ${status}`);
1600
+ const singleLocaleSchema = yup$1.string().nullable();
1601
+ const multipleLocaleSchema = yup$1.lazy(
1602
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1603
+ );
1604
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1605
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1606
+ const { allowMultipleLocales } = opts;
1607
+ const { locale, status: providedStatus, ...rest } = request || {};
1608
+ const defaultStatus = contentTypes$1.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1609
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1610
+ const schema = yup$1.object().shape({
1611
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1612
+ status: statusSchema
1613
+ });
1614
+ try {
1615
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1616
+ return { locale, status, ...rest };
1617
+ } catch (error) {
1618
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1561
1619
  }
1562
- return { locale, status, ...rest };
1620
+ };
1621
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1622
+ const documentMetadata2 = getService$1("document-metadata");
1623
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1624
+ let {
1625
+ meta: { availableLocales, availableStatus }
1626
+ } = serviceOutput;
1627
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1628
+ availableLocales = await async.map(
1629
+ availableLocales,
1630
+ async (localeDocument) => metadataSanitizer(localeDocument)
1631
+ );
1632
+ availableStatus = await async.map(
1633
+ availableStatus,
1634
+ async (statusDocument) => metadataSanitizer(statusDocument)
1635
+ );
1636
+ return {
1637
+ ...serviceOutput,
1638
+ meta: {
1639
+ availableLocales,
1640
+ availableStatus
1641
+ }
1642
+ };
1563
1643
  };
1564
1644
  const createDocument = async (ctx, opts) => {
1565
1645
  const { userAbility, user } = ctx.state;
@@ -1574,7 +1654,7 @@ const createDocument = async (ctx, opts) => {
1574
1654
  const setCreator = setCreatorFields({ user });
1575
1655
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1576
1656
  const sanitizedBody = await sanitizeFn(body);
1577
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1657
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1578
1658
  return documentManager2.create(model, {
1579
1659
  data: sanitizedBody,
1580
1660
  locale,
@@ -1593,7 +1673,7 @@ const updateDocument = async (ctx, opts) => {
1593
1673
  }
1594
1674
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1595
1675
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1596
- const { locale } = getDocumentLocaleAndStatus(body);
1676
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1597
1677
  const [documentVersion, documentExists] = await Promise.all([
1598
1678
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1599
1679
  documentManager2.exists(model, id)
@@ -1631,8 +1711,8 @@ const collectionTypes = {
1631
1711
  }
1632
1712
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1633
1713
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1634
- const { locale, status } = getDocumentLocaleAndStatus(query);
1635
- const { results: documents, pagination } = await documentManager2.findPage(
1714
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1715
+ const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1636
1716
  { ...permissionQuery, populate, locale, status },
1637
1717
  model
1638
1718
  );
@@ -1653,21 +1733,20 @@ const collectionTypes = {
1653
1733
  );
1654
1734
  ctx.body = {
1655
1735
  results,
1656
- pagination
1736
+ pagination: pagination2
1657
1737
  };
1658
1738
  },
1659
1739
  async findOne(ctx) {
1660
1740
  const { userAbility } = ctx.state;
1661
1741
  const { model, id } = ctx.params;
1662
1742
  const documentManager2 = getService$1("document-manager");
1663
- const documentMetadata2 = getService$1("document-metadata");
1664
1743
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1665
1744
  if (permissionChecker2.cannot.read()) {
1666
1745
  return ctx.forbidden();
1667
1746
  }
1668
1747
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1669
1748
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1670
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1749
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1671
1750
  const version = await documentManager2.findOne(id, model, {
1672
1751
  populate,
1673
1752
  locale,
@@ -1678,9 +1757,11 @@ const collectionTypes = {
1678
1757
  if (!exists) {
1679
1758
  return ctx.notFound();
1680
1759
  }
1681
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1760
+ const { meta } = await formatDocumentWithMetadata(
1761
+ permissionChecker2,
1682
1762
  model,
1683
- { id, locale, publishedAt: null },
1763
+ // @ts-expect-error TODO: fix
1764
+ { documentId: id, locale, publishedAt: null },
1684
1765
  { availableLocales: true, availableStatus: false }
1685
1766
  );
1686
1767
  ctx.body = { data: {}, meta };
@@ -1690,12 +1771,11 @@ const collectionTypes = {
1690
1771
  return ctx.forbidden();
1691
1772
  }
1692
1773
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1693
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1774
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1694
1775
  },
1695
1776
  async create(ctx) {
1696
1777
  const { userAbility } = ctx.state;
1697
1778
  const { model } = ctx.params;
1698
- const documentMetadata2 = getService$1("document-metadata");
1699
1779
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1700
1780
  const [totalEntries, document] = await Promise.all([
1701
1781
  strapi.db.query(model).count(),
@@ -1703,7 +1783,7 @@ const collectionTypes = {
1703
1783
  ]);
1704
1784
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1705
1785
  ctx.status = 201;
1706
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1786
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1707
1787
  // Empty metadata as it's not relevant for a new document
1708
1788
  availableLocales: false,
1709
1789
  availableStatus: false
@@ -1717,25 +1797,23 @@ const collectionTypes = {
1717
1797
  async update(ctx) {
1718
1798
  const { userAbility } = ctx.state;
1719
1799
  const { model } = ctx.params;
1720
- const documentMetadata2 = getService$1("document-metadata");
1721
1800
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1722
1801
  const updatedVersion = await updateDocument(ctx);
1723
1802
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1724
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1803
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1725
1804
  },
1726
1805
  async clone(ctx) {
1727
1806
  const { userAbility, user } = ctx.state;
1728
1807
  const { model, sourceId: id } = ctx.params;
1729
1808
  const { body } = ctx.request;
1730
1809
  const documentManager2 = getService$1("document-manager");
1731
- const documentMetadata2 = getService$1("document-metadata");
1732
1810
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1733
1811
  if (permissionChecker2.cannot.create()) {
1734
1812
  return ctx.forbidden();
1735
1813
  }
1736
1814
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1737
1815
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(body);
1816
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1739
1817
  const document = await documentManager2.findOne(id, model, {
1740
1818
  populate,
1741
1819
  locale,
@@ -1751,7 +1829,7 @@ const collectionTypes = {
1751
1829
  const sanitizedBody = await sanitizeFn(body);
1752
1830
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1753
1831
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1754
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1832
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1755
1833
  // Empty metadata as it's not relevant for a new document
1756
1834
  availableLocales: false,
1757
1835
  availableStatus: false
@@ -1780,7 +1858,7 @@ const collectionTypes = {
1780
1858
  }
1781
1859
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1782
1860
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1783
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1861
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1784
1862
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1785
1863
  if (documentLocales.length === 0) {
1786
1864
  return ctx.notFound();
@@ -1802,7 +1880,6 @@ const collectionTypes = {
1802
1880
  const { id, model } = ctx.params;
1803
1881
  const { body } = ctx.request;
1804
1882
  const documentManager2 = getService$1("document-manager");
1805
- const documentMetadata2 = getService$1("document-metadata");
1806
1883
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1807
1884
  if (permissionChecker2.cannot.publish()) {
1808
1885
  return ctx.forbidden();
@@ -1810,25 +1887,46 @@ const collectionTypes = {
1810
1887
  const publishedDocument = await strapi.db.transaction(async () => {
1811
1888
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1812
1889
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1813
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1890
+ let document;
1891
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1892
+ const isCreate = isNil$1(id);
1893
+ if (isCreate) {
1894
+ if (permissionChecker2.cannot.create()) {
1895
+ throw new errors.ForbiddenError();
1896
+ }
1897
+ document = await createDocument(ctx, { populate });
1898
+ }
1899
+ const isUpdate = !isCreate;
1900
+ if (isUpdate) {
1901
+ document = await documentManager2.findOne(id, model, { populate, locale });
1902
+ if (!document) {
1903
+ throw new errors.NotFoundError("Document not found");
1904
+ }
1905
+ if (permissionChecker2.can.update(document)) {
1906
+ await updateDocument(ctx);
1907
+ }
1908
+ }
1814
1909
  if (permissionChecker2.cannot.publish(document)) {
1815
1910
  throw new errors.ForbiddenError();
1816
1911
  }
1817
- const { locale } = getDocumentLocaleAndStatus(body);
1818
- return documentManager2.publish(document.documentId, model, {
1912
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1819
1913
  locale
1820
1914
  // TODO: Allow setting creator fields on publish
1821
1915
  // data: setCreatorFields({ user, isEdition: true })({}),
1822
1916
  });
1917
+ if (!publishResult || publishResult.length === 0) {
1918
+ throw new errors.NotFoundError("Document not found or already published.");
1919
+ }
1920
+ return publishResult[0];
1823
1921
  });
1824
1922
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1825
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1923
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1826
1924
  },
1827
1925
  async bulkPublish(ctx) {
1828
1926
  const { userAbility } = ctx.state;
1829
1927
  const { model } = ctx.params;
1830
1928
  const { body } = ctx.request;
1831
- const { ids } = body;
1929
+ const { documentIds } = body;
1832
1930
  await validateBulkActionInput(body);
1833
1931
  const documentManager2 = getService$1("document-manager");
1834
1932
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1837,8 +1935,13 @@ const collectionTypes = {
1837
1935
  }
1838
1936
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1839
1937
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1840
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1841
- const entities = await Promise.all(entityPromises);
1938
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1939
+ allowMultipleLocales: true
1940
+ });
1941
+ const entityPromises = documentIds.map(
1942
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1943
+ );
1944
+ const entities = (await Promise.all(entityPromises)).flat();
1842
1945
  for (const entity of entities) {
1843
1946
  if (!entity) {
1844
1947
  return ctx.notFound();
@@ -1847,24 +1950,27 @@ const collectionTypes = {
1847
1950
  return ctx.forbidden();
1848
1951
  }
1849
1952
  }
1850
- const { count } = await documentManager2.publishMany(entities, model);
1953
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1851
1954
  ctx.body = { count };
1852
1955
  },
1853
1956
  async bulkUnpublish(ctx) {
1854
1957
  const { userAbility } = ctx.state;
1855
1958
  const { model } = ctx.params;
1856
1959
  const { body } = ctx.request;
1857
- const { ids } = body;
1960
+ const { documentIds } = body;
1858
1961
  await validateBulkActionInput(body);
1859
1962
  const documentManager2 = getService$1("document-manager");
1860
1963
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1861
1964
  if (permissionChecker2.cannot.unpublish()) {
1862
1965
  return ctx.forbidden();
1863
1966
  }
1864
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1865
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1866
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1867
- const entities = await Promise.all(entityPromises);
1967
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1968
+ allowMultipleLocales: true
1969
+ });
1970
+ const entityPromises = documentIds.map(
1971
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1972
+ );
1973
+ const entities = (await Promise.all(entityPromises)).flat();
1868
1974
  for (const entity of entities) {
1869
1975
  if (!entity) {
1870
1976
  return ctx.notFound();
@@ -1873,7 +1979,8 @@ const collectionTypes = {
1873
1979
  return ctx.forbidden();
1874
1980
  }
1875
1981
  }
1876
- const { count } = await documentManager2.unpublishMany(entities, model);
1982
+ const entitiesIds = entities.map((document) => document.documentId);
1983
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1877
1984
  ctx.body = { count };
1878
1985
  },
1879
1986
  async unpublish(ctx) {
@@ -1883,7 +1990,6 @@ const collectionTypes = {
1883
1990
  body: { discardDraft, ...body }
1884
1991
  } = ctx.request;
1885
1992
  const documentManager2 = getService$1("document-manager");
1886
- const documentMetadata2 = getService$1("document-metadata");
1887
1993
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1888
1994
  if (permissionChecker2.cannot.unpublish()) {
1889
1995
  return ctx.forbidden();
@@ -1893,7 +1999,7 @@ const collectionTypes = {
1893
1999
  }
1894
2000
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1895
2001
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1896
- const { locale } = getDocumentLocaleAndStatus(body);
2002
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1897
2003
  const document = await documentManager2.findOne(id, model, {
1898
2004
  populate,
1899
2005
  locale,
@@ -1915,7 +2021,7 @@ const collectionTypes = {
1915
2021
  ctx.body = await async.pipe(
1916
2022
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1917
2023
  permissionChecker2.sanitizeOutput,
1918
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2024
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1919
2025
  )(document);
1920
2026
  });
1921
2027
  },
@@ -1924,14 +2030,13 @@ const collectionTypes = {
1924
2030
  const { id, model } = ctx.params;
1925
2031
  const { body } = ctx.request;
1926
2032
  const documentManager2 = getService$1("document-manager");
1927
- const documentMetadata2 = getService$1("document-metadata");
1928
2033
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1929
2034
  if (permissionChecker2.cannot.discard()) {
1930
2035
  return ctx.forbidden();
1931
2036
  }
1932
2037
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1933
2038
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1934
- const { locale } = getDocumentLocaleAndStatus(body);
2039
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1935
2040
  const document = await documentManager2.findOne(id, model, {
1936
2041
  populate,
1937
2042
  locale,
@@ -1946,14 +2051,14 @@ const collectionTypes = {
1946
2051
  ctx.body = await async.pipe(
1947
2052
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1948
2053
  permissionChecker2.sanitizeOutput,
1949
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2054
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1950
2055
  )(document);
1951
2056
  },
1952
2057
  async bulkDelete(ctx) {
1953
2058
  const { userAbility } = ctx.state;
1954
2059
  const { model } = ctx.params;
1955
2060
  const { query, body } = ctx.request;
1956
- const { ids } = body;
2061
+ const { documentIds } = body;
1957
2062
  await validateBulkActionInput(body);
1958
2063
  const documentManager2 = getService$1("document-manager");
1959
2064
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1961,14 +2066,22 @@ const collectionTypes = {
1961
2066
  return ctx.forbidden();
1962
2067
  }
1963
2068
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1964
- const idsWhereClause = { id: { $in: ids } };
1965
- const params = {
1966
- ...permissionQuery,
1967
- filters: {
1968
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2069
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2070
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2071
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2072
+ populate,
2073
+ locale
2074
+ });
2075
+ if (documentLocales.length === 0) {
2076
+ return ctx.notFound();
2077
+ }
2078
+ for (const document of documentLocales) {
2079
+ if (permissionChecker2.cannot.delete(document)) {
2080
+ return ctx.forbidden();
1969
2081
  }
1970
- };
1971
- const { count } = await documentManager2.deleteMany(params, model);
2082
+ }
2083
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2084
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1972
2085
  ctx.body = { count };
1973
2086
  },
1974
2087
  async countDraftRelations(ctx) {
@@ -1981,7 +2094,7 @@ const collectionTypes = {
1981
2094
  }
1982
2095
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1983
2096
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1984
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2097
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1985
2098
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1986
2099
  if (!entity) {
1987
2100
  return ctx.notFound();
@@ -1996,7 +2109,7 @@ const collectionTypes = {
1996
2109
  },
1997
2110
  async countManyEntriesDraftRelations(ctx) {
1998
2111
  const { userAbility } = ctx.state;
1999
- const ids = ctx.request.query.ids;
2112
+ const ids = ctx.request.query.documentIds;
2000
2113
  const locale = ctx.request.query.locale;
2001
2114
  const { model } = ctx.params;
2002
2115
  const documentManager2 = getService$1("document-manager");
@@ -2004,16 +2117,16 @@ const collectionTypes = {
2004
2117
  if (permissionChecker2.cannot.read()) {
2005
2118
  return ctx.forbidden();
2006
2119
  }
2007
- const entities = await documentManager2.findMany(
2120
+ const documents = await documentManager2.findMany(
2008
2121
  {
2009
2122
  filters: {
2010
- id: ids
2123
+ documentId: ids
2011
2124
  },
2012
2125
  locale
2013
2126
  },
2014
2127
  model
2015
2128
  );
2016
- if (!entities) {
2129
+ if (!documents) {
2017
2130
  return ctx.notFound();
2018
2131
  }
2019
2132
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2204,20 +2317,13 @@ const sanitizeMainField = (model, mainField, userAbility) => {
2204
2317
  userAbility,
2205
2318
  model: model.uid
2206
2319
  });
2207
- if (!isListable(model, mainField)) {
2320
+ const isMainFieldListable = isListable(model, mainField);
2321
+ const canReadMainField = permissionChecker2.can.read(null, mainField);
2322
+ if (!isMainFieldListable || !canReadMainField) {
2208
2323
  return "id";
2209
2324
  }
2210
- if (permissionChecker2.cannot.read(null, mainField)) {
2211
- if (model.uid === "plugin::users-permissions.role") {
2212
- const userPermissionChecker = getService$1("permission-checker").create({
2213
- userAbility,
2214
- model: "plugin::users-permissions.user"
2215
- });
2216
- if (userPermissionChecker.can.read()) {
2217
- return "name";
2218
- }
2219
- }
2220
- return "id";
2325
+ if (model.uid === "plugin::users-permissions.role") {
2326
+ return "name";
2221
2327
  }
2222
2328
  return mainField;
2223
2329
  };
@@ -2475,9 +2581,7 @@ const relations = {
2475
2581
  addFiltersClause(permissionQuery, { id: { $in: loadedIds } });
2476
2582
  const sanitizedRes = await loadRelations({ id: entryId }, targetField, {
2477
2583
  ...strapi.get("query-params").transform(targetUid, permissionQuery),
2478
- ordering: "desc",
2479
- page: ctx.request.query.page,
2480
- pageSize: ctx.request.query.pageSize
2584
+ ordering: "desc"
2481
2585
  });
2482
2586
  const relationsUnion = uniqBy("id", concat(sanitizedRes.results, res.results));
2483
2587
  ctx.body = {
@@ -2509,7 +2613,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2509
2613
  throw new errors.ForbiddenError();
2510
2614
  }
2511
2615
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2512
- const { locale } = getDocumentLocaleAndStatus(body);
2616
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2513
2617
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2514
2618
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2515
2619
  // Find the first document to check if it exists
@@ -2546,12 +2650,11 @@ const singleTypes = {
2546
2650
  const { model } = ctx.params;
2547
2651
  const { query = {} } = ctx.request;
2548
2652
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2549
- const documentMetadata2 = getService$1("document-metadata");
2550
2653
  if (permissionChecker2.cannot.read()) {
2551
2654
  return ctx.forbidden();
2552
2655
  }
2553
2656
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2554
- const { locale, status } = getDocumentLocaleAndStatus(query);
2657
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2555
2658
  const version = await findDocument(permissionQuery, model, { locale, status });
2556
2659
  if (!version) {
2557
2660
  if (permissionChecker2.cannot.create()) {
@@ -2561,8 +2664,10 @@ const singleTypes = {
2561
2664
  if (!document) {
2562
2665
  return ctx.notFound();
2563
2666
  }
2564
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2667
+ const { meta } = await formatDocumentWithMetadata(
2668
+ permissionChecker2,
2565
2669
  model,
2670
+ // @ts-expect-error - fix types
2566
2671
  { id: document.documentId, locale, publishedAt: null },
2567
2672
  { availableLocales: true, availableStatus: false }
2568
2673
  );
@@ -2573,16 +2678,15 @@ const singleTypes = {
2573
2678
  return ctx.forbidden();
2574
2679
  }
2575
2680
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2576
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2681
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2577
2682
  },
2578
2683
  async createOrUpdate(ctx) {
2579
2684
  const { userAbility } = ctx.state;
2580
2685
  const { model } = ctx.params;
2581
- const documentMetadata2 = getService$1("document-metadata");
2582
2686
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2583
2687
  const document = await createOrUpdateDocument(ctx);
2584
2688
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2585
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2689
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2586
2690
  },
2587
2691
  async delete(ctx) {
2588
2692
  const { userAbility } = ctx.state;
@@ -2595,7 +2699,7 @@ const singleTypes = {
2595
2699
  }
2596
2700
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2597
2701
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2598
- const { locale } = getDocumentLocaleAndStatus(query);
2702
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2599
2703
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2600
2704
  populate,
2601
2705
  locale
@@ -2618,7 +2722,6 @@ const singleTypes = {
2618
2722
  const { model } = ctx.params;
2619
2723
  const { query = {} } = ctx.request;
2620
2724
  const documentManager2 = getService$1("document-manager");
2621
- const documentMetadata2 = getService$1("document-metadata");
2622
2725
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2623
2726
  if (permissionChecker2.cannot.publish()) {
2624
2727
  return ctx.forbidden();
@@ -2633,11 +2736,12 @@ const singleTypes = {
2633
2736
  if (permissionChecker2.cannot.publish(document)) {
2634
2737
  throw new errors.ForbiddenError();
2635
2738
  }
2636
- const { locale } = getDocumentLocaleAndStatus(document);
2637
- return documentManager2.publish(document.documentId, model, { locale });
2739
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2740
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2741
+ return publishResult.at(0);
2638
2742
  });
2639
2743
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2640
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2744
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2641
2745
  },
2642
2746
  async unpublish(ctx) {
2643
2747
  const { userAbility } = ctx.state;
@@ -2647,7 +2751,6 @@ const singleTypes = {
2647
2751
  query = {}
2648
2752
  } = ctx.request;
2649
2753
  const documentManager2 = getService$1("document-manager");
2650
- const documentMetadata2 = getService$1("document-metadata");
2651
2754
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2652
2755
  if (permissionChecker2.cannot.unpublish()) {
2653
2756
  return ctx.forbidden();
@@ -2656,7 +2759,7 @@ const singleTypes = {
2656
2759
  return ctx.forbidden();
2657
2760
  }
2658
2761
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2659
- const { locale } = getDocumentLocaleAndStatus(body);
2762
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2660
2763
  const document = await findDocument(sanitizedQuery, model, { locale });
2661
2764
  if (!document) {
2662
2765
  return ctx.notFound();
@@ -2674,7 +2777,7 @@ const singleTypes = {
2674
2777
  ctx.body = await async.pipe(
2675
2778
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2676
2779
  permissionChecker2.sanitizeOutput,
2677
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2780
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2678
2781
  )(document);
2679
2782
  });
2680
2783
  },
@@ -2683,13 +2786,12 @@ const singleTypes = {
2683
2786
  const { model } = ctx.params;
2684
2787
  const { body, query = {} } = ctx.request;
2685
2788
  const documentManager2 = getService$1("document-manager");
2686
- const documentMetadata2 = getService$1("document-metadata");
2687
2789
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2688
2790
  if (permissionChecker2.cannot.discard()) {
2689
2791
  return ctx.forbidden();
2690
2792
  }
2691
2793
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2692
- const { locale } = getDocumentLocaleAndStatus(body);
2794
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2693
2795
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2694
2796
  if (!document) {
2695
2797
  return ctx.notFound();
@@ -2700,7 +2802,7 @@ const singleTypes = {
2700
2802
  ctx.body = await async.pipe(
2701
2803
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2702
2804
  permissionChecker2.sanitizeOutput,
2703
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2805
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2704
2806
  )(document);
2705
2807
  },
2706
2808
  async countDraftRelations(ctx) {
@@ -2709,7 +2811,7 @@ const singleTypes = {
2709
2811
  const { query } = ctx.request;
2710
2812
  const documentManager2 = getService$1("document-manager");
2711
2813
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2712
- const { locale } = getDocumentLocaleAndStatus(query);
2814
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2713
2815
  if (permissionChecker2.cannot.read()) {
2714
2816
  return ctx.forbidden();
2715
2817
  }
@@ -2730,7 +2832,7 @@ const uid$1 = {
2730
2832
  async generateUID(ctx) {
2731
2833
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2732
2834
  const { query = {} } = ctx.request;
2733
- const { locale } = getDocumentLocaleAndStatus(query);
2835
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2734
2836
  await validateUIDField(contentTypeUID, field);
2735
2837
  const uidService = getService$1("uid");
2736
2838
  ctx.body = {
@@ -2742,7 +2844,7 @@ const uid$1 = {
2742
2844
  ctx.request.body
2743
2845
  );
2744
2846
  const { query = {} } = ctx.request;
2745
- const { locale } = getDocumentLocaleAndStatus(query);
2847
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2746
2848
  await validateUIDField(contentTypeUID, field);
2747
2849
  const uidService = getService$1("uid");
2748
2850
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3385,12 +3487,27 @@ const createPermissionChecker = (strapi2) => ({ userAbility, model }) => {
3385
3487
  ability: userAbility,
3386
3488
  model
3387
3489
  });
3388
- const toSubject = (entity) => entity ? permissionsManager.toSubject(entity, model) : model;
3490
+ const { actionProvider } = strapi2.service("admin::permission");
3491
+ const toSubject = (entity) => {
3492
+ return entity ? permissionsManager.toSubject(entity, model) : model;
3493
+ };
3389
3494
  const can = (action, entity, field) => {
3390
- return userAbility.can(action, toSubject(entity), field);
3495
+ const subject = toSubject(entity);
3496
+ const aliases = actionProvider.unstable_aliases(action, model);
3497
+ return (
3498
+ // Test the original action to see if it passes
3499
+ userAbility.can(action, subject, field) || // Else try every known alias if at least one of them succeed, then the user "can"
3500
+ aliases.some((alias) => userAbility.can(alias, subject, field))
3501
+ );
3391
3502
  };
3392
3503
  const cannot = (action, entity, field) => {
3393
- return userAbility.cannot(action, toSubject(entity), field);
3504
+ const subject = toSubject(entity);
3505
+ const aliases = actionProvider.unstable_aliases(action, model);
3506
+ return (
3507
+ // Test both the original action
3508
+ userAbility.cannot(action, subject, field) && // and every known alias, if all of them fail (cannot), then the user truly "cannot"
3509
+ aliases.every((alias) => userAbility.cannot(alias, subject, field))
3510
+ );
3394
3511
  };
3395
3512
  const sanitizeOutput = (data, { action = ACTIONS.read } = {}) => {
3396
3513
  return permissionsManager.sanitizeOutput(data, { subject: toSubject(data), action });
@@ -3533,7 +3650,7 @@ const permission = ({ strapi: strapi2 }) => ({
3533
3650
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3534
3651
  }
3535
3652
  });
3536
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3653
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3537
3654
  const { isAnyToMany } = strapiUtils.relations;
3538
3655
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3539
3656
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3624,6 +3741,42 @@ const getDeepPopulate = (uid2, {
3624
3741
  {}
3625
3742
  );
3626
3743
  };
3744
+ const getValidatableFieldsPopulate = (uid2, {
3745
+ initialPopulate = {},
3746
+ countMany = false,
3747
+ countOne = false,
3748
+ maxLevel = Infinity
3749
+ } = {}, level = 1) => {
3750
+ if (level > maxLevel) {
3751
+ return {};
3752
+ }
3753
+ const model = strapi.getModel(uid2);
3754
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3755
+ if (!getDoesAttributeRequireValidation(attribute)) {
3756
+ return populateAcc;
3757
+ }
3758
+ if (isScalarAttribute(attribute)) {
3759
+ return merge(populateAcc, {
3760
+ [attributeName]: true
3761
+ });
3762
+ }
3763
+ return merge(
3764
+ populateAcc,
3765
+ getPopulateFor(
3766
+ attributeName,
3767
+ model,
3768
+ {
3769
+ // @ts-expect-error - improve types
3770
+ initialPopulate: initialPopulate?.[attributeName],
3771
+ countMany,
3772
+ countOne,
3773
+ maxLevel
3774
+ },
3775
+ level
3776
+ )
3777
+ );
3778
+ }, {});
3779
+ };
3627
3780
  const getDeepPopulateDraftCount = (uid2) => {
3628
3781
  const model = strapi.getModel(uid2);
3629
3782
  let hasRelations = false;
@@ -3631,6 +3784,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3631
3784
  const attribute = model.attributes[attributeName];
3632
3785
  switch (attribute.type) {
3633
3786
  case "relation": {
3787
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3788
+ if (isMorphRelation) {
3789
+ break;
3790
+ }
3634
3791
  if (isVisibleAttribute$1(model, attributeName)) {
3635
3792
  populateAcc[attributeName] = {
3636
3793
  count: true,
@@ -3645,22 +3802,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3645
3802
  attribute.component
3646
3803
  );
3647
3804
  if (childHasRelations) {
3648
- populateAcc[attributeName] = { populate: populate2 };
3805
+ populateAcc[attributeName] = {
3806
+ populate: populate2
3807
+ };
3649
3808
  hasRelations = true;
3650
3809
  }
3651
3810
  break;
3652
3811
  }
3653
3812
  case "dynamiczone": {
3654
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3655
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3656
- if (childHasRelations) {
3813
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3814
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3815
+ if (componentHasRelations) {
3657
3816
  hasRelations = true;
3658
- return merge(acc, populate2);
3817
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3659
3818
  }
3660
3819
  return acc;
3661
3820
  }, {});
3662
- if (!isEmpty(dzPopulate)) {
3663
- populateAcc[attributeName] = { populate: dzPopulate };
3821
+ if (!isEmpty(dzPopulateFragment)) {
3822
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3664
3823
  }
3665
3824
  break;
3666
3825
  }
@@ -3852,41 +4011,70 @@ const AVAILABLE_STATUS_FIELDS = [
3852
4011
  "updatedBy",
3853
4012
  "status"
3854
4013
  ];
3855
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4014
+ const AVAILABLE_LOCALES_FIELDS = [
4015
+ "id",
4016
+ "locale",
4017
+ "updatedAt",
4018
+ "createdAt",
4019
+ "status",
4020
+ "publishedAt",
4021
+ "documentId"
4022
+ ];
3856
4023
  const CONTENT_MANAGER_STATUS = {
3857
4024
  PUBLISHED: "published",
3858
4025
  DRAFT: "draft",
3859
4026
  MODIFIED: "modified"
3860
4027
  };
3861
- const areDatesEqual = (date1, date2, threshold) => {
3862
- if (!date1 || !date2) {
4028
+ const getIsVersionLatestModification = (version, otherVersion) => {
4029
+ if (!version || !version.updatedAt) {
3863
4030
  return false;
3864
4031
  }
3865
- const time1 = new Date(date1).getTime();
3866
- const time2 = new Date(date2).getTime();
3867
- const difference2 = Math.abs(time1 - time2);
3868
- return difference2 <= threshold;
4032
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4033
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4034
+ return versionUpdatedAt > otherUpdatedAt;
3869
4035
  };
3870
4036
  const documentMetadata = ({ strapi: strapi2 }) => ({
3871
4037
  /**
3872
4038
  * Returns available locales of a document for the current status
3873
4039
  */
3874
- getAvailableLocales(uid2, version, allVersions) {
4040
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3875
4041
  const versionsByLocale = groupBy("locale", allVersions);
3876
4042
  delete versionsByLocale[version.locale];
3877
- return Object.values(versionsByLocale).map((localeVersions) => {
3878
- if (!contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2))) {
3879
- return pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
4043
+ const model = strapi2.getModel(uid2);
4044
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4045
+ const traversalFunction = async (localeVersion) => traverseEntity(
4046
+ ({ key }, { remove }) => {
4047
+ if (keysToKeep.includes(key)) {
4048
+ return;
4049
+ }
4050
+ remove(key);
4051
+ },
4052
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4053
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4054
+ localeVersion
4055
+ );
4056
+ const mappingResult = await async.map(
4057
+ Object.values(versionsByLocale),
4058
+ async (localeVersions) => {
4059
+ const mappedLocaleVersions = await async.map(
4060
+ localeVersions,
4061
+ traversalFunction
4062
+ );
4063
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
4064
+ return mappedLocaleVersions[0];
4065
+ }
4066
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4067
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4068
+ if (!draftVersion) {
4069
+ return;
4070
+ }
4071
+ return {
4072
+ ...draftVersion,
4073
+ status: this.getStatus(draftVersion, otherVersions)
4074
+ };
3880
4075
  }
3881
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3882
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3883
- if (!draftVersion)
3884
- return;
3885
- return {
3886
- ...pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3887
- status: this.getStatus(draftVersion, otherVersions)
3888
- };
3889
- }).filter(Boolean);
4076
+ );
4077
+ return mappingResult.filter(Boolean);
3890
4078
  },
3891
4079
  /**
3892
4080
  * Returns available status of a document for the current locale
@@ -3924,26 +4112,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3924
4112
  });
3925
4113
  },
3926
4114
  getStatus(version, otherDocumentStatuses) {
3927
- const isDraft = version.publishedAt === null;
3928
- if (!otherDocumentStatuses?.length) {
3929
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4115
+ let draftVersion;
4116
+ let publishedVersion;
4117
+ if (version.publishedAt) {
4118
+ publishedVersion = version;
4119
+ } else {
4120
+ draftVersion = version;
3930
4121
  }
3931
- if (isDraft) {
3932
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3933
- if (!publishedVersion) {
3934
- return CONTENT_MANAGER_STATUS.DRAFT;
3935
- }
4122
+ const otherVersion = otherDocumentStatuses?.at(0);
4123
+ if (otherVersion?.publishedAt) {
4124
+ publishedVersion = otherVersion;
4125
+ } else if (otherVersion) {
4126
+ draftVersion = otherVersion;
3936
4127
  }
3937
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4128
+ if (!draftVersion)
3938
4129
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3939
- }
3940
- return CONTENT_MANAGER_STATUS.MODIFIED;
4130
+ if (!publishedVersion)
4131
+ return CONTENT_MANAGER_STATUS.DRAFT;
4132
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4133
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3941
4134
  },
4135
+ // TODO is it necessary to return metadata on every page of the CM
4136
+ // We could refactor this so the locales are only loaded when they're
4137
+ // needed. e.g. in the bulk locale action modal.
3942
4138
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4139
+ const populate = getValidatableFieldsPopulate(uid2);
3943
4140
  const versions = await strapi2.db.query(uid2).findMany({
3944
4141
  where: { documentId: version.documentId },
3945
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3946
4142
  populate: {
4143
+ // Populate only fields that require validation for bulk locale actions
4144
+ ...populate,
4145
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3947
4146
  createdBy: {
3948
4147
  select: ["id", "firstname", "lastname", "email"]
3949
4148
  },
@@ -3952,7 +4151,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3952
4151
  }
3953
4152
  }
3954
4153
  });
3955
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4154
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3956
4155
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3957
4156
  return {
3958
4157
  availableLocales: availableLocalesResult,
@@ -3965,8 +4164,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3965
4164
  * - Available status of the document for the current locale
3966
4165
  */
3967
4166
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3968
- if (!document)
3969
- return document;
4167
+ if (!document) {
4168
+ return {
4169
+ data: document,
4170
+ meta: {
4171
+ availableLocales: [],
4172
+ availableStatus: []
4173
+ }
4174
+ };
4175
+ }
3970
4176
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3971
4177
  if (!hasDraftAndPublish) {
3972
4178
  opts.availableStatus = false;
@@ -4016,26 +4222,9 @@ const sumDraftCounts = (entity, uid2) => {
4016
4222
  }, 0);
4017
4223
  };
4018
4224
  const { ApplicationError } = errors;
4019
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4020
4225
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
4021
4226
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
4022
4227
  const omitIdField = omit("id");
4023
- const emitEvent = async (uid2, event, document) => {
4024
- const modelDef = strapi.getModel(uid2);
4025
- const sanitizedDocument = await sanitize.sanitizers.defaultSanitizeOutput(
4026
- {
4027
- schema: modelDef,
4028
- getModel(uid22) {
4029
- return strapi.getModel(uid22);
4030
- }
4031
- },
4032
- document
4033
- );
4034
- strapi.eventHub.emit(event, {
4035
- model: modelDef.modelName,
4036
- entry: sanitizedDocument
4037
- });
4038
- };
4039
4228
  const documentManager = ({ strapi: strapi2 }) => {
4040
4229
  return {
4041
4230
  async findOne(id, uid2, opts = {}) {
@@ -4054,6 +4243,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4054
4243
  } else if (opts.locale && opts.locale !== "*") {
4055
4244
  where.locale = opts.locale;
4056
4245
  }
4246
+ if (typeof opts.isPublished === "boolean") {
4247
+ where.publishedAt = { $notNull: opts.isPublished };
4248
+ }
4057
4249
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4058
4250
  },
4059
4251
  async findMany(opts, uid2) {
@@ -4061,20 +4253,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4061
4253
  return strapi2.documents(uid2).findMany(params);
4062
4254
  },
4063
4255
  async findPage(opts, uid2) {
4064
- const page = Number(opts?.page) || 1;
4065
- const pageSize = Number(opts?.pageSize) || 10;
4256
+ const params = pagination.withDefaultPagination(opts || {}, {
4257
+ maxLimit: 1e3
4258
+ });
4066
4259
  const [documents, total = 0] = await Promise.all([
4067
- strapi2.documents(uid2).findMany(opts),
4068
- strapi2.documents(uid2).count(opts)
4260
+ strapi2.documents(uid2).findMany(params),
4261
+ strapi2.documents(uid2).count(params)
4069
4262
  ]);
4070
4263
  return {
4071
4264
  results: documents,
4072
- pagination: {
4073
- page,
4074
- pageSize,
4075
- pageCount: Math.ceil(total / pageSize),
4076
- total
4077
- }
4265
+ pagination: pagination.transformPagedPaginationInfo(params, total)
4078
4266
  };
4079
4267
  },
4080
4268
  async create(uid2, opts = {}) {
@@ -4091,10 +4279,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4091
4279
  async clone(id, body, uid2) {
4092
4280
  const populate = await buildDeepPopulate(uid2);
4093
4281
  const params = {
4094
- data: {
4095
- ...omitIdField(body),
4096
- [PUBLISHED_AT_ATTRIBUTE]: null
4097
- },
4282
+ data: omitIdField(body),
4098
4283
  populate
4099
4284
  };
4100
4285
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4120,70 +4305,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4120
4305
  return {};
4121
4306
  },
4122
4307
  // FIXME: handle relations
4123
- async deleteMany(opts, uid2) {
4124
- const docs = await strapi2.documents(uid2).findMany(opts);
4125
- for (const doc of docs) {
4126
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4127
- }
4128
- return { count: docs.length };
4308
+ async deleteMany(documentIds, uid2, opts = {}) {
4309
+ const deletedEntries = await strapi2.db.transaction(async () => {
4310
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4311
+ });
4312
+ return { count: deletedEntries.length };
4129
4313
  },
4130
4314
  async publish(id, uid2, opts = {}) {
4131
4315
  const populate = await buildDeepPopulate(uid2);
4132
4316
  const params = { ...opts, populate };
4133
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4317
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4134
4318
  },
4135
- async publishMany(entities, uid2) {
4136
- if (!entities.length) {
4137
- return null;
4138
- }
4139
- await Promise.all(
4140
- entities.map((document) => {
4141
- return strapi2.entityValidator.validateEntityCreation(
4142
- strapi2.getModel(uid2),
4143
- document,
4144
- void 0,
4145
- // @ts-expect-error - FIXME: entity here is unnecessary
4146
- document
4147
- );
4148
- })
4149
- );
4150
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4151
- const filters = { id: { $in: entitiesToPublish } };
4152
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4153
- const populate = await buildDeepPopulate(uid2);
4154
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4155
- where: filters,
4156
- data
4157
- });
4158
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4159
- where: filters,
4160
- populate
4319
+ async publishMany(uid2, documentIds, locale) {
4320
+ return strapi2.db.transaction(async () => {
4321
+ const results = await Promise.all(
4322
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4323
+ );
4324
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4325
+ return publishedEntitiesCount;
4161
4326
  });
4162
- await Promise.all(
4163
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4164
- );
4165
- return publishedEntitiesCount;
4166
4327
  },
4167
- async unpublishMany(documents, uid2) {
4168
- if (!documents.length) {
4169
- return null;
4170
- }
4171
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4172
- const filters = { id: { $in: entitiesToUnpublish } };
4173
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4174
- const populate = await buildDeepPopulate(uid2);
4175
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4176
- where: filters,
4177
- data
4178
- });
4179
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4180
- where: filters,
4181
- populate
4328
+ async unpublishMany(documentIds, uid2, opts = {}) {
4329
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4330
+ return Promise.all(
4331
+ documentIds.map(
4332
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4333
+ )
4334
+ );
4182
4335
  });
4183
- await Promise.all(
4184
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4185
- );
4186
- return unpublishedEntitiesCount;
4336
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4337
+ return { count: unpublishedEntitiesCount };
4187
4338
  },
4188
4339
  async unpublish(id, uid2, opts = {}) {
4189
4340
  const populate = await buildDeepPopulate(uid2);
@@ -4208,16 +4359,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4208
4359
  }
4209
4360
  return sumDraftCounts(document, uid2);
4210
4361
  },
4211
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4362
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4212
4363
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4213
4364
  if (!hasRelations) {
4214
4365
  return 0;
4215
4366
  }
4367
+ let localeFilter = {};
4368
+ if (locale) {
4369
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4370
+ }
4216
4371
  const entities = await strapi2.db.query(uid2).findMany({
4217
4372
  populate,
4218
4373
  where: {
4219
- id: { $in: ids },
4220
- ...locale ? { locale } : {}
4374
+ documentId: { $in: documentIds },
4375
+ ...localeFilter
4221
4376
  }
4222
4377
  });
4223
4378
  const totalNumberDraftRelations = entities.reduce(