@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
@@ -1,5 +1,5 @@
1
- import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, pagination, sanitize } from "@strapi/utils";
2
- import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
1
+ import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, traverseEntity, pagination } from "@strapi/utils";
2
+ import { pick, omit, difference, castArray, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy } from "lodash/fp";
3
3
  import "@strapi/types";
4
4
  import * as yup from "yup";
5
5
  import { scheduleJob } from "node-schedule";
@@ -95,7 +95,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
95
95
  }
96
96
  };
97
97
  };
98
- const controllers$1 = {
98
+ const controllers$2 = {
99
99
  "history-version": createHistoryVersionController
100
100
  /**
101
101
  * Casting is needed because the types aren't aware that Strapi supports
@@ -173,7 +173,9 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
173
173
  return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
174
174
  };
175
175
  const localesService = strapi2.plugin("i18n")?.service("locales");
176
+ const i18nContentTypeService = strapi2.plugin("i18n")?.service("content-types");
176
177
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
178
+ const isLocalizedContentType = (model) => i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
177
179
  const getLocaleDictionary = async () => {
178
180
  if (!localesService)
179
181
  return {};
@@ -200,20 +202,25 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
200
202
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
201
203
  return documentMetadataService.getStatus(document, meta.availableStatus);
202
204
  };
203
- const getDeepPopulate2 = (uid2) => {
205
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
204
206
  const model = strapi2.getModel(uid2);
205
207
  const attributes = Object.entries(model.attributes);
208
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
206
209
  return attributes.reduce((acc, [attributeName, attribute]) => {
207
210
  switch (attribute.type) {
208
211
  case "relation": {
212
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
213
+ if (isMorphRelation) {
214
+ break;
215
+ }
209
216
  const isVisible2 = contentTypes$1.isVisibleAttribute(model, attributeName);
210
217
  if (isVisible2) {
211
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
218
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
212
219
  }
213
220
  break;
214
221
  }
215
222
  case "media": {
216
- acc[attributeName] = { fields: ["id"] };
223
+ acc[attributeName] = { [fieldSelector]: ["id"] };
217
224
  break;
218
225
  }
219
226
  case "component": {
@@ -286,6 +293,7 @@ const createServiceUtils = ({ strapi: strapi2 }) => {
286
293
  getRelationRestoreValue,
287
294
  getMediaRestoreValue,
288
295
  getDefaultLocale,
296
+ isLocalizedContentType,
289
297
  getLocaleDictionary,
290
298
  getRetentionDays,
291
299
  getVersionStatus,
@@ -308,7 +316,13 @@ const createHistoryService = ({ strapi: strapi2 }) => {
308
316
  });
309
317
  },
310
318
  async findVersionsPage(params) {
311
- const locale = params.query.locale || await serviceUtils.getDefaultLocale();
319
+ const model = strapi2.getModel(params.query.contentType);
320
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
321
+ const defaultLocale = await serviceUtils.getDefaultLocale();
322
+ let locale = null;
323
+ if (isLocalizedContentType) {
324
+ locale = params.query.locale || defaultLocale;
325
+ }
312
326
  const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
313
327
  query.findPage({
314
328
  ...params.query,
@@ -353,7 +367,12 @@ const createHistoryService = ({ strapi: strapi2 }) => {
353
367
  if (userToPopulate == null) {
354
368
  return null;
355
369
  }
356
- return strapi2.query("admin::user").findOne({ where: { id: userToPopulate.id } });
370
+ return strapi2.query("admin::user").findOne({
371
+ where: {
372
+ ...userToPopulate.id ? { id: userToPopulate.id } : {},
373
+ ...userToPopulate.documentId ? { documentId: userToPopulate.documentId } : {}
374
+ }
375
+ });
357
376
  })
358
377
  );
359
378
  return {
@@ -464,13 +483,47 @@ const createHistoryService = ({ strapi: strapi2 }) => {
464
483
  }
465
484
  };
466
485
  };
486
+ const shouldCreateHistoryVersion = (context) => {
487
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
488
+ return false;
489
+ }
490
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
491
+ return false;
492
+ }
493
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
494
+ return false;
495
+ }
496
+ if (!context.contentType.uid.startsWith("api::")) {
497
+ return false;
498
+ }
499
+ return true;
500
+ };
501
+ const getSchemas = (uid2) => {
502
+ const attributesSchema = strapi.getModel(uid2).attributes;
503
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
504
+ (currentComponentSchemas, key) => {
505
+ const fieldSchema = attributesSchema[key];
506
+ if (fieldSchema.type === "component") {
507
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
508
+ return {
509
+ ...currentComponentSchemas,
510
+ [fieldSchema.component]: componentSchema
511
+ };
512
+ }
513
+ return currentComponentSchemas;
514
+ },
515
+ {}
516
+ );
517
+ return {
518
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
519
+ componentsSchemas
520
+ };
521
+ };
467
522
  const createLifecyclesService = ({ strapi: strapi2 }) => {
468
523
  const state = {
469
524
  deleteExpiredJob: null,
470
525
  isInitialized: false
471
526
  };
472
- const query = strapi2.db.query(HISTORY_VERSION_UID);
473
- const historyService = getService(strapi2, "history");
474
527
  const serviceUtils = createServiceUtils({ strapi: strapi2 });
475
528
  return {
476
529
  async bootstrap() {
@@ -478,60 +531,53 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
478
531
  return;
479
532
  }
480
533
  strapi2.documents.use(async (context, next) => {
481
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
482
- return next();
483
- }
484
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
485
- return next();
486
- }
487
- const contentTypeUid = context.contentType.uid;
488
- if (!contentTypeUid.startsWith("api::")) {
489
- return next();
490
- }
491
534
  const result = await next();
492
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
535
+ if (!shouldCreateHistoryVersion(context)) {
536
+ return result;
537
+ }
538
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
493
539
  const defaultLocale = await serviceUtils.getDefaultLocale();
494
- const locale = documentContext.locale || defaultLocale;
495
- const document = await strapi2.documents(contentTypeUid).findOne({
496
- documentId: documentContext.documentId,
497
- locale,
498
- populate: serviceUtils.getDeepPopulate(contentTypeUid)
540
+ const locales = castArray(context.params?.locale || defaultLocale);
541
+ if (!locales.length) {
542
+ return result;
543
+ }
544
+ const uid2 = context.contentType.uid;
545
+ const schemas = getSchemas(uid2);
546
+ const model = strapi2.getModel(uid2);
547
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
548
+ const localeEntries = await strapi2.db.query(uid2).findMany({
549
+ where: {
550
+ documentId,
551
+ ...isLocalizedContentType ? { locale: { $in: locales } } : {},
552
+ ...contentTypes$1.hasDraftAndPublish(strapi2.contentTypes[uid2]) ? { publishedAt: null } : {}
553
+ },
554
+ populate: serviceUtils.getDeepPopulate(
555
+ uid2,
556
+ true
557
+ /* use database syntax */
558
+ )
499
559
  });
500
- const status = await serviceUtils.getVersionStatus(contentTypeUid, document);
501
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
502
- const componentsSchemas = Object.keys(
503
- attributesSchema
504
- ).reduce((currentComponentSchemas, key) => {
505
- const fieldSchema = attributesSchema[key];
506
- if (fieldSchema.type === "component") {
507
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
508
- return {
509
- ...currentComponentSchemas,
510
- [fieldSchema.component]: componentSchema
511
- };
512
- }
513
- return currentComponentSchemas;
514
- }, {});
515
560
  await strapi2.db.transaction(async ({ onCommit }) => {
516
- onCommit(() => {
517
- historyService.createVersion({
518
- contentType: contentTypeUid,
519
- data: omit(FIELDS_TO_IGNORE, document),
520
- schema: omit(FIELDS_TO_IGNORE, attributesSchema),
521
- componentsSchemas,
522
- relatedDocumentId: documentContext.documentId,
523
- locale,
524
- status
525
- });
561
+ onCommit(async () => {
562
+ for (const entry of localeEntries) {
563
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
564
+ await getService(strapi2, "history").createVersion({
565
+ contentType: uid2,
566
+ data: omit(FIELDS_TO_IGNORE, entry),
567
+ relatedDocumentId: documentId,
568
+ locale: entry.locale,
569
+ status,
570
+ ...schemas
571
+ });
572
+ }
526
573
  });
527
574
  });
528
575
  return result;
529
576
  });
