@strapi/content-manager 0.0.0-experimental.17b4116f461a49b8ce5386f7c8d79c511d40fb3b → 0.0.0-experimental.1fc4b627b49f713b07ed9f7f2b37741dcf8cf736

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 (214) 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-DjQBdcKF.mjs → ComponentConfigurationPage-CIjXcRAB.mjs} +4 -4
  7. package/dist/_chunks/{ComponentConfigurationPage-DjQBdcKF.mjs.map → ComponentConfigurationPage-CIjXcRAB.mjs.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage-2iOVVhqV.js → ComponentConfigurationPage-gsCd80MU.js} +4 -4
  9. package/dist/_chunks/{ComponentConfigurationPage-2iOVVhqV.js.map → ComponentConfigurationPage-gsCd80MU.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-BoBb-DLH.mjs → EditConfigurationPage-BglmD_BF.mjs} +4 -4
  15. package/dist/_chunks/{EditConfigurationPage-BoBb-DLH.mjs.map → EditConfigurationPage-BglmD_BF.mjs.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-B7dw5_cS.js → EditConfigurationPage-DHDQKBzw.js} +4 -4
  17. package/dist/_chunks/{EditConfigurationPage-B7dw5_cS.js.map → EditConfigurationPage-DHDQKBzw.js.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-KRG56aCq.js → EditViewPage-C4iTxUPU.js} +69 -50
  19. package/dist/_chunks/EditViewPage-C4iTxUPU.js.map +1 -0
  20. package/dist/_chunks/{EditViewPage-aUnqL-63.mjs → EditViewPage-CiwVPMaK.mjs} +70 -49
  21. package/dist/_chunks/EditViewPage-CiwVPMaK.mjs.map +1 -0
  22. package/dist/_chunks/{Field-kVFO4ZKB.mjs → Field-DIjL1b5d.mjs} +1046 -802
  23. package/dist/_chunks/Field-DIjL1b5d.mjs.map +1 -0
  24. package/dist/_chunks/{Field-kq1c2TF1.js → Field-DhXEK8y1.js} +1092 -849
  25. package/dist/_chunks/Field-DhXEK8y1.js.map +1 -0
  26. package/dist/_chunks/{Form-Jgh5hGTu.mjs → Form-CmNesrvR.mjs} +66 -45
  27. package/dist/_chunks/Form-CmNesrvR.mjs.map +1 -0
  28. package/dist/_chunks/{Form-CQ67ZifP.js → Form-CwmJ4sWe.js} +66 -46
  29. package/dist/_chunks/Form-CwmJ4sWe.js.map +1 -0
  30. package/dist/_chunks/{History-BLEnudTX.js → History-BLCCNgCt.js} +164 -54
  31. package/dist/_chunks/History-BLCCNgCt.js.map +1 -0
  32. package/dist/_chunks/{History-DKhZAPcK.mjs → History-D-99Wh30.mjs} +163 -52
  33. package/dist/_chunks/History-D-99Wh30.mjs.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-Zso_LUjn.js → ListConfigurationPage-DxWpeZrO.js} +68 -59
  35. package/dist/_chunks/ListConfigurationPage-DxWpeZrO.js.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-nrXcxNYi.mjs → ListConfigurationPage-JPWZz7Kg.mjs} +64 -54
  37. package/dist/_chunks/ListConfigurationPage-JPWZz7Kg.mjs.map +1 -0
  38. package/dist/_chunks/{ListViewPage-DsaOakWQ.js → ListViewPage-CIQekSFz.js} +143 -139
  39. package/dist/_chunks/ListViewPage-CIQekSFz.js.map +1 -0
  40. package/dist/_chunks/{ListViewPage-ChhYmA-L.mjs → ListViewPage-DSK3f0ST.mjs} +140 -136
  41. package/dist/_chunks/ListViewPage-DSK3f0ST.mjs.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-DPCuS9Y1.js → NoContentTypePage-C5cxKvC2.js} +3 -3
  43. package/dist/_chunks/NoContentTypePage-C5cxKvC2.js.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-BrdFcN33.mjs → NoContentTypePage-D99LU1YP.mjs} +3 -3
  45. package/dist/_chunks/NoContentTypePage-D99LU1YP.mjs.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-B9dqrtTy.mjs → NoPermissionsPage-DBrBw-0y.mjs} +2 -2
  47. package/dist/_chunks/{NoPermissionsPage-B9dqrtTy.mjs.map → NoPermissionsPage-DBrBw-0y.mjs.map} +1 -1
  48. package/dist/_chunks/{NoPermissionsPage-DdyOfdKb.js → NoPermissionsPage-Oy4tmUrW.js} +2 -2
  49. package/dist/_chunks/{NoPermissionsPage-DdyOfdKb.js.map → NoPermissionsPage-Oy4tmUrW.js.map} +1 -1
  50. package/dist/_chunks/{Relations-DjFiYd7-.mjs → Relations-BBmhcWFV.mjs} +132 -89
  51. package/dist/_chunks/Relations-BBmhcWFV.mjs.map +1 -0
  52. package/dist/_chunks/{Relations-CY8Isqdu.js → Relations-eG-9p_qS.js} +135 -93
  53. package/dist/_chunks/Relations-eG-9p_qS.js.map +1 -0
  54. package/dist/_chunks/{en-C-V1_90f.js → en-Bm0D0IWz.js} +23 -15
  55. package/dist/_chunks/{en-C-V1_90f.js.map → en-Bm0D0IWz.js.map} +1 -1
  56. package/dist/_chunks/{en-MBPul9Su.mjs → en-DKV44jRb.mjs} +23 -15
  57. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-DKV44jRb.mjs.map} +1 -1
  58. package/dist/_chunks/{index-DNa1J4HE.js → index-BIWDoFLK.js} +1877 -939
  59. package/dist/_chunks/index-BIWDoFLK.js.map +1 -0
  60. package/dist/_chunks/{index-CAc9yTnx.mjs → index-BrUzbQ30.mjs} +1902 -964
  61. package/dist/_chunks/index-BrUzbQ30.mjs.map +1 -0
  62. package/dist/_chunks/{layout-CXsHbc3E.mjs → layout-_5-cXs34.mjs} +45 -27
  63. package/dist/_chunks/layout-_5-cXs34.mjs.map +1 -0
  64. package/dist/_chunks/{layout-BqtLA6Lb.js → layout-lMc9i1-Z.js} +45 -29
  65. package/dist/_chunks/layout-lMc9i1-Z.js.map +1 -0
  66. package/dist/_chunks/{objects-gigeqt7s.js → objects-BcXOv6_9.js} +2 -4
  67. package/dist/_chunks/{objects-gigeqt7s.js.map → objects-BcXOv6_9.js.map} +1 -1
  68. package/dist/_chunks/{objects-mKMAmfec.mjs → objects-D6yBsdmx.mjs} +2 -4
  69. package/dist/_chunks/{objects-mKMAmfec.mjs.map → objects-D6yBsdmx.mjs.map} +1 -1
  70. package/dist/_chunks/{relations-BHY_KDJ_.js → relations-BRHithi8.js} +3 -7
  71. package/dist/_chunks/relations-BRHithi8.js.map +1 -0
  72. package/dist/_chunks/{relations-mMFEcZRq.mjs → relations-B_VLk-DD.mjs} +3 -7
  73. package/dist/_chunks/relations-B_VLk-DD.mjs.map +1 -0
  74. package/dist/_chunks/useDebounce-CtcjDB3L.js +28 -0
  75. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  76. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  77. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  78. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  79. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  80. package/dist/admin/index.js +3 -1
  81. package/dist/admin/index.js.map +1 -1
  82. package/dist/admin/index.mjs +9 -7
  83. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  84. package/dist/admin/src/content-manager.d.ts +3 -3
  85. package/dist/admin/src/exports.d.ts +2 -1
  86. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +1 -1
  87. package/dist/admin/src/history/index.d.ts +3 -0
  88. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  89. package/dist/admin/src/hooks/useDocument.d.ts +37 -9
  90. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  91. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  92. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  93. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  94. package/dist/admin/src/index.d.ts +1 -0
  95. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +11 -4
  96. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  97. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  98. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  99. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  100. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  101. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +30 -18
  102. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  103. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  104. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  105. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  106. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +16 -53
  107. package/dist/admin/src/pages/EditView/components/Header.d.ts +11 -11
  108. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  109. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  110. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  111. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  112. package/dist/admin/src/preview/constants.d.ts +1 -0
  113. package/dist/admin/src/preview/index.d.ts +4 -0
  114. package/dist/admin/src/services/api.d.ts +2 -3
  115. package/dist/admin/src/services/components.d.ts +2 -2
  116. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  117. package/dist/admin/src/services/documents.d.ts +31 -17
  118. package/dist/admin/src/services/init.d.ts +2 -2
  119. package/dist/admin/src/services/relations.d.ts +3 -3
  120. package/dist/admin/src/services/uid.d.ts +3 -3
  121. package/dist/admin/src/utils/api.d.ts +4 -18
  122. package/dist/admin/src/utils/validation.d.ts +5 -7
  123. package/dist/server/index.js +571 -312
  124. package/dist/server/index.js.map +1 -1
  125. package/dist/server/index.mjs +573 -314
  126. package/dist/server/index.mjs.map +1 -1
  127. package/dist/server/src/bootstrap.d.ts.map +1 -1
  128. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  129. package/dist/server/src/controllers/index.d.ts.map +1 -1
  130. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  131. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  132. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  133. package/dist/server/src/controllers/utils/metadata.d.ts +22 -0
  134. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  135. package/dist/server/src/controllers/validation/dimensions.d.ts +11 -0
  136. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  137. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  138. package/dist/server/src/history/services/history.d.ts.map +1 -1
  139. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  140. package/dist/server/src/history/services/utils.d.ts +2 -1
  141. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  142. package/dist/server/src/index.d.ts +20 -36
  143. package/dist/server/src/index.d.ts.map +1 -1
  144. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  145. package/dist/server/src/preview/constants.d.ts +2 -0
  146. package/dist/server/src/preview/constants.d.ts.map +1 -0
  147. package/dist/server/src/preview/controllers/index.d.ts +2 -0
  148. package/dist/server/src/preview/controllers/index.d.ts.map +1 -0
  149. package/dist/server/src/preview/controllers/preview.d.ts +9 -0
  150. package/dist/server/src/preview/controllers/preview.d.ts.map +1 -0
  151. package/dist/server/src/preview/index.d.ts +4 -0
  152. package/dist/server/src/preview/index.d.ts.map +1 -0
  153. package/dist/server/src/preview/routes/index.d.ts +8 -0
  154. package/dist/server/src/preview/routes/index.d.ts.map +1 -0
  155. package/dist/server/src/preview/routes/preview.d.ts +4 -0
  156. package/dist/server/src/preview/routes/preview.d.ts.map +1 -0
  157. package/dist/server/src/preview/services/index.d.ts +4 -0
  158. package/dist/server/src/preview/services/index.d.ts.map +1 -0
  159. package/dist/server/src/preview/services/preview.d.ts +6 -0
  160. package/dist/server/src/preview/services/preview.d.ts.map +1 -0
  161. package/dist/server/src/preview/utils.d.ts +7 -0
  162. package/dist/server/src/preview/utils.d.ts.map +1 -0
  163. package/dist/server/src/routes/index.d.ts.map +1 -1
  164. package/dist/server/src/services/document-manager.d.ts +11 -6
  165. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  166. package/dist/server/src/services/document-metadata.d.ts +14 -35
  167. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  168. package/dist/server/src/services/index.d.ts +20 -36
  169. package/dist/server/src/services/index.d.ts.map +1 -1
  170. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  171. package/dist/server/src/services/utils/configuration/index.d.ts +2 -2
  172. package/dist/server/src/services/utils/configuration/layouts.d.ts +2 -2
  173. package/dist/server/src/services/utils/populate.d.ts +8 -1
  174. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  175. package/dist/server/src/utils/index.d.ts +2 -0
  176. package/dist/server/src/utils/index.d.ts.map +1 -1
  177. package/dist/shared/contracts/collection-types.d.ts +17 -7
  178. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  179. package/dist/shared/contracts/relations.d.ts +2 -2
  180. package/dist/shared/contracts/relations.d.ts.map +1 -1
  181. package/package.json +17 -18
  182. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  183. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  184. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  185. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  186. package/dist/_chunks/EditViewPage-KRG56aCq.js.map +0 -1
  187. package/dist/_chunks/EditViewPage-aUnqL-63.mjs.map +0 -1
  188. package/dist/_chunks/Field-kVFO4ZKB.mjs.map +0 -1
  189. package/dist/_chunks/Field-kq1c2TF1.js.map +0 -1
  190. package/dist/_chunks/Form-CQ67ZifP.js.map +0 -1
  191. package/dist/_chunks/Form-Jgh5hGTu.mjs.map +0 -1
  192. package/dist/_chunks/History-BLEnudTX.js.map +0 -1
  193. package/dist/_chunks/History-DKhZAPcK.mjs.map +0 -1
  194. package/dist/_chunks/ListConfigurationPage-Zso_LUjn.js.map +0 -1
  195. package/dist/_chunks/ListConfigurationPage-nrXcxNYi.mjs.map +0 -1
  196. package/dist/_chunks/ListViewPage-ChhYmA-L.mjs.map +0 -1
  197. package/dist/_chunks/ListViewPage-DsaOakWQ.js.map +0 -1
  198. package/dist/_chunks/NoContentTypePage-BrdFcN33.mjs.map +0 -1
  199. package/dist/_chunks/NoContentTypePage-DPCuS9Y1.js.map +0 -1
  200. package/dist/_chunks/Relations-CY8Isqdu.js.map +0 -1
  201. package/dist/_chunks/Relations-DjFiYd7-.mjs.map +0 -1
  202. package/dist/_chunks/index-CAc9yTnx.mjs.map +0 -1
  203. package/dist/_chunks/index-DNa1J4HE.js.map +0 -1
  204. package/dist/_chunks/layout-BqtLA6Lb.js.map +0 -1
  205. package/dist/_chunks/layout-CXsHbc3E.mjs.map +0 -1
  206. package/dist/_chunks/relations-BHY_KDJ_.js.map +0 -1
  207. package/dist/_chunks/relations-mMFEcZRq.mjs.map +0 -1
  208. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  209. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  210. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  211. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  212. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  213. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
  214. package/strapi-server.js +0 -3
