@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
@@ -199,7 +199,9 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
199
199
  return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
200
200
  };
201
201
  const localesService = strapi2.plugin("i18n")?.service("locales");
202
+ const i18nContentTypeService = strapi2.plugin("i18n")?.service("content-types");
202
203
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
204
+ const isLocalizedContentType = (model) => i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
203
205
  const getLocaleDictionary = async () => {
204
206
  if (!localesService)
205
207
  return {};
@@ -226,20 +228,25 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
226
228
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
227
229
  return documentMetadataService.getStatus(document, meta.availableStatus);
228
230
  };
229
- const getDeepPopulate2 = (uid2) => {
231
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
230
232
  const model = strapi2.getModel(uid2);
231
233
  const attributes = Object.entries(model.attributes);
234
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
232
235
  return attributes.reduce((acc, [attributeName, attribute]) => {
233
236
  switch (attribute.type) {
234
237
  case "relation": {
238
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
239
+ if (isMorphRelation) {
240
+ break;
241
+ }
235
242
  const isVisible2 = strapiUtils.contentTypes.isVisibleAttribute(model, attributeName);
236
243
  if (isVisible2) {
237
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
244
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
238
245
  }
239
246
  break;
240
247
  }
241
248
  case "media": {
242
- acc[attributeName] = { fields: ["id"] };
249
+ acc[attributeName] = { [fieldSelector]: ["id"] };
243
250
  break;
244
251
  }
245
252
  case "component": {
@@ -312,6 +319,7 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
312
319
  getRelationRestoreValue,
313
320
  getMediaRestoreValue,
314
321
  getDefaultLocale,
322
+ isLocalizedContentType,
315
323
  getLocaleDictionary,
316
324
  getRetentionDays,
317
325
  getVersionStatus,
@@ -334,7 +342,13 @@ const createHistoryService = ({ strapi: strapi2 }) => {
334
342
  });
335
343
  },
336
344
  async findVersionsPage(params) {
337
- const locale = params.query.locale || await serviceUtils.getDefaultLocale();
345
+ const model = strapi2.getModel(params.query.contentType);
346
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
347
+ const defaultLocale = await serviceUtils.getDefaultLocale();
348
+ let locale = null;
349
+ if (isLocalizedContentType) {
350
+ locale = params.query.locale || defaultLocale;
351
+ }
338
352
  const [{ results, pagination }, localeDictionary] = await Promise.all([
339
353
  query.findPage({
340
354
  ...params.query,
@@ -490,13 +504,47 @@ const createHistoryService = ({ strapi: strapi2 }) => {
490
504
  }
491
505
  };
492
506
  };
507
+ const shouldCreateHistoryVersion = (context) => {
508
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
509
+ return false;
510
+ }
511
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
512
+ return false;
513
+ }
514
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
515
+ return false;
516
+ }
517
+ if (!context.contentType.uid.startsWith("api::")) {
518
+ return false;
519
+ }
520
+ return true;
521
+ };
522
+ const getSchemas = (uid2) => {
523
+ const attributesSchema = strapi.getModel(uid2).attributes;
524
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
525
+ (currentComponentSchemas, key) => {
526
+ const fieldSchema = attributesSchema[key];
527
+ if (fieldSchema.type === "component") {
528
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
529
+ return {
530
+ ...currentComponentSchemas,
531
+ [fieldSchema.component]: componentSchema
532
+ };
533
+ }
534
+ return currentComponentSchemas;
535
+ },
536
+ {}
537
+ );
538
+ return {
539
+ schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
540
+ componentsSchemas
541
+ };
542
+ };
493
543
  const createLifecyclesService = ({ strapi: strapi2 }) => {
494
544
  const state = {
495
545
  deleteExpiredJob: null,
496
546
  isInitialized: false
497
547
  };
498
- const query = strapi2.db.query(HISTORY_VERSION_UID);
499
- const historyService = getService(strapi2, "history");
500
548
  const serviceUtils = createServiceUtils({ strapi: strapi2 });
501
549
  return {
502
550
  async bootstrap() {
@@ -504,60 +552,53 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
504
552
  return;
505
553
  }
506
554
  strapi2.documents.use(async (context, next) => {
507
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
508
- return next();
509
- }
510
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
511
- return next();
512
- }
513
- const contentTypeUid = context.contentType.uid;
514
- if (!contentTypeUid.startsWith("api::")) {
515
- return next();
516
- }
517
555
  const result = await next();
518
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
556
+ if (!shouldCreateHistoryVersion(context)) {
557
+ return result;
558
+ }
559
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
519
560
  const defaultLocale = await serviceUtils.getDefaultLocale();
520
- const locale = documentContext.locale || defaultLocale;
521
- const document = await strapi2.documents(contentTypeUid).findOne({
522
- documentId: documentContext.documentId,
523
- locale,
524
- populate: serviceUtils.getDeepPopulate(contentTypeUid)
561
+ const locales = fp.castArray(context.params?.locale || defaultLocale);
562
+ if (!locales.length) {
563
+ return result;
564
+ }
565
+ const uid2 = context.contentType.uid;
566
+ const schemas = getSchemas(uid2);
567
+ const model = strapi2.getModel(uid2);
568
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
569
+ const localeEntries = await strapi2.db.query(uid2).findMany({
570
+ where: {
571
+ documentId,
572
+ ...isLocalizedContentType ? { locale: { $in: locales } } : {},
573
+ ...strapiUtils.contentTypes.hasDraftAndPublish(strapi2.contentTypes[uid2]) ? { publishedAt: null } : {}
574
+ },
575
+ populate: serviceUtils.getDeepPopulate(
576
+ uid2,
577
+ true
578
+ /* use database syntax */
579
+ )
525
580
  });
526
- const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
527
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
528
- const componentsSchemas = Object.keys(
529
- attributesSchema
530
- ).reduce((currentComponentSchemas, key) => {
531
- const fieldSchema = attributesSchema[key];
532
- if (fieldSchema.type === "component") {
533
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
534
- return {
535
- ...currentComponentSchemas,
536
- [fieldSchema.component]: componentSchema
537
- };
538
- }
539
- return currentComponentSchemas;
540
- }, {});
541
581
  await strapi2.db.transaction(async ({ onCommit }) => {
542
- onCommit(() => {
543
- historyService.createVersion({
544
- contentType: contentTypeUid,
545
- data: fp.omit(FIELDS_TO_IGNORE, document),
546
- schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
547
- componentsSchemas,
548
- relatedDocumentId: documentContext.documentId,
549
- locale,
550
- status
551
- });
582
+ onCommit(async () => {
583
+ for (const entry of localeEntries) {
584
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
585
+ await getService(strapi2, "history").createVersion({
586
+ contentType: uid2,
587
+ data: fp.omit(FIELDS_TO_IGNORE, entry),
588
+ relatedDocumentId: documentId,
589
+ locale: entry.locale,
590
+ status,
591
+ ...schemas
592
+ });
593
+ }
552
594
  });
553
595
  });
554
596
  return result;
555
597
  });
556
- const retentionDays = serviceUtils.getRetentionDays();
557
598
  state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
558
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
599
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
559
600
  const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
560
- query.deleteMany({
601
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
561
602
  where: {
562
603
  created_at: {
563
604
  $lt: expirationDate.toISOString()
@@ -1189,6 +1230,11 @@ const { createPolicy } = strapiUtils.policy;
1189
1230
  const hasPermissions = createPolicy({
1190
1231
  name: "plugin::content-manager.hasPermissions",
1191
1232
  validator: validateHasPermissionsInput,
1233
+ /**
1234
+ * NOTE: Action aliases are currently not checked at this level (policy).
1235
+ * This is currently the intended behavior to avoid changing the behavior of API related permissions.
1236
+ * If you want to add support for it, please create a dedicated RFC with a list of potential side effect this could have.
1237
+ */
1192
1238
  handler(ctx, config = {}) {
1193
1239
  const { actions = [], hasAtLeastOne = false } = config;
1194
1240
  const { userAbility } = ctx.state;
@@ -1478,7 +1524,7 @@ const { PaginationError, ValidationError } = strapiUtils.errors;
1478
1524
  const TYPES = ["singleType", "collectionType"];
1479
1525
  const kindSchema = strapiUtils.yup.string().oneOf(TYPES).nullable();
1480
1526
  const bulkActionInputSchema = strapiUtils.yup.object({
1481
- ids: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1527
+ documentIds: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1482
1528
  }).required();
1483
1529
  const generateUIDInputSchema = strapiUtils.yup.object({
1484
1530
  contentTypeUID: strapiUtils.yup.string().required(),
@@ -1577,15 +1623,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1577
1623
  }
1578
1624
  }, body);
1579
1625
  };
1580
- const getDocumentLocaleAndStatus = (request) => {
1581
- const { locale, status, ...rest } = request || {};
1582
- if (!fp.isNil(locale) && typeof locale !== "string") {
1583
- throw new strapiUtils.errors.ValidationError(`Invalid locale: ${locale}`);
1584
- }
1585
- if (!fp.isNil(status) && !["draft", "published"].includes(status)) {
1586
- throw new strapiUtils.errors.ValidationError(`Invalid status: ${status}`);
1626
+ const singleLocaleSchema = strapiUtils.yup.string().nullable();
1627
+ const multipleLocaleSchema = strapiUtils.yup.lazy(
1628
+ (value) => Array.isArray(value) ? strapiUtils.yup.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1629
+ );
1630
+ const statusSchema = strapiUtils.yup.mixed().oneOf(["draft", "published"], "Invalid status");
1631
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1632
+ const { allowMultipleLocales } = opts;
1633
+ const { locale, status: providedStatus, ...rest } = request || {};
1634
+ const defaultStatus = strapiUtils.contentTypes.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1635
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1636
+ const schema = strapiUtils.yup.object().shape({
1637
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1638
+ status: statusSchema
1639
+ });
1640
+ try {
1641
+ await strapiUtils.validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1642
+ return { locale, status, ...rest };
1643
+ } catch (error) {
1644
+ throw new strapiUtils.errors.ValidationError(`Validation error: ${error.message}`);
1587
1645
  }
1588
- return { locale, status, ...rest };
1646
+ };
1647
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1648
+ const documentMetadata2 = getService$1("document-metadata");
1649
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1650
+ let {
1651
+ meta: { availableLocales, availableStatus }
1652
+ } = serviceOutput;
1653
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1654
+ availableLocales = await strapiUtils.async.map(
1655
+ availableLocales,
1656
+ async (localeDocument) => metadataSanitizer(localeDocument)
1657
+ );
1658
+ availableStatus = await strapiUtils.async.map(
1659
+ availableStatus,
1660
+ async (statusDocument) => metadataSanitizer(statusDocument)
1661
+ );
1662
+ return {
1663
+ ...serviceOutput,
1664
+ meta: {
1665
+ availableLocales,
1666
+ availableStatus
1667
+ }
1668
+ };
1589
1669
  };
1590
1670
  const createDocument = async (ctx, opts) => {
1591
1671
  const { userAbility, user } = ctx.state;
@@ -1600,7 +1680,7 @@ const createDocument = async (ctx, opts) => {
1600
1680
  const setCreator = strapiUtils.setCreatorFields({ user });
1601
1681
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1602
1682
  const sanitizedBody = await sanitizeFn(body);
1603
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1683
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1604
1684
  return documentManager2.create(model, {
1605
1685
  data: sanitizedBody,
1606
1686
  locale,
@@ -1619,7 +1699,7 @@ const updateDocument = async (ctx, opts) => {
1619
1699
  }
1620
1700
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1621
1701
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1622
- const { locale } = getDocumentLocaleAndStatus(body);
1702
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1623
1703
  const [documentVersion, documentExists] = await Promise.all([
1624
1704
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1625
1705
  documentManager2.exists(model, id)
@@ -1657,7 +1737,7 @@ const collectionTypes = {
1657
1737
  }
1658
1738
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1659
1739
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1660
- const { locale, status } = getDocumentLocaleAndStatus(query);
1740
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1661
1741
  const { results: documents, pagination } = await documentManager2.findPage(
1662
1742
  { ...permissionQuery, populate, locale, status },
1663
1743
  model
@@ -1686,14 +1766,13 @@ const collectionTypes = {
1686
1766
  const { userAbility } = ctx.state;
1687
1767
  const { model, id } = ctx.params;
1688
1768
  const documentManager2 = getService$1("document-manager");
1689
- const documentMetadata2 = getService$1("document-metadata");
1690
1769
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1691
1770
  if (permissionChecker2.cannot.read()) {
1692
1771
  return ctx.forbidden();
1693
1772
  }
1694
1773
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1695
1774
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1696
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1775
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1697
1776
  const version = await documentManager2.findOne(id, model, {
1698
1777
  populate,
1699
1778
  locale,
@@ -1704,9 +1783,11 @@ const collectionTypes = {
1704
1783
  if (!exists) {
1705
1784
  return ctx.notFound();
1706
1785
  }
1707
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1786
+ const { meta } = await formatDocumentWithMetadata(
1787
+ permissionChecker2,
1708
1788
  model,
1709
- { id, locale, publishedAt: null },
1789
+ // @ts-expect-error TODO: fix
1790
+ { documentId: id, locale, publishedAt: null },
1710
1791
  { availableLocales: true, availableStatus: false }
1711
1792
  );
1712
1793
  ctx.body = { data: {}, meta };
@@ -1716,12 +1797,11 @@ const collectionTypes = {
1716
1797
  return ctx.forbidden();
1717
1798
  }
1718
1799
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1719
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1800
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1720
1801
  },
1721
1802
  async create(ctx) {
1722
1803
  const { userAbility } = ctx.state;
1723
1804
  const { model } = ctx.params;
1724
- const documentMetadata2 = getService$1("document-metadata");
1725
1805
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1726
1806
  const [totalEntries, document] = await Promise.all([
1727
1807
  strapi.db.query(model).count(),
@@ -1729,7 +1809,7 @@ const collectionTypes = {
1729
1809
  ]);
1730
1810
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1731
1811
  ctx.status = 201;
1732
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1812
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1733
1813
  // Empty metadata as it's not relevant for a new document
1734
1814
  availableLocales: false,
1735
1815
  availableStatus: false
@@ -1743,25 +1823,23 @@ const collectionTypes = {
1743
1823
  async update(ctx) {
1744
1824
  const { userAbility } = ctx.state;
1745
1825
  const { model } = ctx.params;
1746
- const documentMetadata2 = getService$1("document-metadata");
1747
1826
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1748
1827
  const updatedVersion = await updateDocument(ctx);
1749
1828
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1750
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1829
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1751
1830
  },
1752
1831
  async clone(ctx) {
1753
1832
  const { userAbility, user } = ctx.state;
1754
1833
  const { model, sourceId: id } = ctx.params;
1755
1834
  const { body } = ctx.request;
1756
1835
  const documentManager2 = getService$1("document-manager");
1757
- const documentMetadata2 = getService$1("document-metadata");
1758
1836
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1759
1837
  if (permissionChecker2.cannot.create()) {
1760
1838
  return ctx.forbidden();
1761
1839
  }
1762
1840
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1763
1841
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1764
- const { locale } = getDocumentLocaleAndStatus(body);
1842
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1765
1843
  const document = await documentManager2.findOne(id, model, {
1766
1844
  populate,
1767
1845
  locale,
@@ -1777,7 +1855,7 @@ const collectionTypes = {
1777
1855
  const sanitizedBody = await sanitizeFn(body);
1778
1856
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1779
1857
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1858
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1781
1859
  // Empty metadata as it's not relevant for a new document
1782
1860
  availableLocales: false,
1783
1861
  availableStatus: false
@@ -1806,7 +1884,7 @@ const collectionTypes = {
1806
1884
  }
1807
1885
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1808
1886
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1809
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1887
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1810
1888
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1811
1889
  if (documentLocales.length === 0) {
1812
1890
  return ctx.notFound();
@@ -1828,7 +1906,6 @@ const collectionTypes = {
1828
1906
  const { id, model } = ctx.params;
1829
1907
  const { body } = ctx.request;
1830
1908
  const documentManager2 = getService$1("document-manager");
1831
- const documentMetadata2 = getService$1("document-metadata");
1832
1909
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1833
1910
  if (permissionChecker2.cannot.publish()) {
1834
1911
  return ctx.forbidden();
@@ -1836,25 +1913,46 @@ const collectionTypes = {
1836
1913
  const publishedDocument = await strapi.db.transaction(async () => {
1837
1914
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1838
1915
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1839
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1916
+ let document;
1917
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1918
+ const isCreate = fp.isNil(id);
1919
+ if (isCreate) {
1920
+ if (permissionChecker2.cannot.create()) {
1921
+ throw new strapiUtils.errors.ForbiddenError();
1922
+ }
1923
+ document = await createDocument(ctx, { populate });
1924
+ }
1925
+ const isUpdate = !isCreate;
1926
+ if (isUpdate) {
1927
+ document = await documentManager2.findOne(id, model, { populate, locale });
1928
+ if (!document) {
1929
+ throw new strapiUtils.errors.NotFoundError("Document not found");
1930
+ }
1931
+ if (permissionChecker2.can.update(document)) {
1932
+ await updateDocument(ctx);
1933
+ }
1934
+ }
1840
1935
  if (permissionChecker2.cannot.publish(document)) {
1841
1936
  throw new strapiUtils.errors.ForbiddenError();
1842
1937
  }
1843
- const { locale } = getDocumentLocaleAndStatus(body);
1844
- return documentManager2.publish(document.documentId, model, {
1938
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1845
1939
  locale
1846
1940
  // TODO: Allow setting creator fields on publish
1847
1941
  // data: setCreatorFields({ user, isEdition: true })({}),
1848
1942
  });
1943
+ if (!publishResult || publishResult.length === 0) {
1944
+ throw new strapiUtils.errors.NotFoundError("Document not found or already published.");
1945
+ }
1946
+ return publishResult[0];
1849
1947
  });
1850
1948
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1851
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1949
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1852
1950
  },
1853
1951
  async bulkPublish(ctx) {
1854
1952
  const { userAbility } = ctx.state;
1855
1953
  const { model } = ctx.params;
1856
1954
  const { body } = ctx.request;
1857
- const { ids } = body;
1955
+ const { documentIds } = body;
1858
1956
  await validateBulkActionInput(body);
1859
1957
  const documentManager2 = getService$1("document-manager");
1860
1958
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1863,8 +1961,13 @@ const collectionTypes = {
1863
1961
  }
1864
1962
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1865
1963
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1866
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1867
- const entities = await Promise.all(entityPromises);
1964
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1965
+ allowMultipleLocales: true
1966
+ });
1967
+ const entityPromises = documentIds.map(
1968
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1969
+ );
1970
+ const entities = (await Promise.all(entityPromises)).flat();
1868
1971
  for (const entity of entities) {
1869
1972
  if (!entity) {
1870
1973
  return ctx.notFound();
@@ -1873,24 +1976,27 @@ const collectionTypes = {
1873
1976
  return ctx.forbidden();
1874
1977
  }
1875
1978
  }
1876
- const { count } = await documentManager2.publishMany(entities, model);
1979
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1877
1980
  ctx.body = { count };
1878
1981
  },
1879
1982
  async bulkUnpublish(ctx) {
1880
1983
  const { userAbility } = ctx.state;
1881
1984
  const { model } = ctx.params;
1882
1985
  const { body } = ctx.request;
1883
- const { ids } = body;
1986
+ const { documentIds } = body;
1884
1987
  await validateBulkActionInput(body);
1885
1988
  const documentManager2 = getService$1("document-manager");
1886
1989
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1887
1990
  if (permissionChecker2.cannot.unpublish()) {
1888
1991
  return ctx.forbidden();
1889
1992
  }
1890
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1891
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1892
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1893
- const entities = await Promise.all(entityPromises);
1993
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1994
+ allowMultipleLocales: true
1995
+ });
1996
+ const entityPromises = documentIds.map(
1997
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1998
+ );
1999
+ const entities = (await Promise.all(entityPromises)).flat();
1894
2000
  for (const entity of entities) {
1895
2001
  if (!entity) {
1896
2002
  return ctx.notFound();
@@ -1899,7 +2005,8 @@ const collectionTypes = {
1899
2005
  return ctx.forbidden();
1900
2006
  }
1901
2007
  }
1902
- const { count } = await documentManager2.unpublishMany(entities, model);
2008
+ const entitiesIds = entities.map((document) => document.documentId);
2009
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1903
2010
  ctx.body = { count };
1904
2011
  },
1905
2012
  async unpublish(ctx) {
@@ -1909,7 +2016,6 @@ const collectionTypes = {
1909
2016
  body: { discardDraft, ...body }
1910
2017
  } = ctx.request;
1911
2018
  const documentManager2 = getService$1("document-manager");
1912
- const documentMetadata2 = getService$1("document-metadata");
1913
2019
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1914
2020
  if (permissionChecker2.cannot.unpublish()) {
1915
2021
  return ctx.forbidden();
@@ -1919,7 +2025,7 @@ const collectionTypes = {
1919
2025
  }
1920
2026
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1921
2027
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1922
- const { locale } = getDocumentLocaleAndStatus(body);
2028
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1923
2029
  const document = await documentManager2.findOne(id, model, {
1924
2030
  populate,
1925
2031
  locale,
@@ -1941,7 +2047,7 @@ const collectionTypes = {
1941
2047
  ctx.body = await strapiUtils.async.pipe(
1942
2048
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1943
2049
  permissionChecker2.sanitizeOutput,
1944
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2050
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1945
2051
  )(document);
1946
2052
  });
1947
2053
  },
@@ -1950,14 +2056,13 @@ const collectionTypes = {
1950
2056
  const { id, model } = ctx.params;
1951
2057
  const { body } = ctx.request;
1952
2058
  const documentManager2 = getService$1("document-manager");
1953
- const documentMetadata2 = getService$1("document-metadata");
1954
2059
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1955
2060
  if (permissionChecker2.cannot.discard()) {
1956
2061
  return ctx.forbidden();
1957
2062
  }
1958
2063
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1959
2064
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1960
- const { locale } = getDocumentLocaleAndStatus(body);
2065
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1961
2066
  const document = await documentManager2.findOne(id, model, {
1962
2067
  populate,
1963
2068
  locale,
@@ -1972,14 +2077,14 @@ const collectionTypes = {
1972
2077
  ctx.body = await strapiUtils.async.pipe(
1973
2078
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1974
2079
  permissionChecker2.sanitizeOutput,
1975
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2080
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1976
2081
  )(document);
1977
2082
  },
1978
2083
  async bulkDelete(ctx) {
1979
2084
  const { userAbility } = ctx.state;
1980
2085
  const { model } = ctx.params;
1981
2086
  const { query, body } = ctx.request;
1982
- const { ids } = body;
2087
+ const { documentIds } = body;
1983
2088
  await validateBulkActionInput(body);
1984
2089
  const documentManager2 = getService$1("document-manager");
1985
2090
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1987,14 +2092,22 @@ const collectionTypes = {
1987
2092
  return ctx.forbidden();
1988
2093
  }
1989
2094
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1990
- const idsWhereClause = { id: { $in: ids } };
1991
- const params = {
1992
- ...permissionQuery,
1993
- filters: {
1994
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2095
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2096
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2097
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2098
+ populate,
2099
+ locale
2100
+ });
2101
+ if (documentLocales.length === 0) {
2102
+ return ctx.notFound();
2103
+ }
2104
+ for (const document of documentLocales) {
2105
+ if (permissionChecker2.cannot.delete(document)) {
2106
+ return ctx.forbidden();
1995
2107
  }
1996
- };
1997
- const { count } = await documentManager2.deleteMany(params, model);
2108
+ }
2109
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2110
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1998
2111
  ctx.body = { count };
1999
2112
  },
2000
2113
  async countDraftRelations(ctx) {
@@ -2007,7 +2120,7 @@ const collectionTypes = {
2007
2120
  }
2008
2121
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
2009
2122
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2010
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2123
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
2011
2124
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
2012
2125
  if (!entity) {
2013
2126
  return ctx.notFound();
@@ -2022,7 +2135,7 @@ const collectionTypes = {
2022
2135
  },
2023
2136
  async countManyEntriesDraftRelations(ctx) {
2024
2137
  const { userAbility } = ctx.state;
2025
- const ids = ctx.request.query.ids;
2138
+ const ids = ctx.request.query.documentIds;
2026
2139
  const locale = ctx.request.query.locale;
2027
2140
  const { model } = ctx.params;
2028
2141
  const documentManager2 = getService$1("document-manager");
@@ -2030,16 +2143,16 @@ const collectionTypes = {
2030
2143
  if (permissionChecker2.cannot.read()) {
2031
2144
  return ctx.forbidden();
2032
2145
  }
2033
- const entities = await documentManager2.findMany(
2146
+ const documents = await documentManager2.findMany(
2034
2147
  {
2035
2148
  filters: {
2036
- id: ids
2149
+ documentId: ids
2037
2150
  },
2038
2151
  locale
2039
2152
  },
2040
2153
  model
2041
2154
  );
2042
- if (!entities) {
2155
+ if (!documents) {
2043
2156
  return ctx.notFound();
2044
2157
  }
2045
2158
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2230,20 +2343,13 @@ const sanitizeMainField = (model, mainField, userAbility) => {
2230
2343
  userAbility,
2231
2344
  model: model.uid
2232
2345
  });
2233
- if (!isListable(model, mainField)) {
2346
+ const isMainFieldListable = isListable(model, mainField);
2347
+ const canReadMainField = permissionChecker2.can.read(null, mainField);
2348
+ if (!isMainFieldListable || !canReadMainField) {
2234
2349
  return "id";
2235
2350
  }
2236
- if (permissionChecker2.cannot.read(null, mainField)) {
2237
- if (model.uid === "plugin::users-permissions.role") {
2238
- const userPermissionChecker = getService$1("permission-checker").create({
2239
- userAbility,
2240
- model: "plugin::users-permissions.user"
2241
- });
2242
- if (userPermissionChecker.can.read()) {
2243
- return "name";
2244
- }
2245
- }
2246
- return "id";
2351
+ if (model.uid === "plugin::users-permissions.role") {
2352
+ return "name";
2247
2353
  }
2248
2354
  return mainField;
2249
2355
  };
@@ -2501,9 +2607,7 @@ const relations = {
2501
2607
  addFiltersClause(permissionQuery, { id: { $in: loadedIds } });
2502
2608
  const sanitizedRes = await loadRelations({ id: entryId }, targetField, {
2503
2609
  ...strapi.get("query-params").transform(targetUid, permissionQuery),
2504
- ordering: "desc",
2505
- page: ctx.request.query.page,
2506
- pageSize: ctx.request.query.pageSize
2610
+ ordering: "desc"
2507
2611
  });
2508
2612
  const relationsUnion = fp.uniqBy("id", fp.concat(sanitizedRes.results, res.results));
2509
2613
  ctx.body = {
@@ -2535,7 +2639,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2535
2639
  throw new strapiUtils.errors.ForbiddenError();
2536
2640
  }
2537
2641
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2538
- const { locale } = getDocumentLocaleAndStatus(body);
2642
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2539
2643
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2540
2644
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2541
2645
  // Find the first document to check if it exists
@@ -2572,12 +2676,11 @@ const singleTypes = {
2572
2676
  const { model } = ctx.params;
2573
2677
  const { query = {} } = ctx.request;
2574
2678
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2575
- const documentMetadata2 = getService$1("document-metadata");
2576
2679
  if (permissionChecker2.cannot.read()) {
2577
2680
  return ctx.forbidden();
2578
2681
  }
2579
2682
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2580
- const { locale, status } = getDocumentLocaleAndStatus(query);
2683
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2581
2684
  const version = await findDocument(permissionQuery, model, { locale, status });
2582
2685
  if (!version) {
2583
2686
  if (permissionChecker2.cannot.create()) {
@@ -2587,8 +2690,10 @@ const singleTypes = {
2587
2690
  if (!document) {
2588
2691
  return ctx.notFound();
2589
2692
  }
2590
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2693
+ const { meta } = await formatDocumentWithMetadata(
2694
+ permissionChecker2,
2591
2695
  model,
2696
+ // @ts-expect-error - fix types
2592
2697
  { id: document.documentId, locale, publishedAt: null },
2593
2698
  { availableLocales: true, availableStatus: false }
2594
2699
  );
@@ -2599,16 +2704,15 @@ const singleTypes = {
2599
2704
  return ctx.forbidden();
2600
2705
  }
2601
2706
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2602
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2707
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2603
2708
  },
2604
2709
  async createOrUpdate(ctx) {
2605
2710
  const { userAbility } = ctx.state;
2606
2711
  const { model } = ctx.params;
2607
- const documentMetadata2 = getService$1("document-metadata");
2608
2712
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2609
2713
  const document = await createOrUpdateDocument(ctx);
2610
2714
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2611
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2715
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2612
2716
  },
2613
2717
  async delete(ctx) {
2614
2718
  const { userAbility } = ctx.state;
@@ -2621,7 +2725,7 @@ const singleTypes = {
2621
2725
  }
2622
2726
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2623
2727
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2624
- const { locale } = getDocumentLocaleAndStatus(query);
2728
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2625
2729
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2626
2730
  populate,
2627
2731
  locale
@@ -2644,7 +2748,6 @@ const singleTypes = {
2644
2748
  const { model } = ctx.params;
2645
2749
  const { query = {} } = ctx.request;
2646
2750
  const documentManager2 = getService$1("document-manager");
2647
- const documentMetadata2 = getService$1("document-metadata");
2648
2751
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2649
2752
  if (permissionChecker2.cannot.publish()) {
2650
2753
  return ctx.forbidden();
@@ -2659,11 +2762,12 @@ const singleTypes = {
2659
2762
  if (permissionChecker2.cannot.publish(document)) {
2660
2763
  throw new strapiUtils.errors.ForbiddenError();
2661
2764
  }
2662
- const { locale } = getDocumentLocaleAndStatus(document);
2663
- return documentManager2.publish(document.documentId, model, { locale });
2765
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2766
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2767
+ return publishResult.at(0);
2664
2768
  });
2665
2769
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2666
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2770
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2667
2771
  },
2668
2772
  async unpublish(ctx) {
2669
2773
  const { userAbility } = ctx.state;
@@ -2673,7 +2777,6 @@ const singleTypes = {
2673
2777
  query = {}
2674
2778
  } = ctx.request;
2675
2779
  const documentManager2 = getService$1("document-manager");
2676
- const documentMetadata2 = getService$1("document-metadata");
2677
2780
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2678
2781
  if (permissionChecker2.cannot.unpublish()) {
2679
2782
  return ctx.forbidden();
@@ -2682,7 +2785,7 @@ const singleTypes = {
2682
2785
  return ctx.forbidden();
2683
2786
  }
2684
2787
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2685
- const { locale } = getDocumentLocaleAndStatus(body);
2788
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2686
2789
  const document = await findDocument(sanitizedQuery, model, { locale });
2687
2790
  if (!document) {
2688
2791
  return ctx.notFound();
@@ -2700,7 +2803,7 @@ const singleTypes = {
2700
2803
  ctx.body = await strapiUtils.async.pipe(
2701
2804
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2702
2805
  permissionChecker2.sanitizeOutput,
2703
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2806
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2704
2807
  )(document);
2705
2808
  });
2706
2809
  },
@@ -2709,13 +2812,12 @@ const singleTypes = {
2709
2812
  const { model } = ctx.params;
2710
2813
  const { body, query = {} } = ctx.request;
2711
2814
  const documentManager2 = getService$1("document-manager");
2712
- const documentMetadata2 = getService$1("document-metadata");
2713
2815
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2714
2816
  if (permissionChecker2.cannot.discard()) {
2715
2817
  return ctx.forbidden();
2716
2818
  }
2717
2819
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2718
- const { locale } = getDocumentLocaleAndStatus(body);
2820
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2719
2821
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2720
2822
  if (!document) {
2721
2823
  return ctx.notFound();
@@ -2726,7 +2828,7 @@ const singleTypes = {
2726
2828
  ctx.body = await strapiUtils.async.pipe(
2727
2829
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2728
2830
  permissionChecker2.sanitizeOutput,
2729
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2831
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2730
2832
  )(document);
2731
2833
  },
2732
2834
  async countDraftRelations(ctx) {
@@ -2735,7 +2837,7 @@ const singleTypes = {
2735
2837
  const { query } = ctx.request;
2736
2838
  const documentManager2 = getService$1("document-manager");
2737
2839
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2738
- const { locale } = getDocumentLocaleAndStatus(query);
2840
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2739
2841
  if (permissionChecker2.cannot.read()) {
2740
2842
  return ctx.forbidden();
2741
2843
  }
@@ -2756,7 +2858,7 @@ const uid$1 = {
2756
2858
  async generateUID(ctx) {
2757
2859
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2758
2860
  const { query = {} } = ctx.request;
2759
- const { locale } = getDocumentLocaleAndStatus(query);
2861
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2760
2862
  await validateUIDField(contentTypeUID, field);
2761
2863
  const uidService = getService$1("uid");
2762
2864
  ctx.body = {
@@ -2768,7 +2870,7 @@ const uid$1 = {
2768
2870
  ctx.request.body
2769
2871
  );
2770
2872
  const { query = {} } = ctx.request;
2771
- const { locale } = getDocumentLocaleAndStatus(query);
2873
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2772
2874
  await validateUIDField(contentTypeUID, field);
2773
2875
  const uidService = getService$1("uid");
2774
2876
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3411,12 +3513,27 @@ const createPermissionChecker = (strapi2) => ({ userAbility, model }) => {
3411
3513
  ability: userAbility,
3412
3514
  model
3413
3515
  });
3414
- const toSubject = (entity) => entity ? permissionsManager.toSubject(entity, model) : model;
3516
+ const { actionProvider } = strapi2.service("admin::permission");
3517
+ const toSubject = (entity) => {
3518
+ return entity ? permissionsManager.toSubject(entity, model) : model;
3519
+ };
3415
3520
  const can = (action, entity, field) => {
3416
- return userAbility.can(action, toSubject(entity), field);
3521
+ const subject = toSubject(entity);
3522
+ const aliases = actionProvider.unstable_aliases(action, model);
3523
+ return (
3524
+ // Test the original action to see if it passes
3525
+ userAbility.can(action, subject, field) || // Else try every known alias if at least one of them succeed, then the user "can"
3526
+ aliases.some((alias) => userAbility.can(alias, subject, field))
3527
+ );
3417
3528
  };
3418
3529
  const cannot = (action, entity, field) => {
3419
- return userAbility.cannot(action, toSubject(entity), field);
3530
+ const subject = toSubject(entity);
3531
+ const aliases = actionProvider.unstable_aliases(action, model);
3532
+ return (
3533
+ // Test both the original action
3534
+ userAbility.cannot(action, subject, field) && // and every known alias, if all of them fail (cannot), then the user truly "cannot"
3535
+ aliases.every((alias) => userAbility.cannot(alias, subject, field))
3536
+ );
3420
3537
  };
3421
3538
  const sanitizeOutput = (data, { action = ACTIONS.read } = {}) => {
3422
3539
  return permissionsManager.sanitizeOutput(data, { subject: toSubject(data), action });
@@ -3559,7 +3676,7 @@ const permission = ({ strapi: strapi2 }) => ({
3559
3676
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3560
3677
  }
3561
3678
  });
3562
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils__default.default.contentTypes;
3679
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils__default.default.contentTypes;
3563
3680
  const { isAnyToMany } = strapiUtils__default.default.relations;
3564
3681
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils__default.default.contentTypes.constants;
3565
3682
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3650,6 +3767,42 @@ const getDeepPopulate = (uid2, {
3650
3767
  {}
3651
3768
  );
3652
3769
  };
3770
+ const getValidatableFieldsPopulate = (uid2, {
3771
+ initialPopulate = {},
3772
+ countMany = false,
3773
+ countOne = false,
3774
+ maxLevel = Infinity
3775
+ } = {}, level = 1) => {
3776
+ if (level > maxLevel) {
3777
+ return {};
3778
+ }
3779
+ const model = strapi.getModel(uid2);
3780
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3781
+ if (!getDoesAttributeRequireValidation(attribute)) {
3782
+ return populateAcc;
3783
+ }
3784
+ if (isScalarAttribute(attribute)) {
3785
+ return fp.merge(populateAcc, {
3786
+ [attributeName]: true
3787
+ });
3788
+ }
3789
+ return fp.merge(
3790
+ populateAcc,
3791
+ getPopulateFor(
3792
+ attributeName,
3793
+ model,
3794
+ {
3795
+ // @ts-expect-error - improve types
3796
+ initialPopulate: initialPopulate?.[attributeName],
3797
+ countMany,
3798
+ countOne,
3799
+ maxLevel
3800
+ },
3801
+ level
3802
+ )
3803
+ );
3804
+ }, {});
3805
+ };
3653
3806
  const getDeepPopulateDraftCount = (uid2) => {
3654
3807
  const model = strapi.getModel(uid2);
3655
3808
  let hasRelations = false;
@@ -3657,6 +3810,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3657
3810
  const attribute = model.attributes[attributeName];
3658
3811
  switch (attribute.type) {
3659
3812
  case "relation": {
3813
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3814
+ if (isMorphRelation) {
3815
+ break;
3816
+ }
3660
3817
  if (isVisibleAttribute$1(model, attributeName)) {
3661
3818
  populateAcc[attributeName] = {
3662
3819
  count: true,
@@ -3671,22 +3828,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3671
3828
  attribute.component
3672
3829
  );
3673
3830
  if (childHasRelations) {
3674
- populateAcc[attributeName] = { populate: populate2 };
3831
+ populateAcc[attributeName] = {
3832
+ populate: populate2
3833
+ };
3675
3834
  hasRelations = true;
3676
3835
  }
3677
3836
  break;
3678
3837
  }
3679
3838
  case "dynamiczone": {
3680
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3681
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3682
- if (childHasRelations) {
3839
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3840
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3841
+ if (componentHasRelations) {
3683
3842
  hasRelations = true;
3684
- return fp.merge(acc, populate2);
3843
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3685
3844
  }
3686
3845
  return acc;
3687
3846
  }, {});
3688
- if (!fp.isEmpty(dzPopulate)) {
3689
- populateAcc[attributeName] = { populate: dzPopulate };
3847
+ if (!fp.isEmpty(dzPopulateFragment)) {
3848
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3690
3849
  }
3691
3850
  break;
3692
3851
  }
@@ -3878,41 +4037,70 @@ const AVAILABLE_STATUS_FIELDS = [
3878
4037
  "updatedBy",
3879
4038
  "status"
3880
4039
  ];
3881
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4040
+ const AVAILABLE_LOCALES_FIELDS = [
4041
+ "id",
4042
+ "locale",
4043
+ "updatedAt",
4044
+ "createdAt",
4045
+ "status",
4046
+ "publishedAt",
4047
+ "documentId"
4048
+ ];
3882
4049
  const CONTENT_MANAGER_STATUS = {
3883
4050
  PUBLISHED: "published",
3884
4051
  DRAFT: "draft",
3885
4052
  MODIFIED: "modified"
3886
4053
  };
3887
- const areDatesEqual = (date1, date2, threshold) => {
3888
- if (!date1 || !date2) {
4054
+ const getIsVersionLatestModification = (version, otherVersion) => {
4055
+ if (!version || !version.updatedAt) {
3889
4056
  return false;
3890
4057
  }
3891
- const time1 = new Date(date1).getTime();
3892
- const time2 = new Date(date2).getTime();
3893
- const difference = Math.abs(time1 - time2);
3894
- return difference <= threshold;
4058
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4059
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4060
+ return versionUpdatedAt > otherUpdatedAt;
3895
4061
  };
3896
4062
  const documentMetadata = ({ strapi: strapi2 }) => ({
3897
4063
  /**
3898
4064
  * Returns available locales of a document for the current status
3899
4065
  */
3900
- getAvailableLocales(uid2, version, allVersions) {
4066
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3901
4067
  const versionsByLocale = fp.groupBy("locale", allVersions);
3902
4068
  delete versionsByLocale[version.locale];
3903
- return Object.values(versionsByLocale).map((localeVersions) => {
3904
- if (!strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2))) {
3905
- return fp.pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
4069
+ const model = strapi2.getModel(uid2);
4070
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4071
+ const traversalFunction = async (localeVersion) => strapiUtils.traverseEntity(
4072
+ ({ key }, { remove }) => {
4073
+ if (keysToKeep.includes(key)) {
4074
+ return;
4075
+ }
4076
+ remove(key);
4077
+ },
4078
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4079
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4080
+ localeVersion
4081
+ );
4082
+ const mappingResult = await strapiUtils.async.map(
4083
+ Object.values(versionsByLocale),
4084
+ async (localeVersions) => {
4085
+ const mappedLocaleVersions = await strapiUtils.async.map(
4086
+ localeVersions,
4087
+ traversalFunction
4088
+ );
4089
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(model)) {
4090
+ return mappedLocaleVersions[0];
4091
+ }
4092
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4093
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4094
+ if (!draftVersion) {
4095
+ return;
4096
+ }
4097
+ return {
4098
+ ...draftVersion,
4099
+ status: this.getStatus(draftVersion, otherVersions)
4100
+ };
3906
4101
  }
3907
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3908
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3909
- if (!draftVersion)
3910
- return;
3911
- return {
3912
- ...fp.pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3913
- status: this.getStatus(draftVersion, otherVersions)
3914
- };
3915
- }).filter(Boolean);
4102
+ );
4103
+ return mappingResult.filter(Boolean);
3916
4104
  },
3917
4105
  /**
3918
4106
  * Returns available status of a document for the current locale
@@ -3950,26 +4138,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3950
4138
  });
3951
4139
  },
3952
4140
  getStatus(version, otherDocumentStatuses) {
3953
- const isDraft = version.publishedAt === null;
3954
- if (!otherDocumentStatuses?.length) {
3955
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4141
+ let draftVersion;
4142
+ let publishedVersion;
4143
+ if (version.publishedAt) {
4144
+ publishedVersion = version;
4145
+ } else {
4146
+ draftVersion = version;
3956
4147
  }
3957
- if (isDraft) {
3958
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3959
- if (!publishedVersion) {
3960
- return CONTENT_MANAGER_STATUS.DRAFT;
3961
- }
4148
+ const otherVersion = otherDocumentStatuses?.at(0);
4149
+ if (otherVersion?.publishedAt) {
4150
+ publishedVersion = otherVersion;
4151
+ } else if (otherVersion) {
4152
+ draftVersion = otherVersion;
3962
4153
  }
3963
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4154
+ if (!draftVersion)
3964
4155
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3965
- }
3966
- return CONTENT_MANAGER_STATUS.MODIFIED;
4156
+ if (!publishedVersion)
4157
+ return CONTENT_MANAGER_STATUS.DRAFT;
4158
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4159
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3967
4160
  },
4161
+ // TODO is it necessary to return metadata on every page of the CM
4162
+ // We could refactor this so the locales are only loaded when they're
4163
+ // needed. e.g. in the bulk locale action modal.
3968
4164
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4165
+ const populate = getValidatableFieldsPopulate(uid2);
3969
4166
  const versions = await strapi2.db.query(uid2).findMany({
3970
4167
  where: { documentId: version.documentId },
3971
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3972
4168
  populate: {
4169
+ // Populate only fields that require validation for bulk locale actions
4170
+ ...populate,
4171
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3973
4172
  createdBy: {
3974
4173
  select: ["id", "firstname", "lastname", "email"]
3975
4174
  },
@@ -3978,7 +4177,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3978
4177
  }
3979
4178
  }
3980
4179
  });
3981
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4180
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3982
4181
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3983
4182
  return {
3984
4183
  availableLocales: availableLocalesResult,
@@ -3991,8 +4190,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3991
4190
  * - Available status of the document for the current locale
3992
4191
  */
3993
4192
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3994
- if (!document)
3995
- return document;
4193
+ if (!document) {
4194
+ return {
4195
+ data: document,
4196
+ meta: {
4197
+ availableLocales: [],
4198
+ availableStatus: []
4199
+ }
4200
+ };
4201
+ }
3996
4202
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2));
3997
4203
  if (!hasDraftAndPublish) {
3998
4204
  opts.availableStatus = false;
@@ -4042,26 +4248,9 @@ const sumDraftCounts = (entity, uid2) => {
4042
4248
  }, 0);
4043
4249
  };
4044
4250
  const { ApplicationError } = strapiUtils.errors;
4045
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4046
4251
  const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
4047
4252
  const omitPublishedAtField = fp.omit(PUBLISHED_AT_ATTRIBUTE);
4048
4253
  const omitIdField = fp.omit("id");
4049
- const emitEvent = async (uid2, event, document) => {
4050
- const modelDef = strapi.getModel(uid2);
4051
- const sanitizedDocument = await strapiUtils.sanitize.sanitizers.defaultSanitizeOutput(
4052
- {
4053
- schema: modelDef,
4054
- getModel(uid22) {
4055
- return strapi.getModel(uid22);
4056
- }
4057
- },
4058
- document
4059
- );
4060
- strapi.eventHub.emit(event, {
4061
- model: modelDef.modelName,
4062
- entry: sanitizedDocument
4063
- });
4064
- };
4065
4254
  const documentManager = ({ strapi: strapi2 }) => {
4066
4255
  return {
4067
4256
  async findOne(id, uid2, opts = {}) {
@@ -4080,6 +4269,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4080
4269
  } else if (opts.locale && opts.locale !== "*") {
4081
4270
  where.locale = opts.locale;
4082
4271
  }
4272
+ if (typeof opts.isPublished === "boolean") {
4273
+ where.publishedAt = { $notNull: opts.isPublished };
4274
+ }
4083
4275
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4084
4276
  },
4085
4277
  async findMany(opts, uid2) {
@@ -4087,20 +4279,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4087
4279
  return strapi2.documents(uid2).findMany(params);
4088
4280
  },
4089
4281
  async findPage(opts, uid2) {
4090
- const page = Number(opts?.page) || 1;
4091
- const pageSize = Number(opts?.pageSize) || 10;
4282
+ const params = strapiUtils.pagination.withDefaultPagination(opts || {}, {
4283
+ maxLimit: 1e3
4284
+ });
4092
4285
  const [documents, total = 0] = await Promise.all([
4093
- strapi2.documents(uid2).findMany(opts),
4094
- strapi2.documents(uid2).count(opts)
4286
+ strapi2.documents(uid2).findMany(params),
4287
+ strapi2.documents(uid2).count(params)
4095
4288
  ]);
4096
4289
  return {
4097
4290
  results: documents,
4098
- pagination: {
4099
- page,
4100
- pageSize,
4101
- pageCount: Math.ceil(total / pageSize),
4102
- total
4103
- }
4291
+ pagination: strapiUtils.pagination.transformPagedPaginationInfo(params, total)
4104
4292
  };
4105
4293
  },
4106
4294
  async create(uid2, opts = {}) {
@@ -4117,10 +4305,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4117
4305
  async clone(id, body, uid2) {
4118
4306
  const populate = await buildDeepPopulate(uid2);
4119
4307
  const params = {
4120
- data: {
4121
- ...omitIdField(body),
4122
- [PUBLISHED_AT_ATTRIBUTE]: null
4123
- },
4308
+ data: omitIdField(body),
4124
4309
  populate
4125
4310
  };
4126
4311
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4146,70 +4331,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4146
4331
  return {};
4147
4332
  },
4148
4333
  // FIXME: handle relations
4149
- async deleteMany(opts, uid2) {
4150
- const docs = await strapi2.documents(uid2).findMany(opts);
4151
- for (const doc of docs) {
4152
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4153
- }
4154
- return { count: docs.length };
4334
+ async deleteMany(documentIds, uid2, opts = {}) {
4335
+ const deletedEntries = await strapi2.db.transaction(async () => {
4336
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4337
+ });
4338
+ return { count: deletedEntries.length };
4155
4339
  },
4156
4340
  async publish(id, uid2, opts = {}) {
4157
4341
  const populate = await buildDeepPopulate(uid2);
4158
4342
  const params = { ...opts, populate };
4159
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4343
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4160
4344
  },
4161
- async publishMany(entities, uid2) {
4162
- if (!entities.length) {
4163
- return null;
4164
- }
4165
- await Promise.all(
4166
- entities.map((document) => {
4167
- return strapi2.entityValidator.validateEntityCreation(
4168
- strapi2.getModel(uid2),
4169
- document,
4170
- void 0,
4171
- // @ts-expect-error - FIXME: entity here is unnecessary
4172
- document
4173
- );
4174
- })
4175
- );
4176
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4177
- const filters = { id: { $in: entitiesToPublish } };
4178
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4179
- const populate = await buildDeepPopulate(uid2);
4180
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4181
- where: filters,
4182
- data
4183
- });
4184
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4185
- where: filters,
4186
- populate
4345
+ async publishMany(uid2, documentIds, locale) {
4346
+ return strapi2.db.transaction(async () => {
4347
+ const results = await Promise.all(
4348
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4349
+ );
4350
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4351
+ return publishedEntitiesCount;
4187
4352
  });
4188
- await Promise.all(
4189
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4190
- );
4191
- return publishedEntitiesCount;
4192
4353
  },
4193
- async unpublishMany(documents, uid2) {
4194
- if (!documents.length) {
4195
- return null;
4196
- }
4197
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4198
- const filters = { id: { $in: entitiesToUnpublish } };
4199
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4200
- const populate = await buildDeepPopulate(uid2);
4201
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4202
- where: filters,
4203
- data
4204
- });
4205
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4206
- where: filters,
4207
- populate
4354
+ async unpublishMany(documentIds, uid2, opts = {}) {
4355
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4356
+ return Promise.all(
4357
+ documentIds.map(
4358
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4359
+ )
4360
+ );
4208
4361
  });
4209
- await Promise.all(
4210
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4211
- );
4212
- return unpublishedEntitiesCount;
4362
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4363
+ return { count: unpublishedEntitiesCount };
4213
4364
  },
4214
4365
  async unpublish(id, uid2, opts = {}) {
4215
4366
  const populate = await buildDeepPopulate(uid2);
@@ -4234,16 +4385,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4234
4385
  }
4235
4386
  return sumDraftCounts(document, uid2);
4236
4387
  },
4237
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4388
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4238
4389
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4239
4390
  if (!hasRelations) {
4240
4391
  return 0;
4241
4392
  }
4393
+ let localeFilter = {};
4394
+ if (locale) {
4395
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4396
+ }
4242
4397
  const entities = await strapi2.db.query(uid2).findMany({
4243
4398
  populate,
4244
4399
  where: {
4245
- id: { $in: ids },
4246
- ...locale ? { locale } : {}
4400
+ documentId: { $in: documentIds },
4401
+ ...localeFilter
4247
4402
  }
4248
4403
  });
4249
4404
  const totalNumberDraftRelations = entities.reduce(