530
- const retentionDays = serviceUtils.getRetentionDays();
531
577
  state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
532
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
578
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
533
579
  const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
534
- query.deleteMany({
580
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
535
581
  where: {
536
582
  created_at: {
537
583
  $lt: expirationDate.toISOString()
@@ -548,17 +594,17 @@ const createLifecyclesService = ({ strapi: strapi2 }) => {
548
594
  }
549
595
  };
550
596
  };
551
- const services$1 = {
597
+ const services$2 = {
552
598
  history: createHistoryService,
553
599
  lifecycles: createLifecyclesService
554
600
  };
555
- const info = { pluginName: "content-manager", type: "admin" };
601
+ const info$1 = { pluginName: "content-manager", type: "admin" };
556
602
  const historyVersionRouter = {
557
603
  type: "admin",
558
604
  routes: [
559
605
  {
560
606
  method: "GET",
561
- info,
607
+ info: info$1,
562
608
  path: "/history-versions",
563
609
  handler: "history-version.findMany",
564
610
  config: {
@@ -567,7 +613,7 @@ const historyVersionRouter = {
567
613
  },
568
614
  {
569
615
  method: "PUT",
570
- info,
616
+ info: info$1,
571
617
  path: "/history-versions/:versionId/restore",
572
618
  handler: "history-version.restoreVersion",
573
619
  config: {
@@ -576,7 +622,7 @@ const historyVersionRouter = {
576
622
  }
577
623
  ]
578
624
  };
579
- const routes$1 = {
625
+ const routes$2 = {
580
626
  "history-version": historyVersionRouter
581
627
  };
582
628
  const historyVersion = {
@@ -623,7 +669,7 @@ const historyVersion = {
623
669
  }
624
670
  }
625
671
  };
626
- const getFeature = () => {
672
+ const getFeature$1 = () => {
627
673
  if (strapi.ee.features.isEnabled("cms-content-history")) {
628
674
  return {
629
675
  register({ strapi: strapi2 }) {
@@ -635,9 +681,9 @@ const getFeature = () => {
635
681
  destroy({ strapi: strapi2 }) {
636
682
  getService(strapi2, "lifecycles").destroy();
637
683
  },
638
- controllers: controllers$1,
639
- services: services$1,
640
- routes: routes$1
684
+ controllers: controllers$2,
685
+ services: services$2,
686
+ routes: routes$2
641
687
  };
642
688
  }
643
689
  return {
@@ -646,7 +692,7 @@ const getFeature = () => {
646
692
  }
647
693
  };
648
694
  };
649
- const history = getFeature();
695
+ const history = getFeature$1();
650
696
  const register = async ({ strapi: strapi2 }) => {
651
697
  await history.register?.({ strapi: strapi2 });
652
698
  };
@@ -654,6 +700,62 @@ const ALLOWED_WEBHOOK_EVENTS = {
654
700
  ENTRY_PUBLISH: "entry.publish",
655
701
  ENTRY_UNPUBLISH: "entry.unpublish"
656
702
  };
703
+ const FEATURE_ID = "preview";
704
+ const info = { pluginName: "content-manager", type: "admin" };
705
+ const previewRouter = {
706
+ type: "admin",
707
+ routes: [
708
+ {
709
+ method: "GET",
710
+ info,
711
+ path: "/preview/url/:contentType",
712
+ handler: "preview.getPreviewURL",
713
+ config: {
714
+ policies: ["admin::isAuthenticatedAdmin"]
715
+ }
716
+ }
717
+ ]
718
+ };
719
+ const routes$1 = {
720
+ preview: previewRouter
721
+ };
722
+ const createPreviewController = () => {
723
+ return {
724
+ async getPreviewURL(ctx) {
725
+ ctx.request;
726
+ return {
727
+ data: { url: "" }
728
+ };
729
+ }
730
+ };
731
+ };
732
+ const controllers$1 = {
733
+ preview: createPreviewController
734
+ /**
735
+ * Casting is needed because the types aren't aware that Strapi supports
736
+ * passing a controller factory as the value, instead of a controller object directly
737
+ */
738
+ };
739
+ const createPreviewService = () => {
740
+ };
741
+ const services$1 = {
742
+ preview: createPreviewService
743
+ };
744
+ const getFeature = () => {
745
+ if (!strapi.features.future.isEnabled(FEATURE_ID)) {
746
+ return {};
747
+ }
748
+ return {
749
+ bootstrap() {
750
+ console.log("Bootstrapping preview server");
751
+ strapi.config.get("admin.preview");
752
+ },
753
+ routes: routes$1,
754
+ controllers: controllers$1,
755
+ services: services$1
756
+ };
757
+ };
758
+ const preview = getFeature();
657
759
  const bootstrap = async () => {
658
760
  Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
659
761
  strapi.get("webhookStore").addAllowedEvent(key, value);
@@ -663,6 +765,7 @@ const bootstrap = async () => {
663
765
  await getService$1("content-types").syncConfigurations();
664
766
  await getService$1("permission").registerPermissions();
665
767
  await history.bootstrap?.({ strapi });
768
+ await preview.bootstrap?.({ strapi });
666
769
  };
667
770
  const destroy = async ({ strapi: strapi2 }) => {
668
771
  await history.destroy?.({ strapi: strapi2 });
@@ -1152,7 +1255,8 @@ const admin = {
1152
1255
  };
1153
1256
  const routes = {
1154
1257
  admin,
1155
- ...history.routes ? history.routes : {}
1258
+ ...history.routes ? history.routes : {},
1259
+ ...preview.routes ? preview.routes : {}
1156
1260
  };
1157
1261
  const hasPermissionsSchema = yup$1.object({
1158
1262
  actions: yup$1.array().of(yup$1.string()),
@@ -1163,6 +1267,11 @@ const { createPolicy } = policy;
1163
1267
  const hasPermissions = createPolicy({
1164
1268
  name: "plugin::content-manager.hasPermissions",
1165
1269
  validator: validateHasPermissionsInput,
1270
+ /**
1271
+ * NOTE: Action aliases are currently not checked at this level (policy).
1272
+ * This is currently the intended behavior to avoid changing the behavior of API related permissions.
1273
+ * If you want to add support for it, please create a dedicated RFC with a list of potential side effect this could have.
1274
+ */
1166
1275
  handler(ctx, config = {}) {
1167
1276
  const { actions = [], hasAtLeastOne = false } = config;
1168
1277
  const { userAbility } = ctx.state;
@@ -1452,7 +1561,7 @@ const { PaginationError, ValidationError } = errors;
1452
1561
  const TYPES = ["singleType", "collectionType"];
1453
1562
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1454
1563
  const bulkActionInputSchema = yup$1.object({
1455
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1564
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1456
1565
  }).required();
1457
1566
  const generateUIDInputSchema = yup$1.object({
1458
1567
  contentTypeUID: yup$1.string().required(),
@@ -1551,15 +1660,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1551
1660
  }
1552
1661
  }, body);
1553
1662
  };
1554
- const getDocumentLocaleAndStatus = (request) => {
1555
- const { locale, status, ...rest } = request || {};
1556
- if (!isNil$1(locale) && typeof locale !== "string") {
1557
- throw new errors.ValidationError(`Invalid locale: ${locale}`);
1558
- }
1559
- if (!isNil$1(status) && !["draft", "published"].includes(status)) {
1560
- throw new errors.ValidationError(`Invalid status: ${status}`);
1663
+ const singleLocaleSchema = yup$1.string().nullable();
1664
+ const multipleLocaleSchema = yup$1.lazy(
1665
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1666
+ );
1667
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1668
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1669
+ const { allowMultipleLocales } = opts;
1670
+ const { locale, status: providedStatus, ...rest } = request || {};
1671
+ const defaultStatus = contentTypes$1.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1672
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1673
+ const schema = yup$1.object().shape({
1674
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1675
+ status: statusSchema
1676
+ });
1677
+ try {
1678
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1679
+ return { locale, status, ...rest };
1680
+ } catch (error) {
1681
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1561
1682
  }
1562
- return { locale, status, ...rest };
1683
+ };
1684
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1685
+ const documentMetadata2 = getService$1("document-metadata");
1686
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1687
+ let {
1688
+ meta: { availableLocales, availableStatus }
1689
+ } = serviceOutput;
1690
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1691
+ availableLocales = await async.map(
1692
+ availableLocales,
1693
+ async (localeDocument) => metadataSanitizer(localeDocument)
1694
+ );
1695
+ availableStatus = await async.map(
1696
+ availableStatus,
1697
+ async (statusDocument) => metadataSanitizer(statusDocument)
1698
+ );
1699
+ return {
1700
+ ...serviceOutput,
1701
+ meta: {
1702
+ availableLocales,
1703
+ availableStatus
1704
+ }
1705
+ };
1563
1706
  };
1564
1707
  const createDocument = async (ctx, opts) => {
1565
1708
  const { userAbility, user } = ctx.state;
@@ -1574,7 +1717,7 @@ const createDocument = async (ctx, opts) => {
1574
1717
  const setCreator = setCreatorFields({ user });
1575
1718
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1576
1719
  const sanitizedBody = await sanitizeFn(body);
1577
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1720
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1578
1721
  return documentManager2.create(model, {
1579
1722
  data: sanitizedBody,
1580
1723
  locale,
@@ -1593,7 +1736,7 @@ const updateDocument = async (ctx, opts) => {
1593
1736
  }
1594
1737
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1595
1738
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1596
- const { locale } = getDocumentLocaleAndStatus(body);
1739
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1597
1740
  const [documentVersion, documentExists] = await Promise.all([
1598
1741
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1599
1742
  documentManager2.exists(model, id)
@@ -1609,7 +1752,7 @@ const updateDocument = async (ctx, opts) => {
1609
1752
  throw new errors.ForbiddenError();
1610
1753
  }
1611
1754
  const pickPermittedFields = documentVersion ? permissionChecker2.sanitizeUpdateInput(documentVersion) : permissionChecker2.sanitizeCreateInput;
1612
- const setCreator = setCreatorFields({ user, isEdition: true });
1755
+ const setCreator = documentVersion ? setCreatorFields({ user, isEdition: true }) : setCreatorFields({ user });
1613
1756
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1614
1757
  const sanitizedBody = await sanitizeFn(body);
1615
1758
  return documentManager2.update(documentVersion?.documentId || id, model, {
@@ -1631,7 +1774,7 @@ const collectionTypes = {
1631
1774
  }
1632
1775
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1633
1776
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1634
- const { locale, status } = getDocumentLocaleAndStatus(query);
1777
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1635
1778
  const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1636
1779
  { ...permissionQuery, populate, locale, status },
1637
1780
  model
@@ -1660,14 +1803,13 @@ const collectionTypes = {
1660
1803
  const { userAbility } = ctx.state;
1661
1804
  const { model, id } = ctx.params;
1662
1805
  const documentManager2 = getService$1("document-manager");
1663
- const documentMetadata2 = getService$1("document-metadata");
1664
1806
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1665
1807
  if (permissionChecker2.cannot.read()) {
1666
1808
  return ctx.forbidden();
1667
1809
  }
1668
1810
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1669
1811
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1670
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1812
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1671
1813
  const version = await documentManager2.findOne(id, model, {
1672
1814
  populate,
1673
1815
  locale,
@@ -1678,9 +1820,11 @@ const collectionTypes = {
1678
1820
  if (!exists) {
1679
1821
  return ctx.notFound();
1680
1822
  }
1681
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1823
+ const { meta } = await formatDocumentWithMetadata(
1824
+ permissionChecker2,
1682
1825
  model,
1683
- { id, locale, publishedAt: null },
1826
+ // @ts-expect-error TODO: fix
1827
+ { documentId: id, locale, publishedAt: null },
1684
1828
  { availableLocales: true, availableStatus: false }
1685
1829
  );
1686
1830
  ctx.body = { data: {}, meta };
@@ -1690,12 +1834,11 @@ const collectionTypes = {
1690
1834
  return ctx.forbidden();
1691
1835
  }
1692
1836
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1693
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1837
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1694
1838
  },
1695
1839
  async create(ctx) {
1696
1840
  const { userAbility } = ctx.state;
1697
1841
  const { model } = ctx.params;
1698
- const documentMetadata2 = getService$1("document-metadata");
1699
1842
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1700
1843
  const [totalEntries, document] = await Promise.all([
1701
1844
  strapi.db.query(model).count(),
@@ -1703,7 +1846,7 @@ const collectionTypes = {
1703
1846
  ]);
1704
1847
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1705
1848
  ctx.status = 201;
1706
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1849
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1707
1850
  // Empty metadata as it's not relevant for a new document
1708
1851
  availableLocales: false,
1709
1852
  availableStatus: false
@@ -1717,25 +1860,23 @@ const collectionTypes = {
1717
1860
  async update(ctx) {
1718
1861
  const { userAbility } = ctx.state;
1719
1862
  const { model } = ctx.params;
1720
- const documentMetadata2 = getService$1("document-metadata");
1721
1863
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1722
1864
  const updatedVersion = await updateDocument(ctx);
1723
1865
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1724
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1866
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1725
1867
  },
1726
1868
  async clone(ctx) {
1727
1869
  const { userAbility, user } = ctx.state;
1728
1870
  const { model, sourceId: id } = ctx.params;
1729
1871
  const { body } = ctx.request;
1730
1872
  const documentManager2 = getService$1("document-manager");
1731
- const documentMetadata2 = getService$1("document-metadata");
1732
1873
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1733
1874
  if (permissionChecker2.cannot.create()) {
1734
1875
  return ctx.forbidden();
1735
1876
  }
1736
1877
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1737
1878
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(body);
1879
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1739
1880
  const document = await documentManager2.findOne(id, model, {
1740
1881
  populate,
1741
1882
  locale,
@@ -1751,7 +1892,7 @@ const collectionTypes = {
1751
1892
  const sanitizedBody = await sanitizeFn(body);
1752
1893
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1753
1894
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1754
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1895
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1755
1896
  // Empty metadata as it's not relevant for a new document
1756
1897
  availableLocales: false,
1757
1898
  availableStatus: false
@@ -1780,7 +1921,7 @@ const collectionTypes = {
1780
1921
  }
1781
1922
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1782
1923
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1783
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1924
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1784
1925
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1785
1926
  if (documentLocales.length === 0) {
1786
1927
  return ctx.notFound();
@@ -1802,7 +1943,6 @@ const collectionTypes = {
1802
1943
  const { id, model } = ctx.params;
1803
1944
  const { body } = ctx.request;
1804
1945
  const documentManager2 = getService$1("document-manager");
1805
- const documentMetadata2 = getService$1("document-metadata");
1806
1946
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1807
1947
  if (permissionChecker2.cannot.publish()) {
1808
1948
  return ctx.forbidden();
@@ -1810,25 +1950,52 @@ const collectionTypes = {
1810
1950
  const publishedDocument = await strapi.db.transaction(async () => {
1811
1951
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1812
1952
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1813
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1953
+ let document;
1954
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1955
+ const isCreate = isNil$1(id);
1956
+ if (isCreate) {
1957
+ if (permissionChecker2.cannot.create()) {
1958
+ throw new errors.ForbiddenError();
1959
+ }
1960
+ document = await createDocument(ctx, { populate });
1961
+ }
1962
+ const isUpdate = !isCreate;
1963
+ if (isUpdate) {
1964
+ const documentExists = documentManager2.exists(model, id);
1965
+ if (!documentExists) {
1966
+ throw new errors.NotFoundError("Document not found");
1967
+ }
1968
+ document = await documentManager2.findOne(id, model, { populate, locale });
1969
+ if (!document) {
1970
+ if (permissionChecker2.cannot.create({ locale }) || permissionChecker2.cannot.publish({ locale })) {
1971
+ throw new errors.ForbiddenError();
1972
+ }
1973
+ document = await updateDocument(ctx);
1974
+ } else if (permissionChecker2.can.update(document)) {
1975
+ await updateDocument(ctx);
1976
+ }
1977
+ }
1814
1978
  if (permissionChecker2.cannot.publish(document)) {
1815
1979
  throw new errors.ForbiddenError();
1816
1980
  }
1817
- const { locale } = getDocumentLocaleAndStatus(body);
1818
- return documentManager2.publish(document.documentId, model, {
1981
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1819
1982
  locale
1820
1983
  // TODO: Allow setting creator fields on publish
1821
1984
  // data: setCreatorFields({ user, isEdition: true })({}),
1822
1985
  });
1986
+ if (!publishResult || publishResult.length === 0) {
1987
+ throw new errors.NotFoundError("Document not found or already published.");
1988
+ }
1989
+ return publishResult[0];
1823
1990
  });
1824
1991
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1825
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1992
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1826
1993
  },
1827
1994
  async bulkPublish(ctx) {
1828
1995
  const { userAbility } = ctx.state;
1829
1996
  const { model } = ctx.params;
1830
1997
  const { body } = ctx.request;
1831
- const { ids } = body;
1998
+ const { documentIds } = body;
1832
1999
  await validateBulkActionInput(body);
1833
2000
  const documentManager2 = getService$1("document-manager");
1834
2001
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1837,8 +2004,13 @@ const collectionTypes = {
1837
2004
  }
1838
2005
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1839
2006
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1840
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1841
- const entities = await Promise.all(entityPromises);
2007
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
2008
+ allowMultipleLocales: true
2009
+ });
2010
+ const entityPromises = documentIds.map(
2011
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
2012
+ );
2013
+ const entities = (await Promise.all(entityPromises)).flat();
1842
2014
  for (const entity of entities) {
1843
2015
  if (!entity) {
1844
2016
  return ctx.notFound();
@@ -1847,24 +2019,27 @@ const collectionTypes = {
1847
2019
  return ctx.forbidden();
1848
2020
  }
1849
2021
  }
1850
- const { count } = await documentManager2.publishMany(entities, model);
2022
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1851
2023
  ctx.body = { count };
1852
2024
  },
1853
2025
  async bulkUnpublish(ctx) {
1854
2026
  const { userAbility } = ctx.state;
1855
2027
  const { model } = ctx.params;
1856
2028
  const { body } = ctx.request;
1857
- const { ids } = body;
2029
+ const { documentIds } = body;
1858
2030
  await validateBulkActionInput(body);
1859
2031
  const documentManager2 = getService$1("document-manager");
1860
2032
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1861
2033
  if (permissionChecker2.cannot.unpublish()) {
1862
2034
  return ctx.forbidden();
1863
2035
  }
1864
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1865
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1866
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1867
- const entities = await Promise.all(entityPromises);
2036
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
2037
+ allowMultipleLocales: true
2038
+ });
2039
+ const entityPromises = documentIds.map(
2040
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
2041
+ );
2042
+ const entities = (await Promise.all(entityPromises)).flat();
1868
2043
  for (const entity of entities) {
1869
2044
  if (!entity) {
1870
2045
  return ctx.notFound();
@@ -1873,7 +2048,8 @@ const collectionTypes = {
1873
2048
  return ctx.forbidden();
1874
2049
  }
1875
2050
  }
1876
- const { count } = await documentManager2.unpublishMany(entities, model);
2051
+ const entitiesIds = entities.map((document) => document.documentId);
2052
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1877
2053
  ctx.body = { count };
1878
2054
  },
1879
2055
  async unpublish(ctx) {
@@ -1883,7 +2059,6 @@ const collectionTypes = {
1883
2059
  body: { discardDraft, ...body }
1884
2060
  } = ctx.request;
1885
2061
  const documentManager2 = getService$1("document-manager");
1886
- const documentMetadata2 = getService$1("document-metadata");
1887
2062
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1888
2063
  if (permissionChecker2.cannot.unpublish()) {
1889
2064
  return ctx.forbidden();
@@ -1893,7 +2068,7 @@ const collectionTypes = {
1893
2068
  }
1894
2069
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1895
2070
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1896
- const { locale } = getDocumentLocaleAndStatus(body);
2071
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1897
2072
  const document = await documentManager2.findOne(id, model, {
1898
2073
  populate,
1899
2074
  locale,
@@ -1915,7 +2090,7 @@ const collectionTypes = {
1915
2090
  ctx.body = await async.pipe(
1916
2091
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1917
2092
  permissionChecker2.sanitizeOutput,
1918
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2093
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1919
2094
  )(document);
1920
2095
  });
1921
2096
  },
@@ -1924,14 +2099,13 @@ const collectionTypes = {
1924
2099
  const { id, model } = ctx.params;
1925
2100
  const { body } = ctx.request;
1926
2101
  const documentManager2 = getService$1("document-manager");
1927
- const documentMetadata2 = getService$1("document-metadata");
1928
2102
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1929
2103
  if (permissionChecker2.cannot.discard()) {
1930
2104
  return ctx.forbidden();
1931
2105
  }
1932
2106
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1933
2107
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1934
- const { locale } = getDocumentLocaleAndStatus(body);
2108
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1935
2109
  const document = await documentManager2.findOne(id, model, {
1936
2110
  populate,
1937
2111
  locale,
@@ -1946,14 +2120,14 @@ const collectionTypes = {
1946
2120
  ctx.body = await async.pipe(
1947
2121
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1948
2122
  permissionChecker2.sanitizeOutput,
1949
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2123
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1950
2124
  )(document);
1951
2125
  },
1952
2126
  async bulkDelete(ctx) {
1953
2127
  const { userAbility } = ctx.state;
1954
2128
  const { model } = ctx.params;
1955
2129
  const { query, body } = ctx.request;
1956
- const { ids } = body;
2130
+ const { documentIds } = body;
1957
2131
  await validateBulkActionInput(body);
1958
2132
  const documentManager2 = getService$1("document-manager");
1959
2133
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1961,14 +2135,22 @@ const collectionTypes = {
1961
2135
  return ctx.forbidden();
1962
2136
  }
1963
2137
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1964
- const idsWhereClause = { id: { $in: ids } };
1965
- const params = {
1966
- ...permissionQuery,
1967
- filters: {
1968
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2138
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2139
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2140
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2141
+ populate,
2142
+ locale
2143
+ });
2144
+ if (documentLocales.length === 0) {
2145
+ return ctx.notFound();
2146
+ }
2147
+ for (const document of documentLocales) {
2148
+ if (permissionChecker2.cannot.delete(document)) {
2149
+ return ctx.forbidden();
1969
2150
  }
1970
- };
1971
- const { count } = await documentManager2.deleteMany(params, model);
2151
+ }
2152
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2153
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1972
2154
  ctx.body = { count };
1973
2155
  },
1974
2156
  async countDraftRelations(ctx) {
@@ -1981,7 +2163,7 @@ const collectionTypes = {
1981
2163
  }
1982
2164
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1983
2165
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1984
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2166
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1985
2167
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1986
2168
  if (!entity) {
1987
2169
  return ctx.notFound();
@@ -1996,7 +2178,7 @@ const collectionTypes = {
1996
2178
  },
1997
2179
  async countManyEntriesDraftRelations(ctx) {
1998
2180
  const { userAbility } = ctx.state;
1999
- const ids = ctx.request.query.ids;
2181
+ const ids = ctx.request.query.documentIds;
2000
2182
  const locale = ctx.request.query.locale;
2001
2183
  const { model } = ctx.params;
2002
2184
  const documentManager2 = getService$1("document-manager");
@@ -2004,16 +2186,16 @@ const collectionTypes = {
2004
2186
  if (permissionChecker2.cannot.read()) {
2005
2187
  return ctx.forbidden();
2006
2188
  }
2007
- const entities = await documentManager2.findMany(
2189
+ const documents = await documentManager2.findMany(
2008
2190
  {
2009
2191
  filters: {
2010
- id: ids
2192
+ documentId: ids
2011
2193
  },
2012
2194
  locale
2013
2195
  },
2014
2196
  model
2015
2197
  );
2016
- if (!entities) {
2198
+ if (!documents) {
2017
2199
  return ctx.notFound();
2018
2200
  }
2019
2201
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2204,32 +2386,37 @@ const sanitizeMainField = (model, mainField, userAbility) => {
2204
2386
  userAbility,
2205
2387
  model: model.uid
2206
2388
  });
2207
- if (!isListable(model, mainField)) {
2389
+ const isMainFieldListable = isListable(model, mainField);
2390
+ const canReadMainField = permissionChecker2.can.read(null, mainField);
2391
+ if (!isMainFieldListable || !canReadMainField) {
2208
2392
  return "id";
2209
2393
  }
2210
- if (permissionChecker2.cannot.read(null, mainField)) {
2211
- if (model.uid === "plugin::users-permissions.role") {
2212
- const userPermissionChecker = getService$1("permission-checker").create({
2213
- userAbility,
2214
- model: "plugin::users-permissions.user"
2215
- });
2216
- if (userPermissionChecker.can.read()) {
2217
- return "name";
2218
- }
2219
- }
2220
- return "id";
2394
+ if (model.uid === "plugin::users-permissions.role") {
2395
+ return "name";
2221
2396
  }
2222
2397
  return mainField;
2223
2398
  };
2224
- const addStatusToRelations = async (uid2, relations2) => {
2225
- if (!contentTypes$1.hasDraftAndPublish(strapi.contentTypes[uid2])) {
2399
+ const addStatusToRelations = async (targetUid, relations2) => {
2400
+ if (!contentTypes$1.hasDraftAndPublish(strapi.getModel(targetUid))) {
2226
2401
  return relations2;
2227
2402
  }
2228
2403
  const documentMetadata2 = getService$1("document-metadata");
2229
- const documentsAvailableStatus = await documentMetadata2.getManyAvailableStatus(uid2, relations2);
2404
+ if (!relations2.length) {
2405
+ return relations2;
2406
+ }
2407
+ const firstRelation = relations2[0];
2408
+ const filters = {
2409
+ documentId: { $in: relations2.map((r) => r.documentId) },
2410
+ // NOTE: find the "opposite" status
2411
+ publishedAt: firstRelation.publishedAt !== null ? { $null: true } : { $notNull: true }
2412
+ };
2413
+ const availableStatus = await strapi.query(targetUid).findMany({
2414
+ select: ["id", "documentId", "locale", "updatedAt", "createdAt", "publishedAt"],
2415
+ filters
2416
+ });
2230
2417
  return relations2.map((relation) => {
2231
- const availableStatuses = documentsAvailableStatus.filter(
2232
- (availableDocument) => availableDocument.documentId === relation.documentId
2418
+ const availableStatuses = availableStatus.filter(
2419
+ (availableDocument) => availableDocument.documentId === relation.documentId && (relation.locale ? availableDocument.locale === relation.locale : true)
2233
2420
  );
2234
2421
  return {
2235
2422
  ...relation,
@@ -2250,11 +2437,8 @@ const validateLocale = (sourceUid, targetUid, locale) => {
2250
2437
  const isLocalized = strapi.plugin("i18n").service("content-types").isLocalizedContentType;
2251
2438
  const isSourceLocalized = isLocalized(sourceModel);
2252
2439
  const isTargetLocalized = isLocalized(targetModel);
2253
- let validatedLocale = locale;
2254
- if (!targetModel || !isTargetLocalized)
2255
- validatedLocale = void 0;
2256
2440
  return {
2257
- locale: validatedLocale,
2441
+ locale,
2258
2442
  isSourceLocalized,
2259
2443
  isTargetLocalized
2260
2444
  };
@@ -2357,7 +2541,7 @@ const relations = {
2357
2541
  attribute,
2358
2542
  fieldsToSelect,
2359
2543
  mainField,
2360
- source: { schema: sourceSchema },
2544
+ source: { schema: sourceSchema, isLocalized: isSourceLocalized },
2361
2545
  target: { schema: targetSchema, isLocalized: isTargetLocalized },
2362
2546
  sourceSchema,
2363
2547
  targetSchema,
@@ -2379,7 +2563,8 @@ const relations = {
2379
2563
  fieldsToSelect,
2380
2564
  mainField,
2381
2565
  source: {
2382
- schema: { uid: sourceUid, modelType: sourceModelType }
2566
+ schema: { uid: sourceUid, modelType: sourceModelType },
2567
+ isLocalized: isSourceLocalized
2383
2568
  },
2384
2569
  target: {
2385
2570
  schema: { uid: targetUid },
@@ -2417,12 +2602,16 @@ const relations = {
2417
2602
  } else {
2418
2603
  where.id = id;
2419
2604
  }
2420
- if (status) {
2421
- where[`${alias}.published_at`] = getPublishedAtClause(status, targetUid);
2605
+ const publishedAt = getPublishedAtClause(status, targetUid);
2606
+ if (!isEmpty(publishedAt)) {
2607
+ where[`${alias}.published_at`] = publishedAt;
2422
2608
  }
2423
- if (filterByLocale) {
2609
+ if (isTargetLocalized && locale) {
2424
2610
  where[`${alias}.locale`] = locale;
2425
2611
  }
2612
+ if (isSourceLocalized && locale) {
2613
+ where.locale = locale;
2614
+ }
2426
2615
  if ((idsToInclude?.length ?? 0) !== 0) {
2427
2616
  where[`${alias}.id`].$notIn = idsToInclude;
2428
2617
  }
@@ -2440,7 +2629,8 @@ const relations = {
2440
2629
  id: { $notIn: uniq(idsToOmit) }
2441
2630
  });
2442
2631
  }
2443
- const res = await strapi.db.query(targetUid).findPage(strapi.get("query-params").transform(targetUid, queryParams));
2632
+ const dbQuery = strapi.get("query-params").transform(targetUid, queryParams);
2633
+ const res = await strapi.db.query(targetUid).findPage(dbQuery);
2444
2634
  ctx.body = {
2445
2635
  ...res,
2446
2636
  results: await addStatusToRelations(targetUid, res.results)
@@ -2455,29 +2645,39 @@ const relations = {
2455
2645
  attribute,
2456
2646
  targetField,
2457
2647
  fieldsToSelect,
2458
- source: {
2459
- schema: { uid: sourceUid }
2460
- },
2461
- target: {
2462
- schema: { uid: targetUid }
2463
- }
2648
+ status,
2649
+ source: { schema: sourceSchema },
2650
+ target: { schema: targetSchema }
2464
2651
  } = await this.extractAndValidateRequestInfo(ctx, id);
2652
+ const { uid: sourceUid } = sourceSchema;
2653
+ const { uid: targetUid } = targetSchema;
2465
2654
  const permissionQuery = await getService$1("permission-checker").create({ userAbility, model: targetUid }).sanitizedQuery.read({ fields: fieldsToSelect });
2466
2655
  const dbQuery = strapi.db.query(sourceUid);
2467
2656
  const loadRelations = relations$1.isAnyToMany(attribute) ? (...args) => dbQuery.loadPages(...args) : (...args) => dbQuery.load(...args).then((res2) => ({ results: res2 ? [res2] : [] }));
2657
+ const filters = {};
2658
+ if (sourceSchema?.options?.draftAndPublish) {
2659
+ if (targetSchema?.options?.draftAndPublish) {
2660
+ if (status === "published") {
2661
+ filters.publishedAt = { $notNull: true };
2662
+ } else {
2663
+ filters.publishedAt = { $null: true };
2664
+ }
2665
+ }
2666
+ } else if (targetSchema?.options?.draftAndPublish) {
2667
+ filters.publishedAt = { $null: true };
2668
+ }
2468
2669
  const res = await loadRelations({ id: entryId }, targetField, {
2469
- select: ["id", "documentId", "locale", "publishedAt"],
2670
+ select: ["id", "documentId", "locale", "publishedAt", "updatedAt"],
2470
2671
  ordering: "desc",
2471
2672
  page: ctx.request.query.page,
2472
- pageSize: ctx.request.query.pageSize
2673
+ pageSize: ctx.request.query.pageSize,
2674
+ filters
2473
2675
  });
2474
2676
  const loadedIds = res.results.map((item) => item.id);
2475
2677
  addFiltersClause(permissionQuery, { id: { $in: loadedIds } });
2476
2678
  const sanitizedRes = await loadRelations({ id: entryId }, targetField, {
2477
2679
  ...strapi.get("query-params").transform(targetUid, permissionQuery),
2478
- ordering: "desc",
2479
- page: ctx.request.query.page,
2480
- pageSize: ctx.request.query.pageSize
2680
+ ordering: "desc"
2481
2681
  });
2482
2682
  const relationsUnion = uniqBy("id", concat(sanitizedRes.results, res.results));
2483
2683
  ctx.body = {
@@ -2509,7 +2709,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2509
2709
  throw new errors.ForbiddenError();
2510
2710
  }
2511
2711
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2512
- const { locale } = getDocumentLocaleAndStatus(body);
2712
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2513
2713
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2514
2714
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2515
2715
  // Find the first document to check if it exists
@@ -2546,12 +2746,11 @@ const singleTypes = {
2546
2746
  const { model } = ctx.params;
2547
2747
  const { query = {} } = ctx.request;
2548
2748
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2549
- const documentMetadata2 = getService$1("document-metadata");
2550
2749
  if (permissionChecker2.cannot.read()) {
2551
2750
  return ctx.forbidden();
2552
2751
  }
2553
2752
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2554
- const { locale, status } = getDocumentLocaleAndStatus(query);
2753
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2555
2754
  const version = await findDocument(permissionQuery, model, { locale, status });
2556
2755
  if (!version) {
2557
2756
  if (permissionChecker2.cannot.create()) {
@@ -2561,9 +2760,11 @@ const singleTypes = {
2561
2760
  if (!document) {
2562
2761
  return ctx.notFound();
2563
2762
  }
2564
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2763
+ const { meta } = await formatDocumentWithMetadata(
2764
+ permissionChecker2,
2565
2765
  model,
2566
- { id: document.documentId, locale, publishedAt: null },
2766
+ // @ts-expect-error - fix types
2767
+ { documentId: document.documentId, locale, publishedAt: null },
2567
2768
  { availableLocales: true, availableStatus: false }
2568
2769
  );
2569
2770
  ctx.body = { data: {}, meta };
@@ -2573,16 +2774,15 @@ const singleTypes = {
2573
2774
  return ctx.forbidden();
2574
2775
  }
2575
2776
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2576
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2777
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2577
2778
  },
2578
2779
  async createOrUpdate(ctx) {
2579
2780
  const { userAbility } = ctx.state;
2580
2781
  const { model } = ctx.params;
2581
- const documentMetadata2 = getService$1("document-metadata");
2582
2782
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2583
2783
  const document = await createOrUpdateDocument(ctx);
2584
2784
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2585
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2785
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2586
2786
  },
2587
2787
  async delete(ctx) {
2588
2788
  const { userAbility } = ctx.state;
@@ -2595,7 +2795,7 @@ const singleTypes = {
2595
2795
  }
2596
2796
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2597
2797
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2598
- const { locale } = getDocumentLocaleAndStatus(query);
2798
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2599
2799
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2600
2800
  populate,
2601
2801
  locale
@@ -2618,7 +2818,6 @@ const singleTypes = {
2618
2818
  const { model } = ctx.params;
2619
2819
  const { query = {} } = ctx.request;
2620
2820
  const documentManager2 = getService$1("document-manager");
2621
- const documentMetadata2 = getService$1("document-metadata");
2622
2821
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2623
2822
  if (permissionChecker2.cannot.publish()) {
2624
2823
  return ctx.forbidden();
@@ -2633,11 +2832,12 @@ const singleTypes = {
2633
2832
  if (permissionChecker2.cannot.publish(document)) {
2634
2833
  throw new errors.ForbiddenError();
2635
2834
  }
2636
- const { locale } = getDocumentLocaleAndStatus(document);
2637
- return documentManager2.publish(document.documentId, model, { locale });
2835
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2836
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2837
+ return publishResult.at(0);
2638
2838
  });
2639
2839
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2640
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2840
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2641
2841
  },
2642
2842
  async unpublish(ctx) {
2643
2843
  const { userAbility } = ctx.state;
@@ -2647,7 +2847,6 @@ const singleTypes = {
2647
2847
  query = {}
2648
2848
  } = ctx.request;
2649
2849
  const documentManager2 = getService$1("document-manager");
2650
- const documentMetadata2 = getService$1("document-metadata");
2651
2850
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2652
2851
  if (permissionChecker2.cannot.unpublish()) {
2653
2852
  return ctx.forbidden();
@@ -2656,7 +2855,7 @@ const singleTypes = {
2656
2855
  return ctx.forbidden();
2657
2856
  }
2658
2857
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2659
- const { locale } = getDocumentLocaleAndStatus(body);
2858
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2660
2859
  const document = await findDocument(sanitizedQuery, model, { locale });
2661
2860
  if (!document) {
2662
2861
  return ctx.notFound();
@@ -2674,7 +2873,7 @@ const singleTypes = {
2674
2873
  ctx.body = await async.pipe(
2675
2874
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2676
2875
  permissionChecker2.sanitizeOutput,
2677
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2876
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2678
2877
  )(document);
2679
2878
  });
2680
2879
  },
@@ -2683,13 +2882,12 @@ const singleTypes = {
2683
2882
  const { model } = ctx.params;
2684
2883
  const { body, query = {} } = ctx.request;
2685
2884
  const documentManager2 = getService$1("document-manager");
2686
- const documentMetadata2 = getService$1("document-metadata");
2687
2885
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2688
2886
  if (permissionChecker2.cannot.discard()) {
2689
2887
  return ctx.forbidden();
2690
2888
  }
2691
2889
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2692
- const { locale } = getDocumentLocaleAndStatus(body);
2890
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2693
2891
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2694
2892
  if (!document) {
2695
2893
  return ctx.notFound();
@@ -2700,7 +2898,7 @@ const singleTypes = {
2700
2898
  ctx.body = await async.pipe(
2701
2899
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2702
2900
  permissionChecker2.sanitizeOutput,
2703
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2901
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2704
2902
  )(document);
2705
2903
  },
2706
2904
  async countDraftRelations(ctx) {
@@ -2709,7 +2907,7 @@ const singleTypes = {
2709
2907
  const { query } = ctx.request;
2710
2908
  const documentManager2 = getService$1("document-manager");
2711
2909
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2712
- const { locale } = getDocumentLocaleAndStatus(query);
2910
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2713
2911
  if (permissionChecker2.cannot.read()) {
2714
2912
  return ctx.forbidden();
2715
2913
  }
@@ -2730,7 +2928,7 @@ const uid$1 = {
2730
2928
  async generateUID(ctx) {
2731
2929
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2732
2930
  const { query = {} } = ctx.request;
2733
- const { locale } = getDocumentLocaleAndStatus(query);
2931
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2734
2932
  await validateUIDField(contentTypeUID, field);
2735
2933
  const uidService = getService$1("uid");
2736
2934
  ctx.body = {
@@ -2742,7 +2940,7 @@ const uid$1 = {
2742
2940
  ctx.request.body
2743
2941
  );
2744
2942
  const { query = {} } = ctx.request;
2745
- const { locale } = getDocumentLocaleAndStatus(query);
2943
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2746
2944
  await validateUIDField(contentTypeUID, field);
2747
2945
  const uidService = getService$1("uid");
2748
2946
  const isAvailable = await uidService.checkUIDAvailability({
@@ -2765,7 +2963,8 @@ const controllers = {
2765
2963
  relations,
2766
2964
  "single-types": singleTypes,
2767
2965
  uid: uid$1,
2768
- ...history.controllers ? history.controllers : {}
2966
+ ...history.controllers ? history.controllers : {},
2967
+ ...preview.controllers ? preview.controllers : {}
2769
2968
  };
2770
2969
  const keys = {
2771
2970
  CONFIGURATION: "configuration"
@@ -3385,12 +3584,27 @@ const createPermissionChecker = (strapi2) => ({ userAbility, model }) => {
3385
3584
  ability: userAbility,
3386
3585
  model
3387
3586
  });
3388
- const toSubject = (entity) => entity ? permissionsManager.toSubject(entity, model) : model;
3587
+ const { actionProvider } = strapi2.service("admin::permission");
3588
+ const toSubject = (entity) => {
3589
+ return entity ? permissionsManager.toSubject(entity, model) : model;
3590
+ };
3389
3591
  const can = (action, entity, field) => {
3390
- return userAbility.can(action, toSubject(entity), field);
3592
+ const subject = toSubject(entity);
3593
+ const aliases = actionProvider.unstable_aliases(action, model);
3594
+ return (
3595
+ // Test the original action to see if it passes
3596
+ userAbility.can(action, subject, field) || // Else try every known alias if at least one of them succeed, then the user "can"
3597
+ aliases.some((alias) => userAbility.can(alias, subject, field))
3598
+ );
3391
3599
  };
3392
3600
  const cannot = (action, entity, field) => {
3393
- return userAbility.cannot(action, toSubject(entity), field);
3601
+ const subject = toSubject(entity);
3602
+ const aliases = actionProvider.unstable_aliases(action, model);
3603
+ return (
3604
+ // Test both the original action
3605
+ userAbility.cannot(action, subject, field) && // and every known alias, if all of them fail (cannot), then the user truly "cannot"
3606
+ aliases.every((alias) => userAbility.cannot(alias, subject, field))
3607
+ );
3394
3608
  };
3395
3609
  const sanitizeOutput = (data, { action = ACTIONS.read } = {}) => {
3396
3610
  return permissionsManager.sanitizeOutput(data, { subject: toSubject(data), action });
@@ -3533,7 +3747,7 @@ const permission = ({ strapi: strapi2 }) => ({
3533
3747
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3534
3748
  }
3535
3749
  });
3536
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3750
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3537
3751
  const { isAnyToMany } = strapiUtils.relations;
3538
3752
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3539
3753
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3624,6 +3838,42 @@ const getDeepPopulate = (uid2, {
3624
3838
  {}
3625
3839
  );
3626
3840
  };
3841
+ const getValidatableFieldsPopulate = (uid2, {
3842
+ initialPopulate = {},
3843
+ countMany = false,
3844
+ countOne = false,
3845
+ maxLevel = Infinity
3846
+ } = {}, level = 1) => {
3847
+ if (level > maxLevel) {
3848
+ return {};
3849
+ }
3850
+ const model = strapi.getModel(uid2);
3851
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3852
+ if (!getDoesAttributeRequireValidation(attribute)) {
3853
+ return populateAcc;
3854
+ }
3855
+ if (isScalarAttribute(attribute)) {
3856
+ return merge(populateAcc, {
3857
+ [attributeName]: true
3858
+ });
3859
+ }
3860
+ return merge(
3861
+ populateAcc,
3862
+ getPopulateFor(
3863
+ attributeName,
3864
+ model,
3865
+ {
3866
+ // @ts-expect-error - improve types
3867
+ initialPopulate: initialPopulate?.[attributeName],
3868
+ countMany,
3869
+ countOne,
3870
+ maxLevel
3871
+ },
3872
+ level
3873
+ )
3874
+ );
3875
+ }, {});
3876
+ };
3627
3877
  const getDeepPopulateDraftCount = (uid2) => {
3628
3878
  const model = strapi.getModel(uid2);
3629
3879
  let hasRelations = false;
@@ -3631,6 +3881,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3631
3881
  const attribute = model.attributes[attributeName];
3632
3882
  switch (attribute.type) {
3633
3883
  case "relation": {
3884
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3885
+ if (isMorphRelation) {
3886
+ break;
3887
+ }
3634
3888
  if (isVisibleAttribute$1(model, attributeName)) {
3635
3889
  populateAcc[attributeName] = {
3636
3890
  count: true,
@@ -3645,22 +3899,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3645
3899
  attribute.component
3646
3900
  );
3647
3901
  if (childHasRelations) {
3648
- populateAcc[attributeName] = { populate: populate2 };
3902
+ populateAcc[attributeName] = {
3903
+ populate: populate2
3904
+ };
3649
3905
  hasRelations = true;
3650
3906
  }
3651
3907
  break;
3652
3908
  }
3653
3909
  case "dynamiczone": {
3654
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3655
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3656
- if (childHasRelations) {
3910
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3911
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3912
+ if (componentHasRelations) {
3657
3913
  hasRelations = true;
3658
- return merge(acc, populate2);
3914
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3659
3915
  }
3660
3916
  return acc;
3661
3917
  }, {});
3662
- if (!isEmpty(dzPopulate)) {
3663
- populateAcc[attributeName] = { populate: dzPopulate };
3918
+ if (!isEmpty(dzPopulateFragment)) {
3919
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3664
3920
  }
3665
3921
  break;
3666
3922
  }
@@ -3852,41 +4108,72 @@ const AVAILABLE_STATUS_FIELDS = [
3852
4108
  "updatedBy",
3853
4109
  "status"
3854
4110
  ];
3855
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4111
+ const AVAILABLE_LOCALES_FIELDS = [
4112
+ "id",
4113
+ "locale",
4114
+ "updatedAt",
4115
+ "createdAt",
4116
+ "status",
4117
+ "publishedAt",
4118
+ "documentId"
4119
+ ];
3856
4120
  const CONTENT_MANAGER_STATUS = {
3857
4121
  PUBLISHED: "published",
3858
4122
  DRAFT: "draft",
3859
4123
  MODIFIED: "modified"
3860
4124
  };
3861
- const areDatesEqual = (date1, date2, threshold) => {
3862
- if (!date1 || !date2) {
4125
+ const getIsVersionLatestModification = (version, otherVersion) => {
4126
+ if (!version || !version.updatedAt) {
3863
4127
  return false;
3864
4128
  }
3865
- const time1 = new Date(date1).getTime();
3866
- const time2 = new Date(date2).getTime();
3867
- const difference2 = Math.abs(time1 - time2);
3868
- return difference2 <= threshold;
4129
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4130
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4131
+ return versionUpdatedAt > otherUpdatedAt;
3869
4132
  };
3870
4133
  const documentMetadata = ({ strapi: strapi2 }) => ({
3871
4134
  /**
3872
4135
  * Returns available locales of a document for the current status
3873
4136
  */
3874
- getAvailableLocales(uid2, version, allVersions) {
4137
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3875
4138
  const versionsByLocale = groupBy("locale", allVersions);
3876
- delete versionsByLocale[version.locale];
3877
- return Object.values(versionsByLocale).map((localeVersions) => {
3878
- if (!contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2))) {
3879
- return pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
4139
+ if (version.locale) {
4140
+ delete versionsByLocale[version.locale];
4141
+ }
4142
+ const model = strapi2.getModel(uid2);
4143
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4144
+ const traversalFunction = async (localeVersion) => traverseEntity(
4145
+ ({ key }, { remove }) => {
4146
+ if (keysToKeep.includes(key)) {
4147
+ return;
4148
+ }
4149
+ remove(key);
4150
+ },
4151
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4152
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4153
+ localeVersion
4154
+ );
4155
+ const mappingResult = await async.map(
4156
+ Object.values(versionsByLocale),
4157
+ async (localeVersions) => {
4158
+ const mappedLocaleVersions = await async.map(
4159
+ localeVersions,
4160
+ traversalFunction
4161
+ );
4162
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
4163
+ return mappedLocaleVersions[0];
4164
+ }
4165
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4166
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4167
+ if (!draftVersion) {
4168
+ return;
4169
+ }
4170
+ return {
4171
+ ...draftVersion,
4172
+ status: this.getStatus(draftVersion, otherVersions)
4173
+ };
3880
4174
  }
3881
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3882
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3883
- if (!draftVersion)
3884
- return;
3885
- return {
3886
- ...pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3887
- status: this.getStatus(draftVersion, otherVersions)
3888
- };
3889
- }).filter(Boolean);
4175
+ );
4176
+ return mappingResult.filter(Boolean);
3890
4177
  },
3891
4178
  /**
3892
4179
  * Returns available status of a document for the current locale
@@ -3924,26 +4211,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3924
4211
  });
3925
4212
  },
3926
4213
  getStatus(version, otherDocumentStatuses) {
3927
- const isDraft = version.publishedAt === null;
3928
- if (!otherDocumentStatuses?.length) {
3929
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4214
+ let draftVersion;
4215
+ let publishedVersion;
4216
+ if (version.publishedAt) {
4217
+ publishedVersion = version;
4218
+ } else {
4219
+ draftVersion = version;
3930
4220
  }
3931
- if (isDraft) {
3932
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3933
- if (!publishedVersion) {
3934
- return CONTENT_MANAGER_STATUS.DRAFT;
3935
- }
4221
+ const otherVersion = otherDocumentStatuses?.at(0);
4222
+ if (otherVersion?.publishedAt) {
4223
+ publishedVersion = otherVersion;
4224
+ } else if (otherVersion) {
4225
+ draftVersion = otherVersion;
3936
4226
  }
3937
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4227
+ if (!draftVersion)
3938
4228
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3939
- }
3940
- return CONTENT_MANAGER_STATUS.MODIFIED;
4229
+ if (!publishedVersion)
4230
+ return CONTENT_MANAGER_STATUS.DRAFT;
4231
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4232
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3941
4233
  },
4234
+ // TODO is it necessary to return metadata on every page of the CM
4235
+ // We could refactor this so the locales are only loaded when they're
4236
+ // needed. e.g. in the bulk locale action modal.
3942
4237
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4238
+ const populate = getValidatableFieldsPopulate(uid2);
3943
4239
  const versions = await strapi2.db.query(uid2).findMany({
3944
4240
  where: { documentId: version.documentId },
3945
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3946
4241
  populate: {
4242
+ // Populate only fields that require validation for bulk locale actions
4243
+ ...populate,
4244
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3947
4245
  createdBy: {
3948
4246
  select: ["id", "firstname", "lastname", "email"]
3949
4247
  },
@@ -3952,7 +4250,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3952
4250
  }
3953
4251
  }
3954
4252
  });
3955
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4253
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3956
4254
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3957
4255
  return {
3958
4256
  availableLocales: availableLocalesResult,
@@ -3965,8 +4263,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3965
4263
  * - Available status of the document for the current locale
3966
4264
  */
3967
4265
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3968
- if (!document)
3969
- return document;
4266
+ if (!document) {
4267
+ return {
4268
+ data: document,
4269
+ meta: {
4270
+ availableLocales: [],
4271
+ availableStatus: []
4272
+ }
4273
+ };
4274
+ }
3970
4275
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3971
4276
  if (!hasDraftAndPublish) {
3972
4277
  opts.availableStatus = false;
@@ -4016,26 +4321,9 @@ const sumDraftCounts = (entity, uid2) => {
4016
4321
  }, 0);
4017
4322
  };
4018
4323
  const { ApplicationError } = errors;
4019
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
4020
4324
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
4021
4325
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
4022
4326
  const omitIdField = omit("id");
4023
- const emitEvent = async (uid2, event, document) => {
4024
- const modelDef = strapi.getModel(uid2);
4025
- const sanitizedDocument = await sanitize.sanitizers.defaultSanitizeOutput(
4026
- {
4027
- schema: modelDef,
4028
- getModel(uid22) {
4029
- return strapi.getModel(uid22);
4030
- }
4031
- },
4032
- document
4033
- );
4034
- strapi.eventHub.emit(event, {
4035
- model: modelDef.modelName,
4036
- entry: sanitizedDocument
4037
- });
4038
- };
4039
4327
  const documentManager = ({ strapi: strapi2 }) => {
4040
4328
  return {
4041
4329
  async findOne(id, uid2, opts = {}) {
@@ -4054,6 +4342,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4054
4342
  } else if (opts.locale && opts.locale !== "*") {
4055
4343
  where.locale = opts.locale;
4056
4344
  }
4345
+ if (typeof opts.isPublished === "boolean") {
4346
+ where.publishedAt = { $notNull: opts.isPublished };
4347
+ }
4057
4348
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4058
4349
  },
4059
4350
  async findMany(opts, uid2) {
@@ -4087,10 +4378,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4087
4378
  async clone(id, body, uid2) {
4088
4379
  const populate = await buildDeepPopulate(uid2);
4089
4380
  const params = {
4090
- data: {
4091
- ...omitIdField(body),
4092
- [PUBLISHED_AT_ATTRIBUTE]: null
4093
- },
4381
+ data: omitIdField(body),
4094
4382
  populate
4095
4383
  };
4096
4384
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4116,70 +4404,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4116
4404
  return {};
4117
4405
  },
4118
4406
  // FIXME: handle relations
4119
- async deleteMany(opts, uid2) {
4120
- const docs = await strapi2.documents(uid2).findMany(opts);
4121
- for (const doc of docs) {
4122
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4123
- }
4124
- return { count: docs.length };
4407
+ async deleteMany(documentIds, uid2, opts = {}) {
4408
+ const deletedEntries = await strapi2.db.transaction(async () => {
4409
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4410
+ });
4411
+ return { count: deletedEntries.length };
4125
4412
  },
4126
4413
  async publish(id, uid2, opts = {}) {
4127
4414
  const populate = await buildDeepPopulate(uid2);
4128
4415
  const params = { ...opts, populate };
4129
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4416
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4130
4417
  },
4131
- async publishMany(entities, uid2) {
4132
- if (!entities.length) {
4133
- return null;
4134
- }
4135
- await Promise.all(
4136
- entities.map((document) => {
4137
- return strapi2.entityValidator.validateEntityCreation(
4138
- strapi2.getModel(uid2),
4139
- document,
4140
- void 0,
4141
- // @ts-expect-error - FIXME: entity here is unnecessary
4142
- document
4143
- );
4144
- })
4145
- );
4146
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4147
- const filters = { id: { $in: entitiesToPublish } };
4148
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4149
- const populate = await buildDeepPopulate(uid2);
4150
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4151
- where: filters,
4152
- data
4153
- });
4154
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4155
- where: filters,
4156
- populate
4418
+ async publishMany(uid2, documentIds, locale) {
4419
+ return strapi2.db.transaction(async () => {
4420
+ const results = await Promise.all(
4421
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4422
+ );
4423
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4424
+ return publishedEntitiesCount;
4157
4425
  });
4158
- await Promise.all(
4159
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4160
- );
4161
- return publishedEntitiesCount;
4162
4426
  },
4163
- async unpublishMany(documents, uid2) {
4164
- if (!documents.length) {
4165
- return null;
4166
- }
4167
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4168
- const filters = { id: { $in: entitiesToUnpublish } };
4169
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4170
- const populate = await buildDeepPopulate(uid2);
4171
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4172
- where: filters,
4173
- data
4174
- });
4175
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4176
- where: filters,
4177
- populate
4427
+ async unpublishMany(documentIds, uid2, opts = {}) {
4428
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4429
+ return Promise.all(
4430
+ documentIds.map(
4431
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4432
+ )
4433
+ );
4178
4434
  });
4179
- await Promise.all(
4180
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4181
- );
4182
- return unpublishedEntitiesCount;
4435
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4436
+ return { count: unpublishedEntitiesCount };
4183
4437
  },
4184
4438
  async unpublish(id, uid2, opts = {}) {
4185
4439
  const populate = await buildDeepPopulate(uid2);
@@ -4204,16 +4458,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4204
4458
  }
4205
4459
  return sumDraftCounts(document, uid2);
4206
4460
  },
4207
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4461
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4208
4462
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4209
4463
  if (!hasRelations) {
4210
4464
  return 0;
4211
4465
  }
4466
+ let localeFilter = {};
4467
+ if (locale) {
4468
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4469
+ }
4212
4470
  const entities = await strapi2.db.query(uid2).findMany({
4213
4471
  populate,
4214
4472
  where: {
4215
- id: { $in: ids },
4216
- ...locale ? { locale } : {}
4473
+ documentId: { $in: documentIds },
4474
+ ...localeFilter
4217
4475
  }
4218
4476
  });
4219
4477
  const totalNumberDraftRelations = entities.reduce(
@@ -4236,7 +4494,8 @@ const services = {
4236
4494
  permission,
4237
4495
  "populate-builder": populateBuilder$1,
4238
4496
  uid,
4239
- ...history.services ? history.services : {}
4497
+ ...history.services ? history.services : {},
4498
+ ...preview.services ? preview.services : {}
4240
4499
  };
4241
4500
  const index = () => {
4242
4501
  return {