@@ -121,7 +121,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
121
121
  }
122
122
  };
123
123
  };
124
- const controllers$1 = {
124
+ const controllers$2 = {
125
125
  "history-version": createHistoryVersionController
126
126
  /**
127
127
  * Casting is needed because the types aren't aware that Strapi supports
@@ -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,
@@ -379,7 +393,12 @@ const createHistoryService = ({ strapi: strapi2 }) => {
379
393
  if (userToPopulate == null) {
380
394
  return null;
381
395
  }
382
- return strapi2.query("admin::user").findOne({ where: { id: userToPopulate.id } });
396
+ return strapi2.query("admin::user").findOne({
397
+ where: {
398
+ ...userToPopulate.id ? { id: userToPopulate.id } : {},
399
+ ...userToPopulate.documentId ? { documentId: userToPopulate.documentId } : {}
400
+ }
401
+ });
383
402
  })
384
403
  );
385
404
  return {
@@ -490,13 +509,47 @@ const createHistoryService = ({ strapi: strapi2 }) => {
490
509
  }
491
510
  };
492
511
  };
512
+ const shouldCreateHistoryVersion = (context) => {
513
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
514
+ return false;
515
+ }
516
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
517
+ return false;
518
+ }
519
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
520
+ return false;
521
+ }
522
+ if (!context.contentType.uid.startsWith("api::")) {
523
+ return false;
524
+ }
525
+ return true;
526
+ };
527
+ const getSchemas = (uid2) => {
528
+ const attributesSchema = strapi.getModel(uid2).attributes;
529
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
530
+ (currentComponentSchemas, key) => {
531
+ const fieldSchema = attributesSchema[key];
532
+ if (fieldSchema.type === "component") {
533
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
534
+ return {
535
+ ...currentComponentSchemas,
536
+ [fieldSchema.component]: componentSchema
537
+ };
538
+ }
539
+ return currentComponentSchemas;
540
+ },
541
+ {}
542
+ );
543
+ return {
544
+ schema: fp.omit(FIELDS_TO_IGNORE, attributesSchema),
545
+ componentsSchemas
546
+ };
547
+ };
493
548
  const createLifecyclesService = ({ strapi: strapi2 }) => {
494
549
  const state = {
495
550
  deleteExpiredJob: null,
496
551
  isInitialized: false
497
552
  };
498
- const query = strapi2.db.query(HISTORY_VERSION_UID);
499
- const historyService = getService(strapi2, "history");
500
553
  const serviceUtils = createServiceUtils({ strapi: strapi2 });
501
554
  return {
502
555
  async bootstrap() {
@@ -504,60 +557,53 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
504
557
  return;
505
558
  }
506
559
  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
560
  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 };
561
+ if (!shouldCreateHistoryVersion(context)) {
562
+ return result;
563
+ }
564
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
519
565
  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)
566
+ const locales = fp.castArray(context.params?.locale || defaultLocale);
567
+ if (!locales.length) {
568
+ return result;
569
+ }
570
+ const uid2 = context.contentType.uid;
571
+ const schemas = getSchemas(uid2);
572
+ const model = strapi2.getModel(uid2);
573
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
574
+ const localeEntries = await strapi2.db.query(uid2).findMany({
575
+ where: {
576
+ documentId,
577
+ ...isLocalizedContentType ? { locale: { $in: locales } } : {},
578
+ ...strapiUtils.contentTypes.hasDraftAndPublish(strapi2.contentTypes[uid2]) ? { publishedAt: null } : {}
579
+ },
580
+ populate: serviceUtils.getDeepPopulate(
581
+ uid2,
582
+ true
583
+ /* use database syntax */
584
+ )
525
585
  });
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
586
  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
- });
587
+ onCommit(async () => {
588
+ for (const entry of localeEntries) {
589
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
590
+ await getService(strapi2, "history").createVersion({
591
+ contentType: uid2,
592
+ data: fp.omit(FIELDS_TO_IGNORE, entry),
593
+ relatedDocumentId: documentId,
594
+ locale: entry.locale,
595
+ status,
596
+ ...schemas
597
+ });
598
+ }
552
599
  });
553
600
  });
554
601
  return result;
555
602
  });
556
- const retentionDays = serviceUtils.getRetentionDays();
557
603
  state.deleteExpiredJob = nodeSchedule.scheduleJob("0 0 * * *", () => {
558
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
604
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
559
605
  const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
560
- query.deleteMany({
606
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
561
607
  where: {
562
608
  created_at: {
563
609
  $lt: expirationDate.toISOString()
@@ -574,17 +620,17 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
574
620
  }
575
621
  };
576
622
  };
577
- const services$1 = {
623
+ const services$2 = {
578
624
  history: createHistoryService,
579
625
  lifecycles: createLifecyclesService
580
626
  };
581
- const info = { pluginName: "content-manager", type: "admin" };
627
+ const info$1 = { pluginName: "content-manager", type: "admin" };
582
628
  const historyVersionRouter = {
583
629
  type: "admin",
584
630
  routes: [
585
631
  {
586
632
  method: "GET",
587
- info,
633
+ info: info$1,
588
634
  path: "/history-versions",
589
635
  handler: "history-version.findMany",
590
636
  config: {
@@ -593,7 +639,7 @@ const historyVersionRouter = {
593
639
  },
594
640
  {
595
641
  method: "PUT",
596
- info,
642
+ info: info$1,
597
643
  path: "/history-versions/:versionId/restore",
598
644
  handler: "history-version.restoreVersion",
599
645
  config: {
@@ -602,7 +648,7 @@ const historyVersionRouter = {
602
648
  }
603
649
  ]
604
650
  };
605
- const routes$1 = {
651
+ const routes$2 = {
606
652
  "history-version": historyVersionRouter
607
653
  };
608
654
  const historyVersion = {
@@ -649,7 +695,7 @@ const historyVersion = {
649
695
  }
650
696
  }
651
697
  };
652
- const getFeature = () => {
698
+ const getFeature$1 = () => {
653
699
  if (strapi.ee.features.isEnabled("cms-content-history")) {
654
700
  return {
655
701
  register({ strapi: strapi2 }) {
@@ -661,9 +707,9 @@ const getFeature = () => {
661
707
  destroy({ strapi: strapi2 }) {
662
708
  getService(strapi2, "lifecycles").destroy();
663
709
  },
664
- controllers: controllers$1,
665
- services: services$1,
666
- routes: routes$1
710
+ controllers: controllers$2,
711
+ services: services$2,
712
+ routes: routes$2
667
713
  };
668
714
  }
669
715
  return {
@@ -672,7 +718,7 @@ const getFeature = () => {
672
718
  }
673
719
  };
674
720
  };
675
- const history = getFeature();
721
+ const history = getFeature$1();
676
722
  const register = async ({ strapi: strapi2 }) => {
677
723
  await history.register?.({ strapi: strapi2 });
678
724
  };
@@ -680,6 +726,62 @@ const ALLOWED_WEBHOOK_EVENTS = {
680
726
  ENTRY_PUBLISH: "entry.publish",
681
727
  ENTRY_UNPUBLISH: "entry.unpublish"
682
728
  };
729
+ const FEATURE_ID = "preview";
730
+ const info = { pluginName: "content-manager", type: "admin" };
731
+ const previewRouter = {
732
+ type: "admin",
733
+ routes: [
734
+ {
735
+ method: "GET",
736
+ info,
737
+ path: "/preview/url/:contentType",
738
+ handler: "preview.getPreviewURL",
739
+ config: {
740
+ policies: ["admin::isAuthenticatedAdmin"]
741
+ }
742
+ }
743
+ ]
744
+ };
745
+ const routes$1 = {
746
+ preview: previewRouter
747
+ };
748
+ const createPreviewController = () => {
749
+ return {
750
+ async getPreviewURL(ctx) {
751
+ ctx.request;
752
+ return {
753
+ data: { url: "" }
754
+ };
755
+ }
756
+ };
757
+ };
758
+ const controllers$1 = {
759
+ preview: createPreviewController
760
+ /**
761
+ * Casting is needed because the types aren't aware that Strapi supports
762
+ * passing a controller factory as the value, instead of a controller object directly
763
+ */
764
+ };
765
+ const createPreviewService = () => {
766
+ };
767
+ const services$1 = {
768
+ preview: createPreviewService
769
+ };
770
+ const getFeature = () => {
771
+ if (!strapi.features.future.isEnabled(FEATURE_ID)) {
772
+ return {};
773
+ }
774
+ return {
775
+ bootstrap() {
776
+ console.log("Bootstrapping preview server");
777
+ strapi.config.get("admin.preview");
778
+ },
779
+ routes: routes$1,
780
+ controllers: controllers$1,
781
+ services: services$1
782
+ };
783
+ };
784
+ const preview = getFeature();
683
785
  const bootstrap = async () => {
684
786
  Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
685
787
  strapi.get("webhookStore").addAllowedEvent(key, value);
@@ -689,6 +791,7 @@ const bootstrap = async () => {
689
791
  await getService$1("content-types").syncConfigurations();
690
792
  await getService$1("permission").registerPermissions();
691
793
  await history.bootstrap?.({ strapi });
794
+ await preview.bootstrap?.({ strapi });
692
795
  };
693
796
  const destroy = async ({ strapi: strapi2 }) => {
694
797
  await history.destroy?.({ strapi: strapi2 });
@@ -1178,7 +1281,8 @@ const admin = {
1178
1281
  };
1179
1282
  const routes = {
1180
1283
  admin,
1181
- ...history.routes ? history.routes : {}
1284
+ ...history.routes ? history.routes : {},
1285
+ ...preview.routes ? preview.routes : {}
1182
1286
  };
1183
1287
  const hasPermissionsSchema = strapiUtils.yup.object({
1184
1288
  actions: strapiUtils.yup.array().of(strapiUtils.yup.string()),
@@ -1189,6 +1293,11 @@ const { createPolicy } = strapiUtils.policy;
1189
1293
  const hasPermissions = createPolicy({
1190
1294
  name: "plugin::content-manager.hasPermissions",
1191
1295
  validator: validateHasPermissionsInput,
1296
+ /**
1297
+ * NOTE: Action aliases are currently not checked at this level (policy).
1298
+ * This is currently the intended behavior to avoid changing the behavior of API related permissions.
1299
+ * If you want to add support for it, please create a dedicated RFC with a list of potential side effect this could have.
1300
+ */
1192
1301
  handler(ctx, config = {}) {
1193
1302
  const { actions = [], hasAtLeastOne = false } = config;
1194
1303
  const { userAbility } = ctx.state;
@@ -1478,7 +1587,7 @@ const { PaginationError, ValidationError } = strapiUtils.errors;
1478
1587
  const TYPES = ["singleType", "collectionType"];
1479
1588
  const kindSchema = strapiUtils.yup.string().oneOf(TYPES).nullable();
1480
1589
  const bulkActionInputSchema = strapiUtils.yup.object({
1481
- ids: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1590
+ documentIds: strapiUtils.yup.array().of(strapiUtils.yup.strapiID()).min(1).required()
1482
1591
  }).required();
1483
1592
  const generateUIDInputSchema = strapiUtils.yup.object({
1484
1593
  contentTypeUID: strapiUtils.yup.string().required(),
@@ -1577,15 +1686,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1577
1686
  }
1578
1687
  }, body);
1579
1688
  };
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}`);
1689
+ const singleLocaleSchema = strapiUtils.yup.string().nullable();
1690
+ const multipleLocaleSchema = strapiUtils.yup.lazy(
1691
+ (value) => Array.isArray(value) ? strapiUtils.yup.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1692
+ );
1693
+ const statusSchema = strapiUtils.yup.mixed().oneOf(["draft", "published"], "Invalid status");
1694
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1695
+ const { allowMultipleLocales } = opts;
1696
+ const { locale, status: providedStatus, ...rest } = request || {};
1697
+ const defaultStatus = strapiUtils.contentTypes.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1698
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1699
+ const schema = strapiUtils.yup.object().shape({
1700
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1701
+ status: statusSchema
1702
+ });
1703
+ try {
1704
+ await strapiUtils.validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1705
+ return { locale, status, ...rest };
1706
+ } catch (error) {
1707
+ throw new strapiUtils.errors.ValidationError(`Validation error: ${error.message}`);
1587
1708
  }
1588
- return { locale, status, ...rest };
1709
+ };
1710
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1711
+ const documentMetadata2 = getService$1("document-metadata");
1712
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1713
+ let {
1714
+ meta: { availableLocales, availableStatus }
1715
+ } = serviceOutput;
1716
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1717
+ availableLocales = await strapiUtils.async.map(
1718
+ availableLocales,
1719
+ async (localeDocument) => metadataSanitizer(localeDocument)
1720
+ );
1721
+ availableStatus = await strapiUtils.async.map(
1722
+ availableStatus,
1723
+ async (statusDocument) => metadataSanitizer(statusDocument)
1724
+ );
1725
+ return {
1726
+ ...serviceOutput,
1727
+ meta: {
1728
+ availableLocales,
1729
+ availableStatus
1730
+ }
1731
+ };
1589
1732
  };
1590
1733
  const createDocument = async (ctx, opts) => {
1591
1734
  const { userAbility, user } = ctx.state;
@@ -1600,7 +1743,7 @@ const createDocument = async (ctx, opts) => {
1600
1743
  const setCreator = strapiUtils.setCreatorFields({ user });
1601
1744
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1602
1745
  const sanitizedBody = await sanitizeFn(body);
1603
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1746
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1604
1747
  return documentManager2.create(model, {
1605
1748
  data: sanitizedBody,
1606
1749
  locale,
@@ -1619,7 +1762,7 @@ const updateDocument = async (ctx, opts) => {
1619
1762
  }
1620
1763
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1621
1764
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1622
- const { locale } = getDocumentLocaleAndStatus(body);
1765
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1623
1766
  const [documentVersion, documentExists] = await Promise.all([
1624
1767
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1625
1768
  documentManager2.exists(model, id)
@@ -1635,7 +1778,7 @@ const updateDocument = async (ctx, opts) => {
1635
1778
  throw new strapiUtils.errors.ForbiddenError();
1636
1779
  }
1637
1780
  const pickPermittedFields = documentVersion ? permissionChecker2.sanitizeUpdateInput(documentVersion) : permissionChecker2.sanitizeCreateInput;
1638
- const setCreator = strapiUtils.setCreatorFields({ user, isEdition: true });
1781
+ const setCreator = documentVersion ? strapiUtils.setCreatorFields({ user, isEdition: true }) : strapiUtils.setCreatorFields({ user });
1639
1782
  const sanitizeFn = strapiUtils.async.pipe(pickPermittedFields, setCreator);
1640
1783
  const sanitizedBody = await sanitizeFn(body);
1641
1784
  return documentManager2.update(documentVersion?.documentId || id, model, {
@@ -1657,7 +1800,7 @@ const collectionTypes = {
1657
1800
  }
1658
1801
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1659
1802
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1660
- const { locale, status } = getDocumentLocaleAndStatus(query);
1803
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1661
1804
  const { results: documents, pagination } = await documentManager2.findPage(
1662
1805
  { ...permissionQuery, populate, locale, status },
1663
1806
  model
@@ -1686,14 +1829,13 @@ const collectionTypes = {
1686
1829
  const { userAbility } = ctx.state;
1687
1830
  const { model, id } = ctx.params;
1688
1831
  const documentManager2 = getService$1("document-manager");
1689
- const documentMetadata2 = getService$1("document-metadata");
1690
1832
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1691
1833
  if (permissionChecker2.cannot.read()) {
1692
1834
  return ctx.forbidden();
1693
1835
  }
1694
1836
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1695
1837
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1696
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1838
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1697
1839
  const version = await documentManager2.findOne(id, model, {
1698
1840
  populate,
1699
1841
  locale,
@@ -1704,9 +1846,11 @@ const collectionTypes = {
1704
1846
  if (!exists) {
1705
1847
  return ctx.notFound();
1706
1848
  }
1707
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1849
+ const { meta } = await formatDocumentWithMetadata(
1850
+ permissionChecker2,
1708
1851
  model,
1709
- { id, locale, publishedAt: null },
1852
+ // @ts-expect-error TODO: fix
1853
+ { documentId: id, locale, publishedAt: null },
1710
1854
  { availableLocales: true, availableStatus: false }
1711
1855
  );
1712
1856
  ctx.body = { data: {}, meta };
@@ -1716,12 +1860,11 @@ const collectionTypes = {
1716
1860
  return ctx.forbidden();
1717
1861
  }
1718
1862
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1719
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1863
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1720
1864
  },
1721
1865
  async create(ctx) {
1722
1866
  const { userAbility } = ctx.state;
1723
1867
  const { model } = ctx.params;
1724
- const documentMetadata2 = getService$1("document-metadata");
1725
1868
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1726
1869
  const [totalEntries, document] = await Promise.all([
1727
1870
  strapi.db.query(model).count(),
@@ -1729,7 +1872,7 @@ const collectionTypes = {
1729
1872
  ]);
1730
1873
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1731
1874
  ctx.status = 201;
1732
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1875
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1733
1876
  // Empty metadata as it's not relevant for a new document
1734
1877
  availableLocales: false,
1735
1878
  availableStatus: false
@@ -1743,25 +1886,23 @@ const collectionTypes = {
1743
1886
  async update(ctx) {
1744
1887
  const { userAbility } = ctx.state;
1745
1888
  const { model } = ctx.params;
1746
- const documentMetadata2 = getService$1("document-metadata");
1747
1889
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1748
1890
  const updatedVersion = await updateDocument(ctx);
1749
1891
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1750
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1892
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1751
1893
  },
1752
1894
  async clone(ctx) {
1753
1895
  const { userAbility, user } = ctx.state;
1754
1896
  const { model, sourceId: id } = ctx.params;
1755
1897
  const { body } = ctx.request;
1756
1898
  const documentManager2 = getService$1("document-manager");
1757
- const documentMetadata2 = getService$1("document-metadata");
1758
1899
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1759
1900
  if (permissionChecker2.cannot.create()) {
1760
1901
  return ctx.forbidden();
1761
1902
  }
1762
1903
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1763
1904
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1764
- const { locale } = getDocumentLocaleAndStatus(body);
1905
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1765
1906
  const document = await documentManager2.findOne(id, model, {
1766
1907
  populate,
1767
1908
  locale,
@@ -1777,7 +1918,7 @@ const collectionTypes = {
1777
1918
  const sanitizedBody = await sanitizeFn(body);
1778
1919
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1779
1920
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1921
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1781
1922
  // Empty metadata as it's not relevant for a new document
1782
1923
  availableLocales: false,
1783
1924
  availableStatus: false
@@ -1806,7 +1947,7 @@ const collectionTypes = {
1806
1947
  }
1807
1948
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1808
1949
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1809
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1950
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1810
1951
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1811
1952
  if (documentLocales.length === 0) {
1812
1953
  return ctx.notFound();
@@ -1828,7 +1969,6 @@ const collectionTypes = {
1828
1969
  const { id, model } = ctx.params;
1829
1970
  const { body } = ctx.request;
1830
1971
  const documentManager2 = getService$1("document-manager");
1831
- const documentMetadata2 = getService$1("document-metadata");
1832
1972
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1833
1973
  if (permissionChecker2.cannot.publish()) {
1834
1974
  return ctx.forbidden();
@@ -1836,25 +1976,52 @@ const collectionTypes = {
1836
1976
  const publishedDocument = await strapi.db.transaction(async () => {
1837
1977
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1838
1978
  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 });
1979
+ let document;
1980
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1981
+ const isCreate = fp.isNil(id);
1982
+ if (isCreate) {
1983
+ if (permissionChecker2.cannot.create()) {
1984
+ throw new strapiUtils.errors.ForbiddenError();
1985
+ }
1986
+ document = await createDocument(ctx, { populate });
1987
+ }
1988
+ const isUpdate = !isCreate;
1989
+ if (isUpdate) {
1990
+ const documentExists = documentManager2.exists(model, id);
1991
+ if (!documentExists) {
1992
+ throw new strapiUtils.errors.NotFoundError("Document not found");
1993
+ }
1994
+ document = await documentManager2.findOne(id, model, { populate, locale });
1995
+ if (!document) {
1996
+ if (permissionChecker2.cannot.create({ locale }) || permissionChecker2.cannot.publish({ locale })) {
1997
+ throw new strapiUtils.errors.ForbiddenError();
1998
+ }
1999
+ document = await updateDocument(ctx);
2000
+ } else if (permissionChecker2.can.update(document)) {
2001
+ await updateDocument(ctx);
2002
+ }
2003
+ }
1840
2004
  if (permissionChecker2.cannot.publish(document)) {
1841
2005
  throw new strapiUtils.errors.ForbiddenError();
1842
2006
  }
1843
- const { locale } = getDocumentLocaleAndStatus(body);
1844
- return documentManager2.publish(document.documentId, model, {
2007
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1845
2008
  locale
1846
2009
  // TODO: Allow setting creator fields on publish
1847
2010
  // data: setCreatorFields({ user, isEdition: true })({}),
1848
2011
  });
2012
+ if (!publishResult || publishResult.length === 0) {
2013
+ throw new strapiUtils.errors.NotFoundError("Document not found or already published.");
2014
+ }
2015
+ return publishResult[0];
1849
2016
  });
1850
2017
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1851
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2018
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1852
2019
  },
1853
2020
  async bulkPublish(ctx) {
1854
2021
  const { userAbility } = ctx.state;
1855
2022
  const { model } = ctx.params;
1856
2023
  const { body } = ctx.request;
1857
- const { ids } = body;
2024
+ const { documentIds } = body;
1858
2025
  await validateBulkActionInput(body);
1859
2026
  const documentManager2 = getService$1("document-manager");
1860
2027
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1863,8 +2030,13 @@ const collectionTypes = {
1863
2030
  }
1864
2031
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1865
2032
  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);
2033
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
2034
+ allowMultipleLocales: true
2035
+ });
2036
+ const entityPromises = documentIds.map(
2037
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
2038
+ );
2039
+ const entities = (await Promise.all(entityPromises)).flat();
1868
2040
  for (const entity of entities) {
1869
2041
  if (!entity) {
1870
2042
  return ctx.notFound();
@@ -1873,24 +2045,27 @@ const collectionTypes = {
1873
2045
  return ctx.forbidden();
1874
2046
  }
1875
2047
  }
1876
- const { count } = await documentManager2.publishMany(entities, model);
2048
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1877
2049
  ctx.body = { count };
1878
2050
  },
1879
2051
  async bulkUnpublish(ctx) {
1880
2052
  const { userAbility } = ctx.state;
1881
2053
  const { model } = ctx.params;
1882
2054
  const { body } = ctx.request;
1883
- const { ids } = body;
2055
+ const { documentIds } = body;
1884
2056
  await validateBulkActionInput(body);
1885
2057
  const documentManager2 = getService$1("document-manager");
1886
2058
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1887
2059
  if (permissionChecker2.cannot.unpublish()) {
1888
2060
  return ctx.forbidden();
1889
2061
  }
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);
2062
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
2063
+ allowMultipleLocales: true
2064
+ });
2065
+ const entityPromises = documentIds.map(
2066
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
2067
+ );
2068
+ const entities = (await Promise.all(entityPromises)).flat();
1894
2069
  for (const entity of entities) {
1895
2070
  if (!entity) {
1896
2071
  return ctx.notFound();
@@ -1899,7 +2074,8 @@ const collectionTypes = {
1899
2074
  return ctx.forbidden();
1900
2075
  }
1901
2076
  }
1902
- const { count } = await documentManager2.unpublishMany(entities, model);
2077
+ const entitiesIds = entities.map((document) => document.documentId);
2078
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1903
2079
  ctx.body = { count };
1904
2080
  },
1905
2081
  async unpublish(ctx) {
@@ -1909,7 +2085,6 @@ const collectionTypes = {
1909
2085
  body: { discardDraft, ...body }
1910
2086
  } = ctx.request;
1911
2087
  const documentManager2 = getService$1("document-manager");
1912
- const documentMetadata2 = getService$1("document-metadata");
1913
2088
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1914
2089
  if (permissionChecker2.cannot.unpublish()) {
1915
2090
  return ctx.forbidden();
@@ -1919,7 +2094,7 @@ const collectionTypes = {
1919
2094
  }
1920
2095
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1921
2096
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1922
- const { locale } = getDocumentLocaleAndStatus(body);
2097
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1923
2098
  const document = await documentManager2.findOne(id, model, {
1924
2099
  populate,
1925
2100
  locale,
@@ -1941,7 +2116,7 @@ const collectionTypes = {
1941
2116
  ctx.body = await strapiUtils.async.pipe(
1942
2117
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1943
2118
  permissionChecker2.sanitizeOutput,
1944
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2119
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1945
2120
  )(document);
1946
2121
  });
1947
2122
  },
@@ -1950,14 +2125,13 @@ const collectionTypes = {
1950
2125
  const { id, model } = ctx.params;
1951
2126
  const { body } = ctx.request;
1952
2127
  const documentManager2 = getService$1("document-manager");
1953
- const documentMetadata2 = getService$1("document-metadata");
1954
2128
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1955
2129
  if (permissionChecker2.cannot.discard()) {
1956
2130
  return ctx.forbidden();
1957
2131
  }
1958
2132
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1959
2133
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1960
- const { locale } = getDocumentLocaleAndStatus(body);
2134
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1961
2135
  const document = await documentManager2.findOne(id, model, {
1962
2136
  populate,
1963
2137
  locale,
@@ -1972,14 +2146,14 @@ const collectionTypes = {
1972
2146
  ctx.body = await strapiUtils.async.pipe(
1973
2147
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1974
2148
  permissionChecker2.sanitizeOutput,
1975
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2149
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1976
2150
  )(document);
1977
2151
  },
1978
2152
  async bulkDelete(ctx) {
1979
2153
  const { userAbility } = ctx.state;
1980
2154
  const { model } = ctx.params;
1981
2155
  const { query, body } = ctx.request;
1982
- const { ids } = body;
2156
+ const { documentIds } = body;
1983
2157
  await validateBulkActionInput(body);
1984
2158
  const documentManager2 = getService$1("document-manager");
1985
2159
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1987,14 +2161,22 @@ const collectionTypes = {
1987
2161
  return ctx.forbidden();
1988
2162
  }
1989
2163
  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 || [])
2164
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2165
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2166
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2167
+ populate,
2168
+ locale
2169
+ });
2170
+ if (documentLocales.length === 0) {
2171
+ return ctx.notFound();
2172
+ }
2173
+ for (const document of documentLocales) {
2174
+ if (permissionChecker2.cannot.delete(document)) {
2175
+ return ctx.forbidden();
1995
2176
  }
1996
- };
1997
- const { count } = await documentManager2.deleteMany(params, model);
2177
+ }
2178
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2179
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1998
2180
  ctx.body = { count };
1999
2181
  },
2000
2182
  async countDraftRelations(ctx) {
@@ -2007,7 +2189,7 @@ const collectionTypes = {
2007
2189
  }
2008
2190
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
2009
2191
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2010
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2192
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
2011
2193
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
2012
2194
  if (!entity) {
2013
2195
  return ctx.notFound();
@@ -2022,7 +2204,7 @@ const collectionTypes = {
2022
2204
  },
2023
2205
  async countManyEntriesDraftRelations(ctx) {
2024
2206
  const { userAbility } = ctx.state;
2025
- const ids = ctx.request.query.ids;
2207
+ const ids = ctx.request.query.documentIds;
2026
2208
  const locale = ctx.request.query.locale;
2027
2209
  const { model } = ctx.params;
2028
2210
  const documentManager2 = getService$1("document-manager");
@@ -2030,16 +2212,16 @@ const collectionTypes = {
2030
2212
  if (permissionChecker2.cannot.read()) {
2031
2213
  return ctx.forbidden();
2032
2214
  }
2033
- const entities = await documentManager2.findMany(
2215
+ const documents = await documentManager2.findMany(
2034
2216
  {
2035
2217
  filters: {
2036
- id: ids
2218
+ documentId: ids
2037
2219
  },
2038
2220
  locale
2039
2221
  },
2040
2222
  model
2041
2223
  );
2042
- if (!entities) {
2224
+ if (!documents) {
2043
2225
  return ctx.notFound();
2044
2226
  }
2045
2227
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2230,32 +2412,37 @@ const sanitizeMainField = (model, mainField, userAbility) => {
2230
2412
  userAbility,
2231
2413
  model: model.uid
2232
2414
  });
2233
- if (!isListable(model, mainField)) {
2415
+ const isMainFieldListable = isListable(model, mainField);
2416
+ const canReadMainField = permissionChecker2.can.read(null, mainField);
2417
+ if (!isMainFieldListable || !canReadMainField) {
2234
2418
  return "id";
2235
2419
  }
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";
2420
+ if (model.uid === "plugin::users-permissions.role") {
2421
+ return "name";
2247
2422
  }
2248
2423
  return mainField;
2249
2424
  };
2250
- const addStatusToRelations = async (uid2, relations2) => {
2251
- if (!strapiUtils.contentTypes.hasDraftAndPublish(strapi.contentTypes[uid2])) {
2425
+ const addStatusToRelations = async (targetUid, relations2) => {
2426
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(strapi.getModel(targetUid))) {
2252
2427
  return relations2;
2253
2428
  }
2254
2429
  const documentMetadata2 = getService$1("document-metadata");
2255
- const documentsAvailableStatus = await documentMetadata2.getManyAvailableStatus(uid2, relations2);
2430
+ if (!relations2.length) {
2431
+ return relations2;
2432
+ }
2433
+ const firstRelation = relations2[0];
2434
+ const filters = {
2435
+ documentId: { $in: relations2.map((r) => r.documentId) },
2436
+ // NOTE: find the "opposite" status
2437
+ publishedAt: firstRelation.publishedAt !== null ? { $null: true } : { $notNull: true }
2438
+ };
2439
+ const availableStatus = await strapi.query(targetUid).findMany({
2440
+ select: ["id", "documentId", "locale", "updatedAt", "createdAt", "publishedAt"],
2441
+ filters
2442
+ });
2256
2443
  return relations2.map((relation) => {
2257
- const availableStatuses = documentsAvailableStatus.filter(
2258
- (availableDocument) => availableDocument.documentId === relation.documentId
2444
+ const availableStatuses = availableStatus.filter(
2445
+ (availableDocument) => availableDocument.documentId === relation.documentId && (relation.locale ? availableDocument.locale === relation.locale : true)
2259
2446
  );
2260
2447
  return {
2261
2448
  ...relation,
@@ -2276,11 +2463,8 @@ const validateLocale = (sourceUid, targetUid, locale) => {
2276
2463
  const isLocalized = strapi.plugin("i18n").service("content-types").isLocalizedContentType;
2277
2464
  const isSourceLocalized = isLocalized(sourceModel);
2278
2465
  const isTargetLocalized = isLocalized(targetModel);
2279
- let validatedLocale = locale;
2280
- if (!targetModel || !isTargetLocalized)
2281
- validatedLocale = void 0;
2282
2466
  return {
2283
- locale: validatedLocale,
2467
+ locale,
2284
2468
  isSourceLocalized,
2285
2469
  isTargetLocalized
2286
2470
  };
@@ -2383,7 +2567,7 @@ const relations = {
2383
2567
  attribute,
2384
2568
  fieldsToSelect,
2385
2569
  mainField,
2386
- source: { schema: sourceSchema },
2570
+ source: { schema: sourceSchema, isLocalized: isSourceLocalized },
2387
2571
  target: { schema: targetSchema, isLocalized: isTargetLocalized },
2388
2572
  sourceSchema,
2389
2573
  targetSchema,
@@ -2405,7 +2589,8 @@ const relations = {
2405
2589
  fieldsToSelect,
2406
2590
  mainField,
2407
2591
  source: {
2408
- schema: { uid: sourceUid, modelType: sourceModelType }
2592
+ schema: { uid: sourceUid, modelType: sourceModelType },
2593
+ isLocalized: isSourceLocalized
2409
2594
  },
2410
2595
  target: {
2411
2596
  schema: { uid: targetUid },
@@ -2443,12 +2628,16 @@ const relations = {
2443
2628
  } else {
2444
2629
  where.id = id;
2445
2630
  }
2446
- if (status) {
2447
- where[`${alias}.published_at`] = getPublishedAtClause(status, targetUid);
2631
+ const publishedAt = getPublishedAtClause(status, targetUid);
2632
+ if (!fp.isEmpty(publishedAt)) {
2633
+ where[`${alias}.published_at`] = publishedAt;
2448
2634
  }
2449
- if (filterByLocale) {
2635
+ if (isTargetLocalized && locale) {
2450
2636
  where[`${alias}.locale`] = locale;
2451
2637
  }
2638
+ if (isSourceLocalized && locale) {
2639
+ where.locale = locale;
2640
+ }
2452
2641
  if ((idsToInclude?.length ?? 0) !== 0) {
2453
2642
  where[`${alias}.id`].$notIn = idsToInclude;
2454
2643
  }
@@ -2466,7 +2655,8 @@ const relations = {
2466
2655
  id: { $notIn: fp.uniq(idsToOmit) }
2467
2656
  });
2468
2657
  }
2469
- const res = await strapi.db.query(targetUid).findPage(strapi.get("query-params").transform(targetUid, queryParams));
2658
+ const dbQuery = strapi.get("query-params").transform(targetUid, queryParams);
2659
+ const res = await strapi.db.query(targetUid).findPage(dbQuery);
2470
2660
  ctx.body = {
2471
2661
  ...res,
2472
2662
  results: await addStatusToRelations(targetUid, res.results)
@@ -2481,29 +2671,39 @@ const relations = {
2481
2671
  attribute,
2482
2672
  targetField,
2483
2673
  fieldsToSelect,
2484
- source: {
2485
- schema: { uid: sourceUid }
2486
- },
2487
- target: {
2488
- schema: { uid: targetUid }
2489
- }
2674
+ status,
2675
+ source: { schema: sourceSchema },
2676
+ target: { schema: targetSchema }
2490
2677
  } = await this.extractAndValidateRequestInfo(ctx, id);
2678
+ const { uid: sourceUid } = sourceSchema;
2679
+ const { uid: targetUid } = targetSchema;
2491
2680
  const permissionQuery = await getService$1("permission-checker").create({ userAbility, model: targetUid }).sanitizedQuery.read({ fields: fieldsToSelect });
2492
2681
  const dbQuery = strapi.db.query(sourceUid);
2493
2682
  const loadRelations = strapiUtils.relations.isAnyToMany(attribute) ? (...args) => dbQuery.loadPages(...args) : (...args) => dbQuery.load(...args).then((res2) => ({ results: res2 ? [res2] : [] }));
2683
+ const filters = {};
2684
+ if (sourceSchema?.options?.draftAndPublish) {
2685
+ if (targetSchema?.options?.draftAndPublish) {
2686
+ if (status === "published") {
2687
+ filters.publishedAt = { $notNull: true };
2688
+ } else {
2689
+ filters.publishedAt = { $null: true };
2690
+ }
2691
+ }
2692
+ } else if (targetSchema?.options?.draftAndPublish) {
2693
+ filters.publishedAt = { $null: true };
2694
+ }
2494
2695
  const res = await loadRelations({ id: entryId }, targetField, {
2495
- select: ["id", "documentId", "locale", "publishedAt"],
2696
+ select: ["id", "documentId", "locale", "publishedAt", "updatedAt"],
2496
2697
  ordering: "desc",
2497
2698
  page: ctx.request.query.page,
2498
- pageSize: ctx.request.query.pageSize
2699
+ pageSize: ctx.request.query.pageSize,
2700
+ filters
2499
2701
  });
2500
2702
  const loadedIds = res.results.map((item) => item.id);
2501
2703
  addFiltersClause(permissionQuery, { id: { $in: loadedIds } });
2502
2704
  const sanitizedRes = await loadRelations({ id: entryId }, targetField, {
2503
2705
  ...strapi.get("query-params").transform(targetUid, permissionQuery),
2504
- ordering: "desc",
2505
- page: ctx.request.query.page,
2506
- pageSize: ctx.request.query.pageSize
2706
+ ordering: "desc"
2507
2707
  });
2508
2708
  const relationsUnion = fp.uniqBy("id", fp.concat(sanitizedRes.results, res.results));
2509
2709
  ctx.body = {
@@ -2535,7 +2735,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2535
2735
  throw new strapiUtils.errors.ForbiddenError();
2536
2736
  }
2537
2737
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2538
- const { locale } = getDocumentLocaleAndStatus(body);
2738
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2539
2739
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2540
2740
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2541
2741
  // Find the first document to check if it exists
@@ -2572,12 +2772,11 @@ const singleTypes = {
2572
2772
  const { model } = ctx.params;
2573
2773
  const { query = {} } = ctx.request;
2574
2774
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2575
- const documentMetadata2 = getService$1("document-metadata");
2576
2775
  if (permissionChecker2.cannot.read()) {
2577
2776
  return ctx.forbidden();
2578
2777
  }
2579
2778
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2580
- const { locale, status } = getDocumentLocaleAndStatus(query);
2779
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2581
2780
  const version = await findDocument(permissionQuery, model, { locale, status });
2582
2781
  if (!version) {
2583
2782
  if (permissionChecker2.cannot.create()) {
@@ -2587,9 +2786,11 @@ const singleTypes = {
2587
2786
  if (!document) {
2588
2787
  return ctx.notFound();
2589
2788
  }
2590
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2789
+ const { meta } = await formatDocumentWithMetadata(
2790
+ permissionChecker2,
2591
2791
  model,
2592
- { id: document.documentId, locale, publishedAt: null },
2792
+ // @ts-expect-error - fix types
2793
+ { documentId: document.documentId, locale, publishedAt: null },
2593
2794
  { availableLocales: true, availableStatus: false }
2594
2795
  );
2595
2796
  ctx.body = { data: {}, meta };
@@ -2599,16 +2800,15 @@ const singleTypes = {
2599
2800
  return ctx.forbidden();
2600
2801
  }
2601
2802
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2602
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2803
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2603
2804
  },
2604
2805
  async createOrUpdate(ctx) {
2605
2806
  const { userAbility } = ctx.state;
2606
2807
  const { model } = ctx.params;
2607
- const documentMetadata2 = getService$1("document-metadata");
2608
2808
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2609
2809
  const document = await createOrUpdateDocument(ctx);
2610
2810
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2611
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2811
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2612
2812
  },
2613
2813
  async delete(ctx) {
2614
2814
  const { userAbility } = ctx.state;
@@ -2621,7 +2821,7 @@ const singleTypes = {
2621
2821
  }
2622
2822
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2623
2823
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2624
- const { locale } = getDocumentLocaleAndStatus(query);
2824
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2625
2825
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2626
2826
  populate,
2627
2827
  locale
@@ -2644,7 +2844,6 @@ const singleTypes = {
2644
2844
  const { model } = ctx.params;
2645
2845
  const { query = {} } = ctx.request;
2646
2846
  const documentManager2 = getService$1("document-manager");
2647
- const documentMetadata2 = getService$1("document-metadata");
2648
2847
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2649
2848
  if (permissionChecker2.cannot.publish()) {
2650
2849
  return ctx.forbidden();
@@ -2659,11 +2858,12 @@ const singleTypes = {
2659
2858
  if (permissionChecker2.cannot.publish(document)) {
2660
2859
  throw new strapiUtils.errors.ForbiddenError();
2661
2860
  }
2662
- const { locale } = getDocumentLocaleAndStatus(document);
2663
- return documentManager2.publish(document.documentId, model, { locale });
2861
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2862
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2863
+ return publishResult.at(0);
2664
2864
  });
2665
2865
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2666
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2866
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2667
2867
  },
2668
2868
  async unpublish(ctx) {
2669
2869
  const { userAbility } = ctx.state;
@@ -2673,7 +2873,6 @@ const singleTypes = {
2673
2873
  query = {}
2674
2874
  } = ctx.request;
2675
2875
  const documentManager2 = getService$1("document-manager");
2676
- const documentMetadata2 = getService$1("document-metadata");
2677
2876
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2678
2877
  if (permissionChecker2.cannot.unpublish()) {
2679
2878
  return ctx.forbidden();
@@ -2682,7 +2881,7 @@ const singleTypes = {
2682
2881
  return ctx.forbidden();
2683
2882
  }
2684
2883
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2685
- const { locale } = getDocumentLocaleAndStatus(body);
2884
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2686
2885
  const document = await findDocument(sanitizedQuery, model, { locale });
2687
2886
  if (!document) {
2688
2887
  return ctx.notFound();
@@ -2700,7 +2899,7 @@ const singleTypes = {
2700
2899
  ctx.body = await strapiUtils.async.pipe(
2701
2900
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2702
2901
  permissionChecker2.sanitizeOutput,
2703
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2902
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2704
2903
  )(document);
2705
2904
  });
2706
2905
  },
@@ -2709,13 +2908,12 @@ const singleTypes = {
2709
2908
  const { model } = ctx.params;
2710
2909
  const { body, query = {} } = ctx.request;
2711
2910
  const documentManager2 = getService$1("document-manager");
2712
- const documentMetadata2 = getService$1("document-metadata");
2713
2911
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2714
2912
  if (permissionChecker2.cannot.discard()) {
2715
2913
  return ctx.forbidden();
2716
2914
  }
2717
2915
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2718
- const { locale } = getDocumentLocaleAndStatus(body);
2916
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2719
2917
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2720
2918
  if (!document) {
2721
2919
  return ctx.notFound();
@@ -2726,7 +2924,7 @@ const singleTypes = {
2726
2924
  ctx.body = await strapiUtils.async.pipe(
2727
2925
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2728
2926
  permissionChecker2.sanitizeOutput,
2729
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2927
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2730
2928
  )(document);
2731
2929
  },
2732
2930
  async countDraftRelations(ctx) {
@@ -2735,7 +2933,7 @@ const singleTypes = {
2735
2933
  const { query } = ctx.request;
2736
2934
  const documentManager2 = getService$1("document-manager");
2737
2935
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2738
- const { locale } = getDocumentLocaleAndStatus(query);
2936
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2739
2937
  if (permissionChecker2.cannot.read()) {
2740
2938
  return ctx.forbidden();
2741
2939
  }
@@ -2756,7 +2954,7 @@ const uid$1 = {
2756
2954
  async generateUID(ctx) {
2757
2955
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2758
2956
  const { query = {} } = ctx.request;
2759
- const { locale } = getDocumentLocaleAndStatus(query);
2957
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2760
2958
  await validateUIDField(contentTypeUID, field);
2761
2959
  const uidService = getService$1("uid");
2762
2960
  ctx.body = {
@@ -2768,7 +2966,7 @@ const uid$1 = {
2768
2966
  ctx.request.body
2769
2967
  );
2770
2968
  const { query = {} } = ctx.request;
2771
- const { locale } = getDocumentLocaleAndStatus(query);
2969
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2772
2970
  await validateUIDField(contentTypeUID, field);
2773
2971
  const uidService = getService$1("uid");
2774
2972
  const isAvailable = await uidService.checkUIDAvailability({
@@ -2791,7 +2989,8 @@ const controllers = {
2791
2989
  relations,
2792
2990
  "single-types": singleTypes,
2793
2991
  uid: uid$1,
2794
- ...history.controllers ? history.controllers : {}
2992
+ ...history.controllers ? history.controllers : {},
2993
+ ...preview.controllers ? preview.controllers : {}
2795
2994
  };
2796
2995
  const keys = {
2797
2996
  CONFIGURATION: "configuration"
@@ -3411,12 +3610,27 @@ const createPermissionChecker = (strapi2) => ({ userAbility, model }) => {
3411
3610
  ability: userAbility,
3412
3611
  model
3413
3612
  });
3414
- const toSubject = (entity) => entity ? permissionsManager.toSubject(entity, model) : model;
3613
+ const { actionProvider } = strapi2.service("admin::permission");
3614
+ const toSubject = (entity) => {
3615
+ return entity ? permissionsManager.toSubject(entity, model) : model;
3616
+ };
3415
3617
  const can = (action, entity, field) => {
3416
- return userAbility.can(action, toSubject(entity), field);
3618
+ const subject = toSubject(entity);
3619
+ const aliases = actionProvider.unstable_aliases(action, model);
3620
+ return (
3621
+ // Test the original action to see if it passes
3622
+ userAbility.can(action, subject, field) || // Else try every known alias if at least one of them succeed, then the user "can"
3623
+ aliases.some((alias) => userAbility.can(alias, subject, field))
3624
+ );
3417
3625
  };
3418
3626
  const cannot = (action, entity, field) => {
3419
- return userAbility.cannot(action, toSubject(entity), field);
3627
+ const subject = toSubject(entity);
3628
+ const aliases = actionProvider.unstable_aliases(action, model);
3629
+ return (
3630
+ // Test both the original action
3631
+ userAbility.cannot(action, subject, field) && // and every known alias, if all of them fail (cannot), then the user truly "cannot"
3632
+ aliases.every((alias) => userAbility.cannot(alias, subject, field))
3633
+ );
3420
3634
  };
3421
3635
  const sanitizeOutput = (data, { action = ACTIONS.read } = {}) => {
3422
3636
  return permissionsManager.sanitizeOutput(data, { subject: toSubject(data), action });
@@ -3559,7 +3773,7 @@ const permission = ({ strapi: strapi2 }) => ({
3559
3773
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3560
3774
  }
3561
3775
  });
3562
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils__default.default.contentTypes;
3776
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils__default.default.contentTypes;
3563
3777
  const { isAnyToMany } = strapiUtils__default.default.relations;
3564
3778
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils__default.default.contentTypes.constants;
3565
3779
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3650,6 +3864,42 @@ const getDeepPopulate = (uid2, {
3650
3864
  {}
3651
3865
  );
3652
3866
  };
3867
+ const getValidatableFieldsPopulate = (uid2, {
3868
+ initialPopulate = {},
3869
+ countMany = false,
3870
+ countOne = false,
3871
+ maxLevel = Infinity
3872
+ } = {}, level = 1) => {
3873
+ if (level > maxLevel) {
3874
+ return {};
3875
+ }
3876
+ const model = strapi.getModel(uid2);
3877
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3878
+ if (!getDoesAttributeRequireValidation(attribute)) {
3879
+ return populateAcc;
3880
+ }
3881
+ if (isScalarAttribute(attribute)) {
3882
+ return fp.merge(populateAcc, {
3883
+ [attributeName]: true
3884
+ });
3885
+ }
3886
+ return fp.merge(
3887
+ populateAcc,
3888
+ getPopulateFor(
3889
+ attributeName,
3890
+ model,
3891
+ {
3892
+ // @ts-expect-error - improve types
3893
+ initialPopulate: initialPopulate?.[attributeName],
3894
+ countMany,
3895
+ countOne,
3896
+ maxLevel
3897
+ },
3898
+ level
3899
+ )
3900
+ );
3901
+ }, {});
3902
+ };
3653
3903
  const getDeepPopulateDraftCount = (uid2) => {
3654
3904
  const model = strapi.getModel(uid2);
3655
3905
  let hasRelations = false;
@@ -3657,6 +3907,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3657
3907
  const attribute = model.attributes[attributeName];
3658
3908
  switch (attribute.type) {
3659
3909
  case "relation": {
3910
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3911
+ if (isMorphRelation) {
3912
+ break;
3913
+ }
3660
3914
  if (isVisibleAttribute$1(model, attributeName)) {
3661
3915
  populateAcc[attributeName] = {
3662
3916
  count: true,
@@ -3671,22 +3925,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3671
3925
  attribute.component
3672
3926
  );
3673
3927
  if (childHasRelations) {
3674
- populateAcc[attributeName] = { populate: populate2 };
3928
+ populateAcc[attributeName] = {
3929
+ populate: populate2
3930
+ };
3675
3931
  hasRelations = true;
3676
3932
  }
3677
3933
  break;
3678
3934
  }
3679
3935
  case "dynamiczone": {
3680
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3681
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3682
- if (childHasRelations) {
3936
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3937
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3938
+ if (componentHasRelations) {
3683
3939
  hasRelations = true;
3684
- return fp.merge(acc, populate2);
3940
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3685
3941
  }
3686
3942
  return acc;
3687
3943
  }, {});
3688
- if (!fp.isEmpty(dzPopulate)) {
3689
- populateAcc[attributeName] = { populate: dzPopulate };
3944
+ if (!fp.isEmpty(dzPopulateFragment)) {
3945
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3690
3946
  }
3691
3947
  break;
3692
3948
  }
@@ -3878,41 +4134,72 @@ const AVAILABLE_STATUS_FIELDS = [
3878
4134
  "updatedBy",
3879
4135
  "status"
3880
4136
  ];
3881
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4137
+ const AVAILABLE_LOCALES_FIELDS = [
4138
+ "id",
4139
+ "locale",
4140
+ "updatedAt",
4141
+ "createdAt",
4142
+ "status",
4143
+ "publishedAt",
4144
+ "documentId"
4145
+ ];
3882
4146
  const CONTENT_MANAGER_STATUS = {
3883
4147
  PUBLISHED: "published",
3884
4148
  DRAFT: "draft",
3885
4149
  MODIFIED: "modified"
3886
4150
  };
3887
- const areDatesEqual = (date1, date2, threshold) => {
3888
- if (!date1 || !date2) {
4151
+ const getIsVersionLatestModification = (version, otherVersion) => {
4152
+ if (!version || !version.updatedAt) {
3889
4153
  return false;
3890
4154
  }
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;
4155
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4156
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4157
+ return versionUpdatedAt > otherUpdatedAt;
3895
4158
  };
3896
4159
  const documentMetadata = ({ strapi: strapi2 }) => ({
3897
4160
  /**
3898
4161
  * Returns available locales of a document for the current status
3899
4162
  */
3900
- getAvailableLocales(uid2, version, allVersions) {
4163
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3901
4164
  const versionsByLocale = fp.groupBy("locale", allVersions);
3902
- 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]);
4165
+ if (version.locale) {
4166
+ delete versionsByLocale[version.locale];
4167
+ }
4168
+ const model = strapi2.getModel(uid2);
4169
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4170
+ const traversalFunction = async (localeVersion) => strapiUtils.traverseEntity(
4171
+ ({ key }, { remove }) => {
4172
+ if (keysToKeep.includes(key)) {
4173
+ return;
4174
+ }
4175
+ remove(key);
4176
+ },
4177
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4178
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4179
+ localeVersion
4180
+ );
4181
+ const mappingResult = await strapiUtils.async.map(
4182
+ Object.values(versionsByLocale),
4183
+ async (localeVersions) => {
4184
+ const mappedLocaleVersions = await strapiUtils.async.map(
4185
+ localeVersions,
4186
+ traversalFunction
4187
+ );
4188
+ if (!strapiUtils.contentTypes.hasDraftAndPublish(model)) {
4189
+ return mappedLocaleVersions[0];
4190
+ }
4191
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4192
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4193
+ if (!draftVersion) {
4194
+ return;
4195
+ }
4196
+ return {
4197
+ ...draftVersion,
4198
+ status: this.getStatus(draftVersion, otherVersions)
4199
+ };
3906
4200
  }
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);
4201
+ );
4202
+ return mappingResult.filter(Boolean);
3916
4203
  },
3917
4204
  /**
3918
4205
  * Returns available status of a document for the current locale
@@ -3950,26 +4237,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3950
4237
  });
3951
4238
  },
3952
4239
  getStatus(version, otherDocumentStatuses) {
3953
- const isDraft = version.publishedAt === null;
3954
- if (!otherDocumentStatuses?.length) {
3955
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4240
+ let draftVersion;
4241
+ let publishedVersion;
4242
+ if (version.publishedAt) {
4243
+ publishedVersion = version;
4244
+ } else {
4245
+ draftVersion = version;
3956
4246
  }
3957
- if (isDraft) {
3958
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3959
- if (!publishedVersion) {
3960
- return CONTENT_MANAGER_STATUS.DRAFT;
3961
- }
4247
+ const otherVersion = otherDocumentStatuses?.at(0);
4248
+ if (otherVersion?.publishedAt) {
4249
+ publishedVersion = otherVersion;
4250
+ } else if (otherVersion) {
4251
+ draftVersion = otherVersion;
3962
4252
  }
3963
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4253
+ if (!draftVersion)
3964
4254
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3965
- }
3966
- return CONTENT_MANAGER_STATUS.MODIFIED;
4255
+ if (!publishedVersion)
4256
+ return CONTENT_MANAGER_STATUS.DRAFT;
4257
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4258
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3967
4259
  },
4260
+ // TODO is it necessary to return metadata on every page of the CM
4261
+ // We could refactor this so the locales are only loaded when they're
4262
+ // needed. e.g. in the bulk locale action modal.
3968
4263
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4264
+ const populate = getValidatableFieldsPopulate(uid2);
3969
4265
  const versions = await strapi2.db.query(uid2).findMany({
3970
4266
  where: { documentId: version.documentId },
3971
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3972
4267
  populate: {
4268
+ // Populate only fields that require validation for bulk locale actions
4269
+ ...populate,
4270
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3973
4271
  createdBy: {
3974
4272
  select: ["id", "firstname", "lastname", "email"]
3975
4273
  },
@@ -3978,7 +4276,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3978
4276
  }
3979
4277
  }
3980
4278
  });
3981
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4279
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3982
4280
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3983
4281
  return {
3984
4282
  availableLocales: availableLocalesResult,
@@ -3991,8 +4289,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3991
4289
  * - Available status of the document for the current locale
3992
4290
  */
3993
4291
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3994
- if (!document)
3995
- return document;
4292
+ if (!document) {
4293
+ return {
4294
+ data: document,
4295
+ meta: {
4296
+ availableLocales: [],
4297
+ availableStatus: []
4298
+ }
4299
+ };
4300
+ }
3996
4301
  const hasDraftAndPublish = strapiUtils.contentTypes.hasDraftAndPublish(strapi2.getModel(uid2));
3997
4302
  if (!hasDraftAndPublish) {
3998
4303
  opts.availableStatus = false;
@@ -4042,26 +4347,9 @@ const sumDraftCounts = (entity, uid2) => {
4042
4347
  }, 0);
4043
4348
  };
4044
4349
  const { ApplicationError } = strapiUtils.errors;
4045
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4046
4350
  const { PUBLISHED_AT_ATTRIBUTE } = strapiUtils.contentTypes.constants;
4047
4351
  const omitPublishedAtField = fp.omit(PUBLISHED_AT_ATTRIBUTE);
4048
4352
  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
4353
  const documentManager = ({ strapi: strapi2 }) => {
4066
4354
  return {
4067
4355
  async findOne(id, uid2, opts = {}) {
@@ -4080,6 +4368,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4080
4368
  } else if (opts.locale && opts.locale !== "*") {
4081
4369
  where.locale = opts.locale;
4082
4370
  }
4371
+ if (typeof opts.isPublished === "boolean") {
4372
+ where.publishedAt = { $notNull: opts.isPublished };
4373
+ }
4083
4374
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4084
4375
  },
4085
4376
  async findMany(opts, uid2) {
@@ -4113,10 +4404,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4113
4404
  async clone(id, body, uid2) {
4114
4405
  const populate = await buildDeepPopulate(uid2);
4115
4406
  const params = {
4116
- data: {
4117
- ...omitIdField(body),
4118
- [PUBLISHED_AT_ATTRIBUTE]: null
4119
- },
4407
+ data: omitIdField(body),
4120
4408
  populate
4121
4409
  };
4122
4410
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4142,70 +4430,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4142
4430
  return {};
4143
4431
  },
4144
4432
  // FIXME: handle relations
4145
- async deleteMany(opts, uid2) {
4146
- const docs = await strapi2.documents(uid2).findMany(opts);
4147
- for (const doc of docs) {
4148
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4149
- }
4150
- return { count: docs.length };
4433
+ async deleteMany(documentIds, uid2, opts = {}) {
4434
+ const deletedEntries = await strapi2.db.transaction(async () => {
4435
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4436
+ });
4437
+ return { count: deletedEntries.length };
4151
4438
  },
4152
4439
  async publish(id, uid2, opts = {}) {
4153
4440
  const populate = await buildDeepPopulate(uid2);
4154
4441
  const params = { ...opts, populate };
4155
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4442
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4156
4443
  },
4157
- async publishMany(entities, uid2) {
4158
- if (!entities.length) {
4159
- return null;
4160
- }
4161
- await Promise.all(
4162
- entities.map((document) => {
4163
- return strapi2.entityValidator.validateEntityCreation(
4164
- strapi2.getModel(uid2),
4165
- document,
4166
- void 0,
4167
- // @ts-expect-error - FIXME: entity here is unnecessary
4168
- document
4169
- );
4170
- })
4171
- );
4172
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4173
- const filters = { id: { $in: entitiesToPublish } };
4174
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4175
- const populate = await buildDeepPopulate(uid2);
4176
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4177
- where: filters,
4178
- data
4179
- });
4180
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4181
- where: filters,
4182
- populate
4444
+ async publishMany(uid2, documentIds, locale) {
4445
+ return strapi2.db.transaction(async () => {
4446
+ const results = await Promise.all(
4447
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4448
+ );
4449
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4450
+ return publishedEntitiesCount;
4183
4451
  });
4184
- await Promise.all(
4185
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4186
- );
4187
- return publishedEntitiesCount;
4188
4452
  },
4189
- async unpublishMany(documents, uid2) {
4190
- if (!documents.length) {
4191
- return null;
4192
- }
4193
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4194
- const filters = { id: { $in: entitiesToUnpublish } };
4195
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4196
- const populate = await buildDeepPopulate(uid2);
4197
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4198
- where: filters,
4199
- data
4200
- });
4201
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4202
- where: filters,
4203
- populate
4453
+ async unpublishMany(documentIds, uid2, opts = {}) {
4454
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4455
+ return Promise.all(
4456
+ documentIds.map(
4457
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4458
+ )
4459
+ );
4204
4460
  });
4205
- await Promise.all(
4206
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4207
- );
4208
- return unpublishedEntitiesCount;
4461
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4462
+ return { count: unpublishedEntitiesCount };
4209
4463
  },
4210
4464
  async unpublish(id, uid2, opts = {}) {
4211
4465
  const populate = await buildDeepPopulate(uid2);
@@ -4230,16 +4484,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4230
4484
  }
4231
4485
  return sumDraftCounts(document, uid2);
4232
4486
  },
4233
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4487
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4234
4488
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4235
4489
  if (!hasRelations) {
4236
4490
  return 0;
4237
4491
  }
4492
+ let localeFilter = {};
4493
+ if (locale) {
4494
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4495
+ }
4238
4496
  const entities = await strapi2.db.query(uid2).findMany({
4239
4497
  populate,
4240
4498
  where: {
4241
- id: { $in: ids },
4242
- ...locale ? { locale } : {}
4499
+ documentId: { $in: documentIds },
4500
+ ...localeFilter
4243
4501
  }
4244
4502
  });
4245
4503
  const totalNumberDraftRelations = entities.reduce(
@@ -4262,7 +4520,8 @@ const services = {
4262
4520
  permission,
4263
4521
  "populate-builder": populateBuilder$1,
4264
4522
  uid,
4265
- ...history.services ? history.services : {}
4523
+ ...history.services ? history.services : {},
4524
+ ...preview.services ? preview.services : {}
4266
4525
  };
4267
4526
  const index = () => {
4268
4527
  return {