@strapi/content-manager 0.0.0-experimental.a65a85fdea97faae8679d3ffc5f9d79af61abd26 → 0.0.0-experimental.a6728ad43ac70ae19dabb624dbfca1f2d9610a86

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 (189) 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--2aLCv-G.mjs → ComponentConfigurationPage-DJ5voqEK.mjs} +3 -3
  7. package/dist/_chunks/{ComponentConfigurationPage--2aLCv-G.mjs.map → ComponentConfigurationPage-DJ5voqEK.mjs.map} +1 -1
  8. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js → ComponentConfigurationPage-_6osrv39.js} +3 -3
  9. package/dist/_chunks/{ComponentConfigurationPage-43KmCNQE.js.map → ComponentConfigurationPage-_6osrv39.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-CUcGHHvQ.mjs → EditConfigurationPage-CZofxSLy.mjs} +3 -3
  15. package/dist/_chunks/{EditConfigurationPage-CUcGHHvQ.mjs.map → EditConfigurationPage-CZofxSLy.mjs.map} +1 -1
  16. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js → EditConfigurationPage-ZN3s568V.js} +3 -3
  17. package/dist/_chunks/{EditConfigurationPage-BfFzJ4Br.js.map → EditConfigurationPage-ZN3s568V.js.map} +1 -1
  18. package/dist/_chunks/{EditViewPage-CzOT5Kpj.js → EditViewPage-Co2IKQZH.js} +58 -49
  19. package/dist/_chunks/EditViewPage-Co2IKQZH.js.map +1 -0
  20. package/dist/_chunks/{EditViewPage-Bm8lgcm6.mjs → EditViewPage-HYljoEY7.mjs} +59 -48
  21. package/dist/_chunks/EditViewPage-HYljoEY7.mjs.map +1 -0
  22. package/dist/_chunks/{Field-Dlh0uGnL.mjs → Field-BOPUMZ1u.mjs} +995 -795
  23. package/dist/_chunks/Field-BOPUMZ1u.mjs.map +1 -0
  24. package/dist/_chunks/{Field-Caef4JjM.js → Field-G9CkFUtP.js} +1041 -842
  25. package/dist/_chunks/Field-G9CkFUtP.js.map +1 -0
  26. package/dist/_chunks/{Form-EnaQL_6L.mjs → Form-CDwNp7pU.mjs} +69 -48
  27. package/dist/_chunks/Form-CDwNp7pU.mjs.map +1 -0
  28. package/dist/_chunks/{Form-BzuAjtRq.js → Form-crsbkGxI.js} +68 -48
  29. package/dist/_chunks/Form-crsbkGxI.js.map +1 -0
  30. package/dist/_chunks/{History-D6sbCJvo.mjs → History-BDZrgfZ3.mjs} +151 -57
  31. package/dist/_chunks/History-BDZrgfZ3.mjs.map +1 -0
  32. package/dist/_chunks/{History-C17LiyRg.js → History-CWcM9HnW.js} +151 -58
  33. package/dist/_chunks/History-CWcM9HnW.js.map +1 -0
  34. package/dist/_chunks/{ListConfigurationPage-Ce4qs7qE.mjs → ListConfigurationPage-BZ3ScUna.mjs} +67 -57
  35. package/dist/_chunks/ListConfigurationPage-BZ3ScUna.mjs.map +1 -0
  36. package/dist/_chunks/{ListConfigurationPage-Dks5SX6f.js → ListConfigurationPage-DGzoQD_I.js} +70 -61
  37. package/dist/_chunks/ListConfigurationPage-DGzoQD_I.js.map +1 -0
  38. package/dist/_chunks/{ListViewPage-BwrZrPsh.js → ListViewPage-BBAC9aPu.js} +132 -139
  39. package/dist/_chunks/ListViewPage-BBAC9aPu.js.map +1 -0
  40. package/dist/_chunks/{ListViewPage-Be7S5aKL.mjs → ListViewPage-CsX7tWx-.mjs} +129 -136
  41. package/dist/_chunks/ListViewPage-CsX7tWx-.mjs.map +1 -0
  42. package/dist/_chunks/{NoContentTypePage-Cu5r1-JT.js → NoContentTypePage-CwVDx_YC.js} +5 -5
  43. package/dist/_chunks/NoContentTypePage-CwVDx_YC.js.map +1 -0
  44. package/dist/_chunks/{NoContentTypePage-CIPmYQMm.mjs → NoContentTypePage-LClTUPWs.mjs} +7 -7
  45. package/dist/_chunks/NoContentTypePage-LClTUPWs.mjs.map +1 -0
  46. package/dist/_chunks/{NoPermissionsPage-C-j6TEUF.js → NoPermissionsPage-D2iWw-sn.js} +4 -5
  47. package/dist/_chunks/NoPermissionsPage-D2iWw-sn.js.map +1 -0
  48. package/dist/_chunks/{NoPermissionsPage-DhJ7LYrr.mjs → NoPermissionsPage-S4Re3FwO.mjs} +5 -6
  49. package/dist/_chunks/NoPermissionsPage-S4Re3FwO.mjs.map +1 -0
  50. package/dist/_chunks/{Relations-CY7AtkDA.mjs → Relations-Dmv0Tpe5.mjs} +67 -57
  51. package/dist/_chunks/Relations-Dmv0Tpe5.mjs.map +1 -0
  52. package/dist/_chunks/{Relations-Czs-uZ-s.js → Relations-jwuTFGOV.js} +71 -62
  53. package/dist/_chunks/Relations-jwuTFGOV.js.map +1 -0
  54. package/dist/_chunks/{en-C-V1_90f.js → en-BlhnxQfj.js} +17 -9
  55. package/dist/_chunks/{en-C-V1_90f.js.map → en-BlhnxQfj.js.map} +1 -1
  56. package/dist/_chunks/{en-MBPul9Su.mjs → en-C8YBvRrK.mjs} +17 -9
  57. package/dist/_chunks/{en-MBPul9Su.mjs.map → en-C8YBvRrK.mjs.map} +1 -1
  58. package/dist/_chunks/{index-DNVx8ssZ.mjs → index-BmUAydCA.mjs} +1715 -813
  59. package/dist/_chunks/index-BmUAydCA.mjs.map +1 -0
  60. package/dist/_chunks/{index-X_2tafck.js → index-CBX6KyXv.js} +1815 -914
  61. package/dist/_chunks/index-CBX6KyXv.js.map +1 -0
  62. package/dist/_chunks/{layout-Dnh0PNp9.mjs → layout-ClP-DC72.mjs} +47 -29
  63. package/dist/_chunks/layout-ClP-DC72.mjs.map +1 -0
  64. package/dist/_chunks/{layout-dBc7wN7L.js → layout-CxxkX9jY.js} +47 -31
  65. package/dist/_chunks/layout-CxxkX9jY.js.map +1 -0
  66. package/dist/_chunks/{relations-4pHtBrHJ.js → relations-DIjTADIu.js} +2 -2
  67. package/dist/_chunks/{relations-4pHtBrHJ.js.map → relations-DIjTADIu.js.map} +1 -1
  68. package/dist/_chunks/{relations-Dx7tMKJN.mjs → relations-op89RClB.mjs} +2 -2
  69. package/dist/_chunks/{relations-Dx7tMKJN.mjs.map → relations-op89RClB.mjs.map} +1 -1
  70. package/dist/_chunks/useDebounce-CtcjDB3L.js +28 -0
  71. package/dist/_chunks/useDebounce-CtcjDB3L.js.map +1 -0
  72. package/dist/_chunks/useDebounce-DmuSJIF3.mjs +29 -0
  73. package/dist/_chunks/useDebounce-DmuSJIF3.mjs.map +1 -0
  74. package/dist/_chunks/useDragAndDrop-DdHgKsqq.mjs.map +1 -1
  75. package/dist/_chunks/useDragAndDrop-J0TUUbR6.js.map +1 -1
  76. package/dist/admin/index.js +3 -1
  77. package/dist/admin/index.js.map +1 -1
  78. package/dist/admin/index.mjs +9 -7
  79. package/dist/admin/src/components/ComponentIcon.d.ts +6 -3
  80. package/dist/admin/src/content-manager.d.ts +3 -3
  81. package/dist/admin/src/exports.d.ts +2 -1
  82. package/dist/admin/src/history/components/VersionInputRenderer.d.ts +1 -1
  83. package/dist/admin/src/history/index.d.ts +3 -0
  84. package/dist/admin/src/history/services/historyVersion.d.ts +1 -1
  85. package/dist/admin/src/hooks/useDocument.d.ts +35 -9
  86. package/dist/admin/src/hooks/useDocumentActions.d.ts +24 -3
  87. package/dist/admin/src/hooks/useDocumentLayout.d.ts +2 -2
  88. package/dist/admin/src/hooks/useDragAndDrop.d.ts +4 -4
  89. package/dist/admin/src/hooks/useKeyboardDragAndDrop.d.ts +1 -1
  90. package/dist/admin/src/index.d.ts +1 -0
  91. package/dist/admin/src/pages/EditView/components/DocumentActions.d.ts +11 -4
  92. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/BlocksInput.d.ts +3 -3
  93. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/utils/constants.d.ts +4 -0
  94. package/dist/admin/src/pages/EditView/components/FormInputs/Component/Input.d.ts +2 -2
  95. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/ComponentCategory.d.ts +3 -5
  96. package/dist/admin/src/pages/EditView/components/FormInputs/DynamicZone/Field.d.ts +1 -1
  97. package/dist/admin/src/pages/EditView/components/FormInputs/Relations.d.ts +30 -18
  98. package/dist/admin/src/pages/EditView/components/FormInputs/UID.d.ts +2 -2
  99. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/EditorLayout.d.ts +3 -49
  100. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/Field.d.ts +2 -2
  101. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygFooter.d.ts +2 -2
  102. package/dist/admin/src/pages/EditView/components/FormInputs/Wysiwyg/WysiwygStyles.d.ts +16 -53
  103. package/dist/admin/src/pages/EditView/components/Header.d.ts +10 -11
  104. package/dist/admin/src/pages/EditView/components/InputRenderer.d.ts +2 -10
  105. package/dist/admin/src/pages/ListView/components/BulkActions/Actions.d.ts +3 -30
  106. package/dist/admin/src/pages/ListView/components/BulkActions/ConfirmBulkActionDialog.d.ts +2 -2
  107. package/dist/admin/src/pages/ListView/components/BulkActions/PublishAction.d.ts +9 -26
  108. package/dist/admin/src/services/api.d.ts +2 -3
  109. package/dist/admin/src/services/components.d.ts +2 -2
  110. package/dist/admin/src/services/contentTypes.d.ts +5 -5
  111. package/dist/admin/src/services/documents.d.ts +31 -17
  112. package/dist/admin/src/services/init.d.ts +2 -2
  113. package/dist/admin/src/services/relations.d.ts +3 -3
  114. package/dist/admin/src/services/uid.d.ts +3 -3
  115. package/dist/admin/src/utils/api.d.ts +4 -18
  116. package/dist/admin/src/utils/validation.d.ts +5 -7
  117. package/dist/server/index.js +648 -447
  118. package/dist/server/index.js.map +1 -1
  119. package/dist/server/index.mjs +656 -455
  120. package/dist/server/index.mjs.map +1 -1
  121. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  122. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  123. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  124. package/dist/server/src/controllers/uid.d.ts.map +1 -1
  125. package/dist/server/src/controllers/utils/metadata.d.ts +8 -0
  126. package/dist/server/src/controllers/utils/metadata.d.ts.map +1 -0
  127. package/dist/server/src/controllers/validation/dimensions.d.ts +11 -0
  128. package/dist/server/src/controllers/validation/dimensions.d.ts.map +1 -0
  129. package/dist/server/src/controllers/validation/index.d.ts +1 -1
  130. package/dist/server/src/history/services/history.d.ts +2 -4
  131. package/dist/server/src/history/services/history.d.ts.map +1 -1
  132. package/dist/server/src/history/services/index.d.ts +6 -2
  133. package/dist/server/src/history/services/index.d.ts.map +1 -1
  134. package/dist/server/src/history/services/lifecycles.d.ts +9 -0
  135. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -0
  136. package/dist/server/src/history/services/utils.d.ts +42 -9
  137. package/dist/server/src/history/services/utils.d.ts.map +1 -1
  138. package/dist/server/src/history/utils.d.ts +6 -2
  139. package/dist/server/src/history/utils.d.ts.map +1 -1
  140. package/dist/server/src/index.d.ts +18 -39
  141. package/dist/server/src/index.d.ts.map +1 -1
  142. package/dist/server/src/policies/hasPermissions.d.ts.map +1 -1
  143. package/dist/server/src/services/document-manager.d.ts +13 -12
  144. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  145. package/dist/server/src/services/document-metadata.d.ts +8 -29
  146. package/dist/server/src/services/document-metadata.d.ts.map +1 -1
  147. package/dist/server/src/services/index.d.ts +18 -39
  148. package/dist/server/src/services/index.d.ts.map +1 -1
  149. package/dist/server/src/services/permission-checker.d.ts.map +1 -1
  150. package/dist/server/src/services/utils/populate.d.ts +8 -1
  151. package/dist/server/src/services/utils/populate.d.ts.map +1 -1
  152. package/dist/shared/contracts/collection-types.d.ts +17 -7
  153. package/dist/shared/contracts/collection-types.d.ts.map +1 -1
  154. package/dist/shared/contracts/relations.d.ts +2 -2
  155. package/dist/shared/contracts/relations.d.ts.map +1 -1
  156. package/package.json +16 -17
  157. package/dist/_chunks/CardDragPreview-DSVYodBX.js.map +0 -1
  158. package/dist/_chunks/CardDragPreview-ikSG4M46.mjs.map +0 -1
  159. package/dist/_chunks/ComponentIcon-BBQsYCVn.js.map +0 -1
  160. package/dist/_chunks/ComponentIcon-BOFnK76n.mjs.map +0 -1
  161. package/dist/_chunks/EditViewPage-Bm8lgcm6.mjs.map +0 -1
  162. package/dist/_chunks/EditViewPage-CzOT5Kpj.js.map +0 -1
  163. package/dist/_chunks/Field-Caef4JjM.js.map +0 -1
  164. package/dist/_chunks/Field-Dlh0uGnL.mjs.map +0 -1
  165. package/dist/_chunks/Form-BzuAjtRq.js.map +0 -1
  166. package/dist/_chunks/Form-EnaQL_6L.mjs.map +0 -1
  167. package/dist/_chunks/History-C17LiyRg.js.map +0 -1
  168. package/dist/_chunks/History-D6sbCJvo.mjs.map +0 -1
  169. package/dist/_chunks/ListConfigurationPage-Ce4qs7qE.mjs.map +0 -1
  170. package/dist/_chunks/ListConfigurationPage-Dks5SX6f.js.map +0 -1
  171. package/dist/_chunks/ListViewPage-Be7S5aKL.mjs.map +0 -1
  172. package/dist/_chunks/ListViewPage-BwrZrPsh.js.map +0 -1
  173. package/dist/_chunks/NoContentTypePage-CIPmYQMm.mjs.map +0 -1
  174. package/dist/_chunks/NoContentTypePage-Cu5r1-JT.js.map +0 -1
  175. package/dist/_chunks/NoPermissionsPage-C-j6TEUF.js.map +0 -1
  176. package/dist/_chunks/NoPermissionsPage-DhJ7LYrr.mjs.map +0 -1
  177. package/dist/_chunks/Relations-CY7AtkDA.mjs.map +0 -1
  178. package/dist/_chunks/Relations-Czs-uZ-s.js.map +0 -1
  179. package/dist/_chunks/index-DNVx8ssZ.mjs.map +0 -1
  180. package/dist/_chunks/index-X_2tafck.js.map +0 -1
  181. package/dist/_chunks/layout-Dnh0PNp9.mjs.map +0 -1
  182. package/dist/_chunks/layout-dBc7wN7L.js.map +0 -1
  183. package/dist/_chunks/urls-CbOsUOoW.mjs +0 -7
  184. package/dist/_chunks/urls-CbOsUOoW.mjs.map +0 -1
  185. package/dist/_chunks/urls-DzZya_gm.js +0 -6
  186. package/dist/_chunks/urls-DzZya_gm.js.map +0 -1
  187. package/dist/server/src/controllers/utils/dimensions.d.ts +0 -5
  188. package/dist/server/src/controllers/utils/dimensions.d.ts.map +0 -1
  189. 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, sanitize } from "@strapi/utils";
2
- import { pick, omit, difference, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy, castArray } from "lodash/fp";
1
+ import strapiUtils, { validateYupSchema, errors, async, contentTypes as contentTypes$1, yup as yup$1, validateYupSchemaSync, policy, traverse, setCreatorFields, isOperatorOfType, relations as relations$1, traverseEntity, pagination } from "@strapi/utils";
2
+ import { pick, omit, difference, castArray, intersection, pipe, propOr, isEqual, isEmpty, set, isNil as isNil$1, has, prop, assoc, mapValues, flow, uniq, uniqBy, concat, getOr, propEq, merge, groupBy } from "lodash/fp";
3
3
  import "@strapi/types";
4
4
  import * as yup from "yup";
5
5
  import { scheduleJob } from "node-schedule";
@@ -54,7 +54,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
54
54
  return ctx.forbidden();
55
55
  }
56
56
  const query = await permissionChecker2.sanitizeQuery(ctx.query);
57
- const { results, pagination } = await getService(strapi2, "history").findVersionsPage({
57
+ const { results, pagination: pagination2 } = await getService(strapi2, "history").findVersionsPage({
58
58
  query: {
59
59
  ...query,
60
60
  ...getValidPagination({ page: query.page, pageSize: query.pageSize })
@@ -73,7 +73,7 @@ const createHistoryVersionController = ({ strapi: strapi2 }) => {
73
73
  );
74
74
  return {
75
75
  data: sanitizedResults,
76
- meta: { pagination }
76
+ meta: { pagination: pagination2 }
77
77
  };
78
78
  },
79
79
  async restoreVersion(ctx) {
@@ -112,43 +112,70 @@ const FIELDS_TO_IGNORE = [
112
112
  "strapi_stage",
113
113
  "strapi_assignee"
114
114
  ];
115
- const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
116
- const sanitizedContentTypeSchemaAttributes = omit(FIELDS_TO_IGNORE, contentTypeSchemaAttributes);
117
- const reduceDifferenceToAttributesObject = (diffKeys, source) => {
118
- return diffKeys.reduce((previousAttributesObject, diffKey) => {
119
- previousAttributesObject[diffKey] = source[diffKey];
120
- return previousAttributesObject;
121
- }, {});
122
- };
123
- const versionSchemaKeys = Object.keys(versionSchemaAttributes);
124
- const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
125
- const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
126
- const added = reduceDifferenceToAttributesObject(
127
- uniqueToContentType,
128
- sanitizedContentTypeSchemaAttributes
129
- );
130
- const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
131
- const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
132
- return { added, removed };
133
- };
134
115
  const DEFAULT_RETENTION_DAYS = 90;
135
- const createHistoryService = ({ strapi: strapi2 }) => {
136
- const state = {
137
- deleteExpiredJob: null,
138
- isInitialized: false
116
+ const createServiceUtils = ({ strapi: strapi2 }) => {
117
+ const getSchemaAttributesDiff = (versionSchemaAttributes, contentTypeSchemaAttributes) => {
118
+ const sanitizedContentTypeSchemaAttributes = omit(
119
+ FIELDS_TO_IGNORE,
120
+ contentTypeSchemaAttributes
121
+ );
122
+ const reduceDifferenceToAttributesObject = (diffKeys, source) => {
123
+ return diffKeys.reduce(
124
+ (previousAttributesObject, diffKey) => {
125
+ previousAttributesObject[diffKey] = source[diffKey];
126
+ return previousAttributesObject;
127
+ },
128
+ {}
129
+ );
130
+ };
131
+ const versionSchemaKeys = Object.keys(versionSchemaAttributes);
132
+ const contentTypeSchemaAttributesKeys = Object.keys(sanitizedContentTypeSchemaAttributes);
133
+ const uniqueToContentType = difference(contentTypeSchemaAttributesKeys, versionSchemaKeys);
134
+ const added = reduceDifferenceToAttributesObject(
135
+ uniqueToContentType,
136
+ sanitizedContentTypeSchemaAttributes
137
+ );
138
+ const uniqueToVersion = difference(versionSchemaKeys, contentTypeSchemaAttributesKeys);
139
+ const removed = reduceDifferenceToAttributesObject(uniqueToVersion, versionSchemaAttributes);
140
+ return { added, removed };
139
141
  };
140
- const query = strapi2.db.query(HISTORY_VERSION_UID);
141
- const getRetentionDays = (strapi22) => {
142
- const featureConfig = strapi22.ee.features.get("cms-content-history");
143
- const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
144
- const userRetentionDays = strapi22.config.get("admin.history.retentionDays");
145
- if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
146
- return userRetentionDays;
142
+ const getRelationRestoreValue = async (versionRelationData, attribute) => {
143
+ if (Array.isArray(versionRelationData)) {
144
+ if (versionRelationData.length === 0)
145
+ return versionRelationData;
146
+ const existingAndMissingRelations = await Promise.all(
147
+ versionRelationData.map((relation) => {
148
+ return strapi2.documents(attribute.target).findOne({
149
+ documentId: relation.documentId,
150
+ locale: relation.locale || void 0
151
+ });
152
+ })
153
+ );
154
+ return existingAndMissingRelations.filter(
155
+ (relation) => relation !== null
156
+ );
147
157
  }
148
- return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
158
+ return strapi2.documents(attribute.target).findOne({
159
+ documentId: versionRelationData.documentId,
160
+ locale: versionRelationData.locale || void 0
161
+ });
162
+ };
163
+ const getMediaRestoreValue = async (versionRelationData, attribute) => {
164
+ if (attribute.multiple) {
165
+ const existingAndMissingMedias = await Promise.all(
166
+ // @ts-expect-error Fix the type definitions so this isn't any
167
+ versionRelationData.map((media) => {
168
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
169
+ })
170
+ );
171
+ return existingAndMissingMedias.filter((media) => media != null);
172
+ }
173
+ return strapi2.db.query("plugin::upload.file").findOne({ where: { id: versionRelationData.id } });
149
174
  };
150
175
  const localesService = strapi2.plugin("i18n")?.service("locales");
176
+ const i18nContentTypeService = strapi2.plugin("i18n")?.service("content-types");
151
177
  const getDefaultLocale = async () => localesService ? localesService.getDefaultLocale() : null;
178
+ const isLocalizedContentType = (model) => i18nContentTypeService ? i18nContentTypeService.isLocalizedContentType(model) : false;
152
179
  const getLocaleDictionary = async () => {
153
180
  if (!localesService)
154
181
  return {};
@@ -161,25 +188,39 @@ const createHistoryService = ({ strapi: strapi2 }) => {
161
188
  {}
162
189
  );
163
190
  };
191
+ const getRetentionDays = () => {
192
+ const featureConfig = strapi2.ee.features.get("cms-content-history");
193
+ const licenseRetentionDays = typeof featureConfig === "object" && featureConfig?.options.retentionDays;
194
+ const userRetentionDays = strapi2.config.get("admin.history.retentionDays");
195
+ if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
196
+ return userRetentionDays;
197
+ }
198
+ return Math.min(licenseRetentionDays, DEFAULT_RETENTION_DAYS);
199
+ };
164
200
  const getVersionStatus = async (contentTypeUid, document) => {
165
201
  const documentMetadataService = strapi2.plugin("content-manager").service("document-metadata");
166
202
  const meta = await documentMetadataService.getMetadata(contentTypeUid, document);
167
203
  return documentMetadataService.getStatus(document, meta.availableStatus);
168
204
  };
169
- const getDeepPopulate2 = (uid2) => {
205
+ const getDeepPopulate2 = (uid2, useDatabaseSyntax = false) => {
170
206
  const model = strapi2.getModel(uid2);
171
207
  const attributes = Object.entries(model.attributes);
208
+ const fieldSelector = useDatabaseSyntax ? "select" : "fields";
172
209
  return attributes.reduce((acc, [attributeName, attribute]) => {
173
210
  switch (attribute.type) {
174
211
  case "relation": {
212
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
213
+ if (isMorphRelation) {
214
+ break;
215
+ }
175
216
  const isVisible2 = contentTypes$1.isVisibleAttribute(model, attributeName);
176
217
  if (isVisible2) {
177
- acc[attributeName] = { fields: ["documentId", "locale", "publishedAt"] };
218
+ acc[attributeName] = { [fieldSelector]: ["documentId", "locale", "publishedAt"] };
178
219
  }
179
220
  break;
180
221
  }
181
222
  case "media": {
182
- acc[attributeName] = { fields: ["id"] };
223
+ acc[attributeName] = { [fieldSelector]: ["id"] };
183
224
  break;
184
225
  }
185
226
  case "component": {
@@ -202,80 +243,69 @@ const createHistoryService = ({ strapi: strapi2 }) => {
202
243
  return acc;
203
244
  }, {});
204
245
  };
205
- return {
206
- async bootstrap() {
207
- if (state.isInitialized) {
208
- return;
209
- }
210
- strapi2.documents.use(async (context, next) => {
211
- if (!strapi2.requestContext.get()?.request.url.startsWith("/content-manager")) {
212
- return next();
246
+ const buildMediaResponse = async (values) => {
247
+ return values.slice(0, 25).reduce(
248
+ async (currentRelationDataPromise, entry) => {
249
+ const currentRelationData = await currentRelationDataPromise;
250
+ if (!entry) {
251
+ return currentRelationData;
213
252
  }
214
- if (context.action !== "create" && context.action !== "update" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
215
- return next();
253
+ const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
254
+ if (relatedEntry) {
255
+ currentRelationData.results.push(relatedEntry);
256
+ } else {
257
+ currentRelationData.meta.missingCount += 1;
216
258
  }
217
- const contentTypeUid = context.contentType.uid;
218
- if (!contentTypeUid.startsWith("api::")) {
219
- return next();
259
+ return currentRelationData;
260
+ },
261
+ Promise.resolve({
262
+ results: [],
263
+ meta: { missingCount: 0 }
264
+ })
265
+ );
266
+ };
267
+ const buildRelationReponse = async (values, attributeSchema) => {
268
+ return values.slice(0, 25).reduce(
269
+ async (currentRelationDataPromise, entry) => {
270
+ const currentRelationData = await currentRelationDataPromise;
271
+ if (!entry) {
272
+ return currentRelationData;
220
273
  }
221
- const result = await next();
222
- const documentContext = context.action === "create" ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale };
223
- const defaultLocale = await getDefaultLocale();
224
- const locale = documentContext.locale || defaultLocale;
225
- const document = await strapi2.documents(contentTypeUid).findOne({
226
- documentId: documentContext.documentId,
227
- locale,
228
- populate: getDeepPopulate2(contentTypeUid)
229
- });
230
- const status = await getVersionStatus(contentTypeUid, document);
231
- const attributesSchema = strapi2.getModel(contentTypeUid).attributes;
232
- const componentsSchemas = Object.keys(
233
- attributesSchema
234
- ).reduce((currentComponentSchemas, key) => {
235
- const fieldSchema = attributesSchema[key];
236
- if (fieldSchema.type === "component") {
237
- const componentSchema = strapi2.getModel(fieldSchema.component).attributes;
238
- return {
239
- ...currentComponentSchemas,
240
- [fieldSchema.component]: componentSchema
241
- };
242
- }
243
- return currentComponentSchemas;
244
- }, {});
245
- await strapi2.db.transaction(async ({ onCommit }) => {
246
- onCommit(() => {
247
- this.createVersion({
248
- contentType: contentTypeUid,
249
- data: omit(FIELDS_TO_IGNORE, document),
250
- schema: omit(FIELDS_TO_IGNORE, attributesSchema),
251
- componentsSchemas,
252
- relatedDocumentId: documentContext.documentId,
253
- locale,
254
- status
255
- });
274
+ const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
275
+ if (relatedEntry) {
276
+ currentRelationData.results.push({
277
+ ...relatedEntry,
278
+ status: await getVersionStatus(attributeSchema.target, relatedEntry)
256
279
  });
257
- });
258
- return result;
259
- });
260
- const retentionDays = getRetentionDays(strapi2);
261
- state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
262
- const retentionDaysInMilliseconds = retentionDays * 24 * 60 * 60 * 1e3;
263
- const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
264
- query.deleteMany({
265
- where: {
266
- created_at: {
267
- $lt: expirationDate.toISOString()
268
- }
269
- }
270
- });
271
- });
272
- state.isInitialized = true;
273
- },
274
- async destroy() {
275
- if (state.deleteExpiredJob) {
276
- state.deleteExpiredJob.cancel();
277
- }
278
- },
280
+ } else {
281
+ currentRelationData.meta.missingCount += 1;
282
+ }
283
+ return currentRelationData;
284
+ },
285
+ Promise.resolve({
286
+ results: [],
287
+ meta: { missingCount: 0 }
288
+ })
289
+ );
290
+ };
291
+ return {
292
+ getSchemaAttributesDiff,
293
+ getRelationRestoreValue,
294
+ getMediaRestoreValue,
295
+ getDefaultLocale,
296
+ isLocalizedContentType,
297
+ getLocaleDictionary,
298
+ getRetentionDays,
299
+ getVersionStatus,
300
+ getDeepPopulate: getDeepPopulate2,
301
+ buildMediaResponse,
302
+ buildRelationReponse
303
+ };
304
+ };
305
+ const createHistoryService = ({ strapi: strapi2 }) => {
306
+ const query = strapi2.db.query(HISTORY_VERSION_UID);
307
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
308
+ return {
279
309
  async createVersion(historyVersionData) {
280
310
  await query.create({
281
311
  data: {
@@ -286,8 +316,14 @@ const createHistoryService = ({ strapi: strapi2 }) => {
286
316
  });
287
317
  },
288
318
  async findVersionsPage(params) {
289
- const locale = params.query.locale || await getDefaultLocale();
290
- const [{ results, pagination }, localeDictionary] = await Promise.all([
319
+ const model = strapi2.getModel(params.query.contentType);
320
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
321
+ const defaultLocale = await serviceUtils.getDefaultLocale();
322
+ let locale = null;
323
+ if (isLocalizedContentType) {
324
+ locale = params.query.locale || defaultLocale;
325
+ }
326
+ const [{ results, pagination: pagination2 }, localeDictionary] = await Promise.all([
291
327
  query.findPage({
292
328
  ...params.query,
293
329
  where: {
@@ -300,78 +336,34 @@ const createHistoryService = ({ strapi: strapi2 }) => {
300
336
  populate: ["createdBy"],
301
337
  orderBy: [{ createdAt: "desc" }]
302
338
  }),
303
- getLocaleDictionary()
339
+ serviceUtils.getLocaleDictionary()
304
340
  ]);
305
- const buildRelationReponse = async (values, attributeSchema) => {
306
- return values.slice(0, 25).reduce(
307
- async (currentRelationDataPromise, entry) => {
308
- const currentRelationData = await currentRelationDataPromise;
309
- if (!entry) {
310
- return currentRelationData;
311
- }
312
- const relatedEntry = await strapi2.documents(attributeSchema.target).findOne({ documentId: entry.documentId, locale: entry.locale || void 0 });
313
- const permissionChecker2 = getService$1("permission-checker").create({
314
- userAbility: params.state.userAbility,
315
- model: attributeSchema.target
316
- });
317
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
318
- if (sanitizedEntry) {
319
- currentRelationData.results.push({
320
- ...sanitizedEntry,
321
- status: await getVersionStatus(attributeSchema.target, sanitizedEntry)
322
- });
323
- } else {
324
- currentRelationData.meta.missingCount += 1;
325
- }
326
- return currentRelationData;
327
- },
328
- Promise.resolve({
329
- results: [],
330
- meta: { missingCount: 0 }
331
- })
332
- );
333
- };
334
- const buildMediaResponse = async (values) => {
335
- return values.slice(0, 25).reduce(
336
- async (currentRelationDataPromise, entry) => {
337
- const currentRelationData = await currentRelationDataPromise;
338
- if (!entry) {
339
- return currentRelationData;
340
- }
341
- const permissionChecker2 = getService$1("permission-checker").create({
342
- userAbility: params.state.userAbility,
343
- model: "plugin::upload.file"
344
- });
345
- const relatedEntry = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: entry.id } });
346
- const sanitizedEntry = await permissionChecker2.sanitizeOutput(relatedEntry);
347
- if (sanitizedEntry) {
348
- currentRelationData.results.push(sanitizedEntry);
349
- } else {
350
- currentRelationData.meta.missingCount += 1;
351
- }
352
- return currentRelationData;
353
- },
354
- Promise.resolve({
355
- results: [],
356
- meta: { missingCount: 0 }
357
- })
358
- );
359
- };
360
341
  const populateEntryRelations = async (entry) => {
361
342
  const entryWithRelations = await Object.entries(entry.schema).reduce(
362
343
  async (currentDataWithRelations, [attributeKey, attributeSchema]) => {
363
344
  const attributeValue = entry.data[attributeKey];
364
345
  const attributeValues = Array.isArray(attributeValue) ? attributeValue : [attributeValue];
365
346
  if (attributeSchema.type === "media") {
347
+ const permissionChecker2 = getService$1("permission-checker").create({
348
+ userAbility: params.state.userAbility,
349
+ model: "plugin::upload.file"
350
+ });
351
+ const response = await serviceUtils.buildMediaResponse(attributeValues);
352
+ const sanitizedResults = await Promise.all(
353
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
354
+ );
366
355
  return {
367
356
  ...await currentDataWithRelations,
368
- [attributeKey]: await buildMediaResponse(attributeValues)
357
+ [attributeKey]: {
358
+ results: sanitizedResults,
359
+ meta: response.meta
360
+ }
369
361
  };
370
362
  }
371
363
  if (attributeSchema.type === "relation" && attributeSchema.relation !== "morphToOne" && attributeSchema.relation !== "morphToMany") {
372
364
  if (attributeSchema.target === "admin::user") {
373
365
  const adminUsers = await Promise.all(
374
- attributeValues.map(async (userToPopulate) => {
366
+ attributeValues.map((userToPopulate) => {
375
367
  if (userToPopulate == null) {
376
368
  return null;
377
369
  }
@@ -388,9 +380,23 @@ const createHistoryService = ({ strapi: strapi2 }) => {
388
380
  [attributeKey]: adminUsers
389
381
  };
390
382
  }
383
+ const permissionChecker2 = getService$1("permission-checker").create({
384
+ userAbility: params.state.userAbility,
385
+ model: attributeSchema.target
386
+ });
387
+ const response = await serviceUtils.buildRelationReponse(
388
+ attributeValues,
389
+ attributeSchema
390
+ );
391
+ const sanitizedResults = await Promise.all(
392
+ response.results.map((media) => permissionChecker2.sanitizeOutput(media))
393
+ );
391
394
  return {
392
395
  ...await currentDataWithRelations,
393
- [attributeKey]: await buildRelationReponse(attributeValues, attributeSchema)
396
+ [attributeKey]: {
397
+ results: sanitizedResults,
398
+ meta: response.meta
399
+ }
394
400
  };
395
401
  }
396
402
  return currentDataWithRelations;
@@ -405,7 +411,7 @@ const createHistoryService = ({ strapi: strapi2 }) => {
405
411
  ...result,
406
412
  data: await populateEntryRelations(result),
407
413
  meta: {
408
- unknownAttributes: getSchemaAttributesDiff(
414
+ unknownAttributes: serviceUtils.getSchemaAttributesDiff(
409
415
  result.schema,
410
416
  strapi2.getModel(params.query.contentType).attributes
411
417
  )
@@ -416,13 +422,16 @@ const createHistoryService = ({ strapi: strapi2 }) => {
416
422
  );
417
423
  return {
418
424
  results: formattedResults,
419
- pagination
425
+ pagination: pagination2
420
426
  };
421
427
  },
422
428
  async restoreVersion(versionId) {
423
429
  const version = await query.findOne({ where: { id: versionId } });
424
430
  const contentTypeSchemaAttributes = strapi2.getModel(version.contentType).attributes;
425
- const schemaDiff = getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
431
+ const schemaDiff = serviceUtils.getSchemaAttributesDiff(
432
+ version.schema,
433
+ contentTypeSchemaAttributes
434
+ );
426
435
  const dataWithoutAddedAttributes = Object.keys(schemaDiff.added).reduce(
427
436
  (currentData, addedKey) => {
428
437
  currentData[addedKey] = null;
@@ -435,61 +444,26 @@ const createHistoryService = ({ strapi: strapi2 }) => {
435
444
  FIELDS_TO_IGNORE,
436
445
  contentTypeSchemaAttributes
437
446
  );
438
- const dataWithoutMissingRelations = await Object.entries(sanitizedSchemaAttributes).reduce(
439
- async (previousRelationAttributesPromise, [name, attribute]) => {
440
- const previousRelationAttributes = await previousRelationAttributesPromise;
441
- const relationData = version.data[name];
442
- if (relationData === null) {
447
+ const reducer = async.reduce(Object.entries(sanitizedSchemaAttributes));
448
+ const dataWithoutMissingRelations = await reducer(
449
+ async (previousRelationAttributes, [name, attribute]) => {
450
+ const versionRelationData = version.data[name];
451
+ if (!versionRelationData) {
443
452
  return previousRelationAttributes;
444
453
  }
445
454
  if (attribute.type === "relation" && // TODO: handle polymorphic relations
446
455
  attribute.relation !== "morphToOne" && attribute.relation !== "morphToMany") {
447
- if (Array.isArray(relationData)) {
448
- if (relationData.length === 0)
449
- return previousRelationAttributes;
450
- const existingAndMissingRelations = await Promise.all(
451
- relationData.map((relation) => {
452
- return strapi2.documents(attribute.target).findOne({
453
- documentId: relation.documentId,
454
- locale: relation.locale || void 0
455
- });
456
- })
457
- );
458
- const existingRelations = existingAndMissingRelations.filter(
459
- (relation) => relation !== null
460
- );
461
- previousRelationAttributes[name] = existingRelations;
462
- } else {
463
- const existingRelation = await strapi2.documents(attribute.target).findOne({
464
- documentId: relationData.documentId,
465
- locale: relationData.locale || void 0
466
- });
467
- if (!existingRelation) {
468
- previousRelationAttributes[name] = null;
469
- }
470
- }
456
+ const data2 = await serviceUtils.getRelationRestoreValue(versionRelationData, attribute);
457
+ previousRelationAttributes[name] = data2;
471
458
  }
472
459
  if (attribute.type === "media") {
473
- if (attribute.multiple) {
474
- const existingAndMissingMedias = await Promise.all(
475
- // @ts-expect-error Fix the type definitions so this isn't any
476
- relationData.map((media) => {
477
- return strapi2.db.query("plugin::upload.file").findOne({ where: { id: media.id } });
478
- })
479
- );
480
- const existingMedias = existingAndMissingMedias.filter((media) => media != null);
481
- previousRelationAttributes[name] = existingMedias;
482
- } else {
483
- const existingMedia = await strapi2.db.query("plugin::upload.file").findOne({ where: { id: version.data[name].id } });
484
- if (!existingMedia) {
485
- previousRelationAttributes[name] = null;
486
- }
487
- }
460
+ const data2 = await serviceUtils.getMediaRestoreValue(versionRelationData, attribute);
461
+ previousRelationAttributes[name] = data2;
488
462
  }
489
463
  return previousRelationAttributes;
490
464
  },
491
465
  // Clone to avoid mutating the original version data
492
- Promise.resolve(structuredClone(dataWithoutAddedAttributes))
466
+ structuredClone(dataWithoutAddedAttributes)
493
467
  );
494
468
  const data = omit(["id", ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations);
495
469
  const restoredDocument = await strapi2.documents(version.contentType).update({
@@ -504,8 +478,120 @@ const createHistoryService = ({ strapi: strapi2 }) => {
504
478
  }
505
479
  };
506
480
  };
481
+ const shouldCreateHistoryVersion = (context) => {
482
+ if (!strapi.requestContext.get()?.request.url.startsWith("/content-manager")) {
483
+ return false;
484
+ }
485
+ if (context.action !== "create" && context.action !== "update" && context.action !== "clone" && context.action !== "publish" && context.action !== "unpublish" && context.action !== "discardDraft") {
486
+ return false;
487
+ }
488
+ if (context.action === "update" && strapi.requestContext.get()?.request.url.endsWith("/actions/publish")) {
489
+ return false;
490
+ }
491
+ if (!context.contentType.uid.startsWith("api::")) {
492
+ return false;
493
+ }
494
+ return true;
495
+ };
496
+ const getSchemas = (uid2) => {
497
+ const attributesSchema = strapi.getModel(uid2).attributes;
498
+ const componentsSchemas = Object.keys(attributesSchema).reduce(
499
+ (currentComponentSchemas, key) => {
500
+ const fieldSchema = attributesSchema[key];
501
+ if (fieldSchema.type === "component") {
502
+ const componentSchema = strapi.getModel(fieldSchema.component).attributes;
503
+ return {
504
+ ...currentComponentSchemas,
505
+ [fieldSchema.component]: componentSchema
506
+ };
507
+ }
508
+ return currentComponentSchemas;
509
+ },
510
+ {}
511
+ );
512
+ return {
513
+ schema: omit(FIELDS_TO_IGNORE, attributesSchema),
514
+ componentsSchemas
515
+ };
516
+ };
517
+ const createLifecyclesService = ({ strapi: strapi2 }) => {
518
+ const state = {
519
+ deleteExpiredJob: null,
520
+ isInitialized: false
521
+ };
522
+ const serviceUtils = createServiceUtils({ strapi: strapi2 });
523
+ return {
524
+ async bootstrap() {
525
+ if (state.isInitialized) {
526
+ return;
527
+ }
528
+ strapi2.documents.use(async (context, next) => {
529
+ const result = await next();
530
+ if (!shouldCreateHistoryVersion(context)) {
531
+ return result;
532
+ }
533
+ const documentId = context.action === "create" || context.action === "clone" ? result.documentId : context.params.documentId;
534
+ const defaultLocale = await serviceUtils.getDefaultLocale();
535
+ const locales = castArray(context.params?.locale || defaultLocale);
536
+ if (!locales.length) {
537
+ return result;
538
+ }
539
+ const uid2 = context.contentType.uid;
540
+ const schemas = getSchemas(uid2);
541
+ const model = strapi2.getModel(uid2);
542
+ const isLocalizedContentType = serviceUtils.isLocalizedContentType(model);
543
+ const localeEntries = await strapi2.db.query(uid2).findMany({
544
+ where: {
545
+ documentId,
546
+ ...isLocalizedContentType ? { locale: { $in: locales } } : {},
547
+ ...contentTypes$1.hasDraftAndPublish(strapi2.contentTypes[uid2]) ? { publishedAt: null } : {}
548
+ },
549
+ populate: serviceUtils.getDeepPopulate(
550
+ uid2,
551
+ true
552
+ /* use database syntax */
553
+ )
554
+ });
555
+ await strapi2.db.transaction(async ({ onCommit }) => {
556
+ onCommit(async () => {
557
+ for (const entry of localeEntries) {
558
+ const status = await serviceUtils.getVersionStatus(uid2, entry);
559
+ await getService(strapi2, "history").createVersion({
560
+ contentType: uid2,
561
+ data: omit(FIELDS_TO_IGNORE, entry),
562
+ relatedDocumentId: documentId,
563
+ locale: entry.locale,
564
+ status,
565
+ ...schemas
566
+ });
567
+ }
568
+ });
569
+ });
570
+ return result;
571
+ });
572
+ state.deleteExpiredJob = scheduleJob("0 0 * * *", () => {
573
+ const retentionDaysInMilliseconds = serviceUtils.getRetentionDays() * 24 * 60 * 60 * 1e3;
574
+ const expirationDate = new Date(Date.now() - retentionDaysInMilliseconds);
575
+ strapi2.db.query(HISTORY_VERSION_UID).deleteMany({
576
+ where: {
577
+ created_at: {
578
+ $lt: expirationDate.toISOString()
579
+ }
580
+ }
581
+ });
582
+ });
583
+ state.isInitialized = true;
584
+ },
585
+ async destroy() {
586
+ if (state.deleteExpiredJob) {
587
+ state.deleteExpiredJob.cancel();
588
+ }
589
+ }
590
+ };
591
+ };
507
592
  const services$1 = {
508
- history: createHistoryService
593
+ history: createHistoryService,
594
+ lifecycles: createLifecyclesService
509
595
  };
510
596
  const info = { pluginName: "content-manager", type: "admin" };
511
597
  const historyVersionRouter = {
@@ -585,10 +671,10 @@ const getFeature = () => {
585
671
  strapi2.get("models").add(historyVersion);
586
672
  },
587
673
  bootstrap({ strapi: strapi2 }) {
588
- getService(strapi2, "history").bootstrap();
674
+ getService(strapi2, "lifecycles").bootstrap();
589
675
  },
590
676
  destroy({ strapi: strapi2 }) {
591
- getService(strapi2, "history").destroy();
677
+ getService(strapi2, "lifecycles").destroy();
592
678
  },
593
679
  controllers: controllers$1,
594
680
  services: services$1,
@@ -1118,6 +1204,11 @@ const { createPolicy } = policy;
1118
1204
  const hasPermissions = createPolicy({
1119
1205
  name: "plugin::content-manager.hasPermissions",
1120
1206
  validator: validateHasPermissionsInput,
1207
+ /**
1208
+ * NOTE: Action aliases are currently not checked at this level (policy).
1209
+ * This is currently the intended behavior to avoid changing the behavior of API related permissions.
1210
+ * If you want to add support for it, please create a dedicated RFC with a list of potential side effect this could have.
1211
+ */
1121
1212
  handler(ctx, config = {}) {
1122
1213
  const { actions = [], hasAtLeastOne = false } = config;
1123
1214
  const { userAbility } = ctx.state;
@@ -1407,7 +1498,7 @@ const { PaginationError, ValidationError } = errors;
1407
1498
  const TYPES = ["singleType", "collectionType"];
1408
1499
  const kindSchema = yup$1.string().oneOf(TYPES).nullable();
1409
1500
  const bulkActionInputSchema = yup$1.object({
1410
- ids: yup$1.array().of(yup$1.strapiID()).min(1).required()
1501
+ documentIds: yup$1.array().of(yup$1.strapiID()).min(1).required()
1411
1502
  }).required();
1412
1503
  const generateUIDInputSchema = yup$1.object({
1413
1504
  contentTypeUID: yup$1.string().required(),
@@ -1506,15 +1597,49 @@ const excludeNotCreatableFields = (uid2, permissionChecker2) => (body, path = []
1506
1597
  }
1507
1598
  }, body);
1508
1599
  };
1509
- const getDocumentLocaleAndStatus = (request) => {
1510
- const { locale, status, ...rest } = request || {};
1511
- if (!isNil$1(locale) && typeof locale !== "string") {
1512
- throw new errors.ValidationError(`Invalid locale: ${locale}`);
1513
- }
1514
- if (!isNil$1(status) && !["draft", "published"].includes(status)) {
1515
- throw new errors.ValidationError(`Invalid status: ${status}`);
1600
+ const singleLocaleSchema = yup$1.string().nullable();
1601
+ const multipleLocaleSchema = yup$1.lazy(
1602
+ (value) => Array.isArray(value) ? yup$1.array().of(singleLocaleSchema.required()) : singleLocaleSchema
1603
+ );
1604
+ const statusSchema = yup$1.mixed().oneOf(["draft", "published"], "Invalid status");
1605
+ const getDocumentLocaleAndStatus = async (request, model, opts = { allowMultipleLocales: false }) => {
1606
+ const { allowMultipleLocales } = opts;
1607
+ const { locale, status: providedStatus, ...rest } = request || {};
1608
+ const defaultStatus = contentTypes$1.hasDraftAndPublish(strapi.getModel(model)) ? void 0 : "published";
1609
+ const status = providedStatus !== void 0 ? providedStatus : defaultStatus;
1610
+ const schema = yup$1.object().shape({
1611
+ locale: allowMultipleLocales ? multipleLocaleSchema : singleLocaleSchema,
1612
+ status: statusSchema
1613
+ });
1614
+ try {
1615
+ await validateYupSchema(schema, { strict: true, abortEarly: false })(request);
1616
+ return { locale, status, ...rest };
1617
+ } catch (error) {
1618
+ throw new errors.ValidationError(`Validation error: ${error.message}`);
1516
1619
  }
1517
- return { locale, status, ...rest };
1620
+ };
1621
+ const formatDocumentWithMetadata = async (permissionChecker2, uid2, document, opts = {}) => {
1622
+ const documentMetadata2 = getService$1("document-metadata");
1623
+ const serviceOutput = await documentMetadata2.formatDocumentWithMetadata(uid2, document, opts);
1624
+ let {
1625
+ meta: { availableLocales, availableStatus }
1626
+ } = serviceOutput;
1627
+ const metadataSanitizer = permissionChecker2.sanitizeOutput;
1628
+ availableLocales = await async.map(
1629
+ availableLocales,
1630
+ async (localeDocument) => metadataSanitizer(localeDocument)
1631
+ );
1632
+ availableStatus = await async.map(
1633
+ availableStatus,
1634
+ async (statusDocument) => metadataSanitizer(statusDocument)
1635
+ );
1636
+ return {
1637
+ ...serviceOutput,
1638
+ meta: {
1639
+ availableLocales,
1640
+ availableStatus
1641
+ }
1642
+ };
1518
1643
  };
1519
1644
  const createDocument = async (ctx, opts) => {
1520
1645
  const { userAbility, user } = ctx.state;
@@ -1529,7 +1654,7 @@ const createDocument = async (ctx, opts) => {
1529
1654
  const setCreator = setCreatorFields({ user });
1530
1655
  const sanitizeFn = async.pipe(pickPermittedFields, setCreator);
1531
1656
  const sanitizedBody = await sanitizeFn(body);
1532
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(body);
1657
+ const { locale, status } = await getDocumentLocaleAndStatus(body, model);
1533
1658
  return documentManager2.create(model, {
1534
1659
  data: sanitizedBody,
1535
1660
  locale,
@@ -1548,7 +1673,7 @@ const updateDocument = async (ctx, opts) => {
1548
1673
  }
1549
1674
  const permissionQuery = await permissionChecker2.sanitizedQuery.update(ctx.query);
1550
1675
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1551
- const { locale } = getDocumentLocaleAndStatus(body);
1676
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1552
1677
  const [documentVersion, documentExists] = await Promise.all([
1553
1678
  documentManager2.findOne(id, model, { populate, locale, status: "draft" }),
1554
1679
  documentManager2.exists(model, id)
@@ -1586,8 +1711,8 @@ const collectionTypes = {
1586
1711
  }
1587
1712
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
1588
1713
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(1).countRelations({ toOne: false, toMany: true }).build();
1589
- const { locale, status } = getDocumentLocaleAndStatus(query);
1590
- const { results: documents, pagination } = await documentManager2.findPage(
1714
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
1715
+ const { results: documents, pagination: pagination2 } = await documentManager2.findPage(
1591
1716
  { ...permissionQuery, populate, locale, status },
1592
1717
  model
1593
1718
  );
@@ -1608,21 +1733,20 @@ const collectionTypes = {
1608
1733
  );
1609
1734
  ctx.body = {
1610
1735
  results,
1611
- pagination
1736
+ pagination: pagination2
1612
1737
  };
1613
1738
  },
1614
1739
  async findOne(ctx) {
1615
1740
  const { userAbility } = ctx.state;
1616
1741
  const { model, id } = ctx.params;
1617
1742
  const documentManager2 = getService$1("document-manager");
1618
- const documentMetadata2 = getService$1("document-metadata");
1619
1743
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1620
1744
  if (permissionChecker2.cannot.read()) {
1621
1745
  return ctx.forbidden();
1622
1746
  }
1623
1747
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1624
1748
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1625
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
1749
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1626
1750
  const version = await documentManager2.findOne(id, model, {
1627
1751
  populate,
1628
1752
  locale,
@@ -1633,9 +1757,11 @@ const collectionTypes = {
1633
1757
  if (!exists) {
1634
1758
  return ctx.notFound();
1635
1759
  }
1636
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
1760
+ const { meta } = await formatDocumentWithMetadata(
1761
+ permissionChecker2,
1637
1762
  model,
1638
- { id, locale, publishedAt: null },
1763
+ // @ts-expect-error TODO: fix
1764
+ { documentId: id, locale, publishedAt: null },
1639
1765
  { availableLocales: true, availableStatus: false }
1640
1766
  );
1641
1767
  ctx.body = { data: {}, meta };
@@ -1645,12 +1771,11 @@ const collectionTypes = {
1645
1771
  return ctx.forbidden();
1646
1772
  }
1647
1773
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
1648
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1774
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1649
1775
  },
1650
1776
  async create(ctx) {
1651
1777
  const { userAbility } = ctx.state;
1652
1778
  const { model } = ctx.params;
1653
- const documentMetadata2 = getService$1("document-metadata");
1654
1779
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1655
1780
  const [totalEntries, document] = await Promise.all([
1656
1781
  strapi.db.query(model).count(),
@@ -1658,7 +1783,7 @@ const collectionTypes = {
1658
1783
  ]);
1659
1784
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
1660
1785
  ctx.status = 201;
1661
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1786
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1662
1787
  // Empty metadata as it's not relevant for a new document
1663
1788
  availableLocales: false,
1664
1789
  availableStatus: false
@@ -1672,25 +1797,23 @@ const collectionTypes = {
1672
1797
  async update(ctx) {
1673
1798
  const { userAbility } = ctx.state;
1674
1799
  const { model } = ctx.params;
1675
- const documentMetadata2 = getService$1("document-metadata");
1676
1800
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1677
1801
  const updatedVersion = await updateDocument(ctx);
1678
1802
  const sanitizedVersion = await permissionChecker2.sanitizeOutput(updatedVersion);
1679
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedVersion);
1803
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedVersion);
1680
1804
  },
1681
1805
  async clone(ctx) {
1682
1806
  const { userAbility, user } = ctx.state;
1683
1807
  const { model, sourceId: id } = ctx.params;
1684
1808
  const { body } = ctx.request;
1685
1809
  const documentManager2 = getService$1("document-manager");
1686
- const documentMetadata2 = getService$1("document-metadata");
1687
1810
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1688
1811
  if (permissionChecker2.cannot.create()) {
1689
1812
  return ctx.forbidden();
1690
1813
  }
1691
1814
  const permissionQuery = await permissionChecker2.sanitizedQuery.create(ctx.query);
1692
1815
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1693
- const { locale } = getDocumentLocaleAndStatus(body);
1816
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1694
1817
  const document = await documentManager2.findOne(id, model, {
1695
1818
  populate,
1696
1819
  locale,
@@ -1706,7 +1829,7 @@ const collectionTypes = {
1706
1829
  const sanitizedBody = await sanitizeFn(body);
1707
1830
  const clonedDocument = await documentManager2.clone(document.documentId, sanitizedBody, model);
1708
1831
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(clonedDocument);
1709
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument, {
1832
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument, {
1710
1833
  // Empty metadata as it's not relevant for a new document
1711
1834
  availableLocales: false,
1712
1835
  availableStatus: false
@@ -1735,7 +1858,7 @@ const collectionTypes = {
1735
1858
  }
1736
1859
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(ctx.query);
1737
1860
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1738
- const { locale } = getDocumentLocaleAndStatus(ctx.query);
1861
+ const { locale } = await getDocumentLocaleAndStatus(ctx.query, model);
1739
1862
  const documentLocales = await documentManager2.findLocales(id, model, { populate, locale });
1740
1863
  if (documentLocales.length === 0) {
1741
1864
  return ctx.notFound();
@@ -1757,7 +1880,6 @@ const collectionTypes = {
1757
1880
  const { id, model } = ctx.params;
1758
1881
  const { body } = ctx.request;
1759
1882
  const documentManager2 = getService$1("document-manager");
1760
- const documentMetadata2 = getService$1("document-metadata");
1761
1883
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1762
1884
  if (permissionChecker2.cannot.publish()) {
1763
1885
  return ctx.forbidden();
@@ -1765,25 +1887,46 @@ const collectionTypes = {
1765
1887
  const publishedDocument = await strapi.db.transaction(async () => {
1766
1888
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1767
1889
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1768
- const document = id ? await updateDocument(ctx, { populate }) : await createDocument(ctx, { populate });
1890
+ let document;
1891
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1892
+ const isCreate = isNil$1(id);
1893
+ if (isCreate) {
1894
+ if (permissionChecker2.cannot.create()) {
1895
+ throw new errors.ForbiddenError();
1896
+ }
1897
+ document = await createDocument(ctx, { populate });
1898
+ }
1899
+ const isUpdate = !isCreate;
1900
+ if (isUpdate) {
1901
+ document = await documentManager2.findOne(id, model, { populate, locale });
1902
+ if (!document) {
1903
+ throw new errors.NotFoundError("Document not found");
1904
+ }
1905
+ if (permissionChecker2.can.update(document)) {
1906
+ await updateDocument(ctx);
1907
+ }
1908
+ }
1769
1909
  if (permissionChecker2.cannot.publish(document)) {
1770
1910
  throw new errors.ForbiddenError();
1771
1911
  }
1772
- const { locale } = getDocumentLocaleAndStatus(body);
1773
- return documentManager2.publish(document.documentId, model, {
1912
+ const publishResult = await documentManager2.publish(document.documentId, model, {
1774
1913
  locale
1775
1914
  // TODO: Allow setting creator fields on publish
1776
1915
  // data: setCreatorFields({ user, isEdition: true })({}),
1777
1916
  });
1917
+ if (!publishResult || publishResult.length === 0) {
1918
+ throw new errors.NotFoundError("Document not found or already published.");
1919
+ }
1920
+ return publishResult[0];
1778
1921
  });
1779
1922
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
1780
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
1923
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
1781
1924
  },
1782
1925
  async bulkPublish(ctx) {
1783
1926
  const { userAbility } = ctx.state;
1784
1927
  const { model } = ctx.params;
1785
1928
  const { body } = ctx.request;
1786
- const { ids } = body;
1929
+ const { documentIds } = body;
1787
1930
  await validateBulkActionInput(body);
1788
1931
  const documentManager2 = getService$1("document-manager");
1789
1932
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1792,8 +1935,13 @@ const collectionTypes = {
1792
1935
  }
1793
1936
  const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1794
1937
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).populateDeep(Infinity).countRelations().build();
1795
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1796
- const entities = await Promise.all(entityPromises);
1938
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1939
+ allowMultipleLocales: true
1940
+ });
1941
+ const entityPromises = documentIds.map(
1942
+ (documentId) => documentManager2.findLocales(documentId, model, { populate, locale, isPublished: false })
1943
+ );
1944
+ const entities = (await Promise.all(entityPromises)).flat();
1797
1945
  for (const entity of entities) {
1798
1946
  if (!entity) {
1799
1947
  return ctx.notFound();
@@ -1802,24 +1950,27 @@ const collectionTypes = {
1802
1950
  return ctx.forbidden();
1803
1951
  }
1804
1952
  }
1805
- const { count } = await documentManager2.publishMany(entities, model);
1953
+ const count = await documentManager2.publishMany(model, documentIds, locale);
1806
1954
  ctx.body = { count };
1807
1955
  },
1808
1956
  async bulkUnpublish(ctx) {
1809
1957
  const { userAbility } = ctx.state;
1810
1958
  const { model } = ctx.params;
1811
1959
  const { body } = ctx.request;
1812
- const { ids } = body;
1960
+ const { documentIds } = body;
1813
1961
  await validateBulkActionInput(body);
1814
1962
  const documentManager2 = getService$1("document-manager");
1815
1963
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1816
1964
  if (permissionChecker2.cannot.unpublish()) {
1817
1965
  return ctx.forbidden();
1818
1966
  }
1819
- const permissionQuery = await permissionChecker2.sanitizedQuery.publish(ctx.query);
1820
- const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1821
- const entityPromises = ids.map((id) => documentManager2.findOne(id, model, { populate }));
1822
- const entities = await Promise.all(entityPromises);
1967
+ const { locale } = await getDocumentLocaleAndStatus(body, model, {
1968
+ allowMultipleLocales: true
1969
+ });
1970
+ const entityPromises = documentIds.map(
1971
+ (documentId) => documentManager2.findLocales(documentId, model, { locale, isPublished: true })
1972
+ );
1973
+ const entities = (await Promise.all(entityPromises)).flat();
1823
1974
  for (const entity of entities) {
1824
1975
  if (!entity) {
1825
1976
  return ctx.notFound();
@@ -1828,7 +1979,8 @@ const collectionTypes = {
1828
1979
  return ctx.forbidden();
1829
1980
  }
1830
1981
  }
1831
- const { count } = await documentManager2.unpublishMany(entities, model);
1982
+ const entitiesIds = entities.map((document) => document.documentId);
1983
+ const { count } = await documentManager2.unpublishMany(entitiesIds, model, { locale });
1832
1984
  ctx.body = { count };
1833
1985
  },
1834
1986
  async unpublish(ctx) {
@@ -1838,7 +1990,6 @@ const collectionTypes = {
1838
1990
  body: { discardDraft, ...body }
1839
1991
  } = ctx.request;
1840
1992
  const documentManager2 = getService$1("document-manager");
1841
- const documentMetadata2 = getService$1("document-metadata");
1842
1993
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1843
1994
  if (permissionChecker2.cannot.unpublish()) {
1844
1995
  return ctx.forbidden();
@@ -1848,7 +1999,7 @@ const collectionTypes = {
1848
1999
  }
1849
2000
  const permissionQuery = await permissionChecker2.sanitizedQuery.unpublish(ctx.query);
1850
2001
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1851
- const { locale } = getDocumentLocaleAndStatus(body);
2002
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1852
2003
  const document = await documentManager2.findOne(id, model, {
1853
2004
  populate,
1854
2005
  locale,
@@ -1870,7 +2021,7 @@ const collectionTypes = {
1870
2021
  ctx.body = await async.pipe(
1871
2022
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
1872
2023
  permissionChecker2.sanitizeOutput,
1873
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2024
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1874
2025
  )(document);
1875
2026
  });
1876
2027
  },
@@ -1879,14 +2030,13 @@ const collectionTypes = {
1879
2030
  const { id, model } = ctx.params;
1880
2031
  const { body } = ctx.request;
1881
2032
  const documentManager2 = getService$1("document-manager");
1882
- const documentMetadata2 = getService$1("document-metadata");
1883
2033
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
1884
2034
  if (permissionChecker2.cannot.discard()) {
1885
2035
  return ctx.forbidden();
1886
2036
  }
1887
2037
  const permissionQuery = await permissionChecker2.sanitizedQuery.discard(ctx.query);
1888
2038
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1889
- const { locale } = getDocumentLocaleAndStatus(body);
2039
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
1890
2040
  const document = await documentManager2.findOne(id, model, {
1891
2041
  populate,
1892
2042
  locale,
@@ -1901,14 +2051,14 @@ const collectionTypes = {
1901
2051
  ctx.body = await async.pipe(
1902
2052
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
1903
2053
  permissionChecker2.sanitizeOutput,
1904
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2054
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
1905
2055
  )(document);
1906
2056
  },
1907
2057
  async bulkDelete(ctx) {
1908
2058
  const { userAbility } = ctx.state;
1909
2059
  const { model } = ctx.params;
1910
2060
  const { query, body } = ctx.request;
1911
- const { ids } = body;
2061
+ const { documentIds } = body;
1912
2062
  await validateBulkActionInput(body);
1913
2063
  const documentManager2 = getService$1("document-manager");
1914
2064
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
@@ -1916,14 +2066,22 @@ const collectionTypes = {
1916
2066
  return ctx.forbidden();
1917
2067
  }
1918
2068
  const permissionQuery = await permissionChecker2.sanitizedQuery.delete(query);
1919
- const idsWhereClause = { id: { $in: ids } };
1920
- const params = {
1921
- ...permissionQuery,
1922
- filters: {
1923
- $and: [idsWhereClause].concat(permissionQuery.filters || [])
2069
+ const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
2070
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2071
+ const documentLocales = await documentManager2.findLocales(documentIds, model, {
2072
+ populate,
2073
+ locale
2074
+ });
2075
+ if (documentLocales.length === 0) {
2076
+ return ctx.notFound();
2077
+ }
2078
+ for (const document of documentLocales) {
2079
+ if (permissionChecker2.cannot.delete(document)) {
2080
+ return ctx.forbidden();
1924
2081
  }
1925
- };
1926
- const { count } = await documentManager2.deleteMany(params, model);
2082
+ }
2083
+ const localeDocumentsIds = documentLocales.map((document) => document.documentId);
2084
+ const { count } = await documentManager2.deleteMany(localeDocumentsIds, model, { locale });
1927
2085
  ctx.body = { count };
1928
2086
  },
1929
2087
  async countDraftRelations(ctx) {
@@ -1936,7 +2094,7 @@ const collectionTypes = {
1936
2094
  }
1937
2095
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(ctx.query);
1938
2096
  const populate = await getService$1("populate-builder")(model).populateFromQuery(permissionQuery).build();
1939
- const { locale, status = "draft" } = getDocumentLocaleAndStatus(ctx.query);
2097
+ const { locale, status } = await getDocumentLocaleAndStatus(ctx.query, model);
1940
2098
  const entity = await documentManager2.findOne(id, model, { populate, locale, status });
1941
2099
  if (!entity) {
1942
2100
  return ctx.notFound();
@@ -1951,7 +2109,7 @@ const collectionTypes = {
1951
2109
  },
1952
2110
  async countManyEntriesDraftRelations(ctx) {
1953
2111
  const { userAbility } = ctx.state;
1954
- const ids = ctx.request.query.ids;
2112
+ const ids = ctx.request.query.documentIds;
1955
2113
  const locale = ctx.request.query.locale;
1956
2114
  const { model } = ctx.params;
1957
2115
  const documentManager2 = getService$1("document-manager");
@@ -1959,16 +2117,16 @@ const collectionTypes = {
1959
2117
  if (permissionChecker2.cannot.read()) {
1960
2118
  return ctx.forbidden();
1961
2119
  }
1962
- const entities = await documentManager2.findMany(
2120
+ const documents = await documentManager2.findMany(
1963
2121
  {
1964
2122
  filters: {
1965
- id: ids
2123
+ documentId: ids
1966
2124
  },
1967
2125
  locale
1968
2126
  },
1969
2127
  model
1970
2128
  );
1971
- if (!entities) {
2129
+ if (!documents) {
1972
2130
  return ctx.notFound();
1973
2131
  }
1974
2132
  const number = await documentManager2.countManyEntriesDraftRelations(ids, model, locale);
@@ -2159,20 +2317,13 @@ const sanitizeMainField = (model, mainField, userAbility) => {
2159
2317
  userAbility,
2160
2318
  model: model.uid
2161
2319
  });
2162
- if (!isListable(model, mainField)) {
2320
+ const isMainFieldListable = isListable(model, mainField);
2321
+ const canReadMainField = permissionChecker2.can.read(null, mainField);
2322
+ if (!isMainFieldListable || !canReadMainField) {
2163
2323
  return "id";
2164
2324
  }
2165
- if (permissionChecker2.cannot.read(null, mainField)) {
2166
- if (model.uid === "plugin::users-permissions.role") {
2167
- const userPermissionChecker = getService$1("permission-checker").create({
2168
- userAbility,
2169
- model: "plugin::users-permissions.user"
2170
- });
2171
- if (userPermissionChecker.can.read()) {
2172
- return "name";
2173
- }
2174
- }
2175
- return "id";
2325
+ if (model.uid === "plugin::users-permissions.role") {
2326
+ return "name";
2176
2327
  }
2177
2328
  return mainField;
2178
2329
  };
@@ -2372,8 +2523,9 @@ const relations = {
2372
2523
  } else {
2373
2524
  where.id = id;
2374
2525
  }
2375
- if (status) {
2376
- where[`${alias}.published_at`] = getPublishedAtClause(status, targetUid);
2526
+ const publishedAt = getPublishedAtClause(status, targetUid);
2527
+ if (!isEmpty(publishedAt)) {
2528
+ where[`${alias}.published_at`] = publishedAt;
2377
2529
  }
2378
2530
  if (filterByLocale) {
2379
2531
  where[`${alias}.locale`] = locale;
@@ -2430,9 +2582,7 @@ const relations = {
2430
2582
  addFiltersClause(permissionQuery, { id: { $in: loadedIds } });
2431
2583
  const sanitizedRes = await loadRelations({ id: entryId }, targetField, {
2432
2584
  ...strapi.get("query-params").transform(targetUid, permissionQuery),
2433
- ordering: "desc",
2434
- page: ctx.request.query.page,
2435
- pageSize: ctx.request.query.pageSize
2585
+ ordering: "desc"
2436
2586
  });
2437
2587
  const relationsUnion = uniqBy("id", concat(sanitizedRes.results, res.results));
2438
2588
  ctx.body = {
@@ -2464,7 +2614,7 @@ const createOrUpdateDocument = async (ctx, opts) => {
2464
2614
  throw new errors.ForbiddenError();
2465
2615
  }
2466
2616
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.update(query);
2467
- const { locale } = getDocumentLocaleAndStatus(body);
2617
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2468
2618
  const [documentVersion, otherDocumentVersion] = await Promise.all([
2469
2619
  findDocument(sanitizedQuery, model, { locale, status: "draft" }),
2470
2620
  // Find the first document to check if it exists
@@ -2501,12 +2651,11 @@ const singleTypes = {
2501
2651
  const { model } = ctx.params;
2502
2652
  const { query = {} } = ctx.request;
2503
2653
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2504
- const documentMetadata2 = getService$1("document-metadata");
2505
2654
  if (permissionChecker2.cannot.read()) {
2506
2655
  return ctx.forbidden();
2507
2656
  }
2508
2657
  const permissionQuery = await permissionChecker2.sanitizedQuery.read(query);
2509
- const { locale, status } = getDocumentLocaleAndStatus(query);
2658
+ const { locale, status } = await getDocumentLocaleAndStatus(query, model);
2510
2659
  const version = await findDocument(permissionQuery, model, { locale, status });
2511
2660
  if (!version) {
2512
2661
  if (permissionChecker2.cannot.create()) {
@@ -2516,8 +2665,10 @@ const singleTypes = {
2516
2665
  if (!document) {
2517
2666
  return ctx.notFound();
2518
2667
  }
2519
- const { meta } = await documentMetadata2.formatDocumentWithMetadata(
2668
+ const { meta } = await formatDocumentWithMetadata(
2669
+ permissionChecker2,
2520
2670
  model,
2671
+ // @ts-expect-error - fix types
2521
2672
  { id: document.documentId, locale, publishedAt: null },
2522
2673
  { availableLocales: true, availableStatus: false }
2523
2674
  );
@@ -2528,16 +2679,15 @@ const singleTypes = {
2528
2679
  return ctx.forbidden();
2529
2680
  }
2530
2681
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(version);
2531
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2682
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2532
2683
  },
2533
2684
  async createOrUpdate(ctx) {
2534
2685
  const { userAbility } = ctx.state;
2535
2686
  const { model } = ctx.params;
2536
- const documentMetadata2 = getService$1("document-metadata");
2537
2687
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2538
2688
  const document = await createOrUpdateDocument(ctx);
2539
2689
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(document);
2540
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2690
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2541
2691
  },
2542
2692
  async delete(ctx) {
2543
2693
  const { userAbility } = ctx.state;
@@ -2550,7 +2700,7 @@ const singleTypes = {
2550
2700
  }
2551
2701
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.delete(query);
2552
2702
  const populate = await buildPopulateFromQuery(sanitizedQuery, model);
2553
- const { locale } = getDocumentLocaleAndStatus(query);
2703
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2554
2704
  const documentLocales = await documentManager2.findLocales(void 0, model, {
2555
2705
  populate,
2556
2706
  locale
@@ -2573,7 +2723,6 @@ const singleTypes = {
2573
2723
  const { model } = ctx.params;
2574
2724
  const { query = {} } = ctx.request;
2575
2725
  const documentManager2 = getService$1("document-manager");
2576
- const documentMetadata2 = getService$1("document-metadata");
2577
2726
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2578
2727
  if (permissionChecker2.cannot.publish()) {
2579
2728
  return ctx.forbidden();
@@ -2588,11 +2737,12 @@ const singleTypes = {
2588
2737
  if (permissionChecker2.cannot.publish(document)) {
2589
2738
  throw new errors.ForbiddenError();
2590
2739
  }
2591
- const { locale } = getDocumentLocaleAndStatus(document);
2592
- return documentManager2.publish(document.documentId, model, { locale });
2740
+ const { locale } = await getDocumentLocaleAndStatus(document, model);
2741
+ const publishResult = await documentManager2.publish(document.documentId, model, { locale });
2742
+ return publishResult.at(0);
2593
2743
  });
2594
2744
  const sanitizedDocument = await permissionChecker2.sanitizeOutput(publishedDocument);
2595
- ctx.body = await documentMetadata2.formatDocumentWithMetadata(model, sanitizedDocument);
2745
+ ctx.body = await formatDocumentWithMetadata(permissionChecker2, model, sanitizedDocument);
2596
2746
  },
2597
2747
  async unpublish(ctx) {
2598
2748
  const { userAbility } = ctx.state;
@@ -2602,7 +2752,6 @@ const singleTypes = {
2602
2752
  query = {}
2603
2753
  } = ctx.request;
2604
2754
  const documentManager2 = getService$1("document-manager");
2605
- const documentMetadata2 = getService$1("document-metadata");
2606
2755
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2607
2756
  if (permissionChecker2.cannot.unpublish()) {
2608
2757
  return ctx.forbidden();
@@ -2611,7 +2760,7 @@ const singleTypes = {
2611
2760
  return ctx.forbidden();
2612
2761
  }
2613
2762
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.unpublish(query);
2614
- const { locale } = getDocumentLocaleAndStatus(body);
2763
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2615
2764
  const document = await findDocument(sanitizedQuery, model, { locale });
2616
2765
  if (!document) {
2617
2766
  return ctx.notFound();
@@ -2629,7 +2778,7 @@ const singleTypes = {
2629
2778
  ctx.body = await async.pipe(
2630
2779
  (document2) => documentManager2.unpublish(document2.documentId, model, { locale }),
2631
2780
  permissionChecker2.sanitizeOutput,
2632
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2781
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2633
2782
  )(document);
2634
2783
  });
2635
2784
  },
@@ -2638,13 +2787,12 @@ const singleTypes = {
2638
2787
  const { model } = ctx.params;
2639
2788
  const { body, query = {} } = ctx.request;
2640
2789
  const documentManager2 = getService$1("document-manager");
2641
- const documentMetadata2 = getService$1("document-metadata");
2642
2790
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2643
2791
  if (permissionChecker2.cannot.discard()) {
2644
2792
  return ctx.forbidden();
2645
2793
  }
2646
2794
  const sanitizedQuery = await permissionChecker2.sanitizedQuery.discard(query);
2647
- const { locale } = getDocumentLocaleAndStatus(body);
2795
+ const { locale } = await getDocumentLocaleAndStatus(body, model);
2648
2796
  const document = await findDocument(sanitizedQuery, model, { locale, status: "published" });
2649
2797
  if (!document) {
2650
2798
  return ctx.notFound();
@@ -2655,7 +2803,7 @@ const singleTypes = {
2655
2803
  ctx.body = await async.pipe(
2656
2804
  (document2) => documentManager2.discardDraft(document2.documentId, model, { locale }),
2657
2805
  permissionChecker2.sanitizeOutput,
2658
- (document2) => documentMetadata2.formatDocumentWithMetadata(model, document2)
2806
+ (document2) => formatDocumentWithMetadata(permissionChecker2, model, document2)
2659
2807
  )(document);
2660
2808
  },
2661
2809
  async countDraftRelations(ctx) {
@@ -2664,7 +2812,7 @@ const singleTypes = {
2664
2812
  const { query } = ctx.request;
2665
2813
  const documentManager2 = getService$1("document-manager");
2666
2814
  const permissionChecker2 = getService$1("permission-checker").create({ userAbility, model });
2667
- const { locale } = getDocumentLocaleAndStatus(query);
2815
+ const { locale } = await getDocumentLocaleAndStatus(query, model);
2668
2816
  if (permissionChecker2.cannot.read()) {
2669
2817
  return ctx.forbidden();
2670
2818
  }
@@ -2685,7 +2833,7 @@ const uid$1 = {
2685
2833
  async generateUID(ctx) {
2686
2834
  const { contentTypeUID, field, data } = await validateGenerateUIDInput(ctx.request.body);
2687
2835
  const { query = {} } = ctx.request;
2688
- const { locale } = getDocumentLocaleAndStatus(query);
2836
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2689
2837
  await validateUIDField(contentTypeUID, field);
2690
2838
  const uidService = getService$1("uid");
2691
2839
  ctx.body = {
@@ -2697,7 +2845,7 @@ const uid$1 = {
2697
2845
  ctx.request.body
2698
2846
  );
2699
2847
  const { query = {} } = ctx.request;
2700
- const { locale } = getDocumentLocaleAndStatus(query);
2848
+ const { locale } = await getDocumentLocaleAndStatus(query, contentTypeUID);
2701
2849
  await validateUIDField(contentTypeUID, field);
2702
2850
  const uidService = getService$1("uid");
2703
2851
  const isAvailable = await uidService.checkUIDAvailability({
@@ -3340,12 +3488,27 @@ const createPermissionChecker = (strapi2) => ({ userAbility, model }) => {
3340
3488
  ability: userAbility,
3341
3489
  model
3342
3490
  });
3343
- const toSubject = (entity) => entity ? permissionsManager.toSubject(entity, model) : model;
3491
+ const { actionProvider } = strapi2.service("admin::permission");
3492
+ const toSubject = (entity) => {
3493
+ return entity ? permissionsManager.toSubject(entity, model) : model;
3494
+ };
3344
3495
  const can = (action, entity, field) => {
3345
- return userAbility.can(action, toSubject(entity), field);
3496
+ const subject = toSubject(entity);
3497
+ const aliases = actionProvider.unstable_aliases(action, model);
3498
+ return (
3499
+ // Test the original action to see if it passes
3500
+ userAbility.can(action, subject, field) || // Else try every known alias if at least one of them succeed, then the user "can"
3501
+ aliases.some((alias) => userAbility.can(alias, subject, field))
3502
+ );
3346
3503
  };
3347
3504
  const cannot = (action, entity, field) => {
3348
- return userAbility.cannot(action, toSubject(entity), field);
3505
+ const subject = toSubject(entity);
3506
+ const aliases = actionProvider.unstable_aliases(action, model);
3507
+ return (
3508
+ // Test both the original action
3509
+ userAbility.cannot(action, subject, field) && // and every known alias, if all of them fail (cannot), then the user truly "cannot"
3510
+ aliases.every((alias) => userAbility.cannot(alias, subject, field))
3511
+ );
3349
3512
  };
3350
3513
  const sanitizeOutput = (data, { action = ACTIONS.read } = {}) => {
3351
3514
  return permissionsManager.sanitizeOutput(data, { subject: toSubject(data), action });
@@ -3488,7 +3651,7 @@ const permission = ({ strapi: strapi2 }) => ({
3488
3651
  await strapi2.service("admin::permission").actionProvider.registerMany(actions);
3489
3652
  }
3490
3653
  });
3491
- const { isVisibleAttribute: isVisibleAttribute$1 } = strapiUtils.contentTypes;
3654
+ const { isVisibleAttribute: isVisibleAttribute$1, isScalarAttribute, getDoesAttributeRequireValidation } = strapiUtils.contentTypes;
3492
3655
  const { isAnyToMany } = strapiUtils.relations;
3493
3656
  const { PUBLISHED_AT_ATTRIBUTE: PUBLISHED_AT_ATTRIBUTE$1 } = strapiUtils.contentTypes.constants;
3494
3657
  const isMorphToRelation = (attribute) => isRelation(attribute) && attribute.relation.includes("morphTo");
@@ -3579,6 +3742,42 @@ const getDeepPopulate = (uid2, {
3579
3742
  {}
3580
3743
  );
3581
3744
  };
3745
+ const getValidatableFieldsPopulate = (uid2, {
3746
+ initialPopulate = {},
3747
+ countMany = false,
3748
+ countOne = false,
3749
+ maxLevel = Infinity
3750
+ } = {}, level = 1) => {
3751
+ if (level > maxLevel) {
3752
+ return {};
3753
+ }
3754
+ const model = strapi.getModel(uid2);
3755
+ return Object.entries(model.attributes).reduce((populateAcc, [attributeName, attribute]) => {
3756
+ if (!getDoesAttributeRequireValidation(attribute)) {
3757
+ return populateAcc;
3758
+ }
3759
+ if (isScalarAttribute(attribute)) {
3760
+ return merge(populateAcc, {
3761
+ [attributeName]: true
3762
+ });
3763
+ }
3764
+ return merge(
3765
+ populateAcc,
3766
+ getPopulateFor(
3767
+ attributeName,
3768
+ model,
3769
+ {
3770
+ // @ts-expect-error - improve types
3771
+ initialPopulate: initialPopulate?.[attributeName],
3772
+ countMany,
3773
+ countOne,
3774
+ maxLevel
3775
+ },
3776
+ level
3777
+ )
3778
+ );
3779
+ }, {});
3780
+ };
3582
3781
  const getDeepPopulateDraftCount = (uid2) => {
3583
3782
  const model = strapi.getModel(uid2);
3584
3783
  let hasRelations = false;
@@ -3586,6 +3785,10 @@ const getDeepPopulateDraftCount = (uid2) => {
3586
3785
  const attribute = model.attributes[attributeName];
3587
3786
  switch (attribute.type) {
3588
3787
  case "relation": {
3788
+ const isMorphRelation = attribute.relation.toLowerCase().startsWith("morph");
3789
+ if (isMorphRelation) {
3790
+ break;
3791
+ }
3589
3792
  if (isVisibleAttribute$1(model, attributeName)) {
3590
3793
  populateAcc[attributeName] = {
3591
3794
  count: true,
@@ -3600,22 +3803,24 @@ const getDeepPopulateDraftCount = (uid2) => {
3600
3803
  attribute.component
3601
3804
  );
3602
3805
  if (childHasRelations) {
3603
- populateAcc[attributeName] = { populate: populate2 };
3806
+ populateAcc[attributeName] = {
3807
+ populate: populate2
3808
+ };
3604
3809
  hasRelations = true;
3605
3810
  }
3606
3811
  break;
3607
3812
  }
3608
3813
  case "dynamiczone": {
3609
- const dzPopulate = (attribute.components || []).reduce((acc, componentUID) => {
3610
- const { populate: populate2, hasRelations: childHasRelations } = getDeepPopulateDraftCount(componentUID);
3611
- if (childHasRelations) {
3814
+ const dzPopulateFragment = attribute.components?.reduce((acc, componentUID) => {
3815
+ const { populate: componentPopulate, hasRelations: componentHasRelations } = getDeepPopulateDraftCount(componentUID);
3816
+ if (componentHasRelations) {
3612
3817
  hasRelations = true;
3613
- return merge(acc, populate2);
3818
+ return { ...acc, [componentUID]: { populate: componentPopulate } };
3614
3819
  }
3615
3820
  return acc;
3616
3821
  }, {});
3617
- if (!isEmpty(dzPopulate)) {
3618
- populateAcc[attributeName] = { populate: dzPopulate };
3822
+ if (!isEmpty(dzPopulateFragment)) {
3823
+ populateAcc[attributeName] = { on: dzPopulateFragment };
3619
3824
  }
3620
3825
  break;
3621
3826
  }
@@ -3807,41 +4012,70 @@ const AVAILABLE_STATUS_FIELDS = [
3807
4012
  "updatedBy",
3808
4013
  "status"
3809
4014
  ];
3810
- const AVAILABLE_LOCALES_FIELDS = ["id", "locale", "updatedAt", "createdAt", "status"];
4015
+ const AVAILABLE_LOCALES_FIELDS = [
4016
+ "id",
4017
+ "locale",
4018
+ "updatedAt",
4019
+ "createdAt",
4020
+ "status",
4021
+ "publishedAt",
4022
+ "documentId"
4023
+ ];
3811
4024
  const CONTENT_MANAGER_STATUS = {
3812
4025
  PUBLISHED: "published",
3813
4026
  DRAFT: "draft",
3814
4027
  MODIFIED: "modified"
3815
4028
  };
3816
- const areDatesEqual = (date1, date2, threshold) => {
3817
- if (!date1 || !date2) {
4029
+ const getIsVersionLatestModification = (version, otherVersion) => {
4030
+ if (!version || !version.updatedAt) {
3818
4031
  return false;
3819
4032
  }
3820
- const time1 = new Date(date1).getTime();
3821
- const time2 = new Date(date2).getTime();
3822
- const difference2 = Math.abs(time1 - time2);
3823
- return difference2 <= threshold;
4033
+ const versionUpdatedAt = version?.updatedAt ? new Date(version.updatedAt).getTime() : 0;
4034
+ const otherUpdatedAt = otherVersion?.updatedAt ? new Date(otherVersion.updatedAt).getTime() : 0;
4035
+ return versionUpdatedAt > otherUpdatedAt;
3824
4036
  };
3825
4037
  const documentMetadata = ({ strapi: strapi2 }) => ({
3826
4038
  /**
3827
4039
  * Returns available locales of a document for the current status
3828
4040
  */
3829
- getAvailableLocales(uid2, version, allVersions) {
4041
+ async getAvailableLocales(uid2, version, allVersions, validatableFields = []) {
3830
4042
  const versionsByLocale = groupBy("locale", allVersions);
3831
4043
  delete versionsByLocale[version.locale];
3832
- return Object.values(versionsByLocale).map((localeVersions) => {
3833
- if (!contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2))) {
3834
- return pick(AVAILABLE_LOCALES_FIELDS, localeVersions[0]);
4044
+ const model = strapi2.getModel(uid2);
4045
+ const keysToKeep = [...AVAILABLE_LOCALES_FIELDS, ...validatableFields];
4046
+ const traversalFunction = async (localeVersion) => traverseEntity(
4047
+ ({ key }, { remove }) => {
4048
+ if (keysToKeep.includes(key)) {
4049
+ return;
4050
+ }
4051
+ remove(key);
4052
+ },
4053
+ { schema: model, getModel: strapi2.getModel.bind(strapi2) },
4054
+ // @ts-expect-error fix types DocumentVersion incompatible with Data
4055
+ localeVersion
4056
+ );
4057
+ const mappingResult = await async.map(
4058
+ Object.values(versionsByLocale),
4059
+ async (localeVersions) => {
4060
+ const mappedLocaleVersions = await async.map(
4061
+ localeVersions,
4062
+ traversalFunction
4063
+ );
4064
+ if (!contentTypes$1.hasDraftAndPublish(model)) {
4065
+ return mappedLocaleVersions[0];
4066
+ }
4067
+ const draftVersion = mappedLocaleVersions.find((v) => v.publishedAt === null);
4068
+ const otherVersions = mappedLocaleVersions.filter((v) => v.id !== draftVersion?.id);
4069
+ if (!draftVersion) {
4070
+ return;
4071
+ }
4072
+ return {
4073
+ ...draftVersion,
4074
+ status: this.getStatus(draftVersion, otherVersions)
4075
+ };
3835
4076
  }
3836
- const draftVersion = localeVersions.find((v) => v.publishedAt === null);
3837
- const otherVersions = localeVersions.filter((v) => v.id !== draftVersion?.id);
3838
- if (!draftVersion)
3839
- return;
3840
- return {
3841
- ...pick(AVAILABLE_LOCALES_FIELDS, draftVersion),
3842
- status: this.getStatus(draftVersion, otherVersions)
3843
- };
3844
- }).filter(Boolean);
4077
+ );
4078
+ return mappingResult.filter(Boolean);
3845
4079
  },
3846
4080
  /**
3847
4081
  * Returns available status of a document for the current locale
@@ -3879,26 +4113,37 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3879
4113
  });
3880
4114
  },
3881
4115
  getStatus(version, otherDocumentStatuses) {
3882
- const isDraft = version.publishedAt === null;
3883
- if (!otherDocumentStatuses?.length) {
3884
- return isDraft ? CONTENT_MANAGER_STATUS.DRAFT : CONTENT_MANAGER_STATUS.PUBLISHED;
4116
+ let draftVersion;
4117
+ let publishedVersion;
4118
+ if (version.publishedAt) {
4119
+ publishedVersion = version;
4120
+ } else {
4121
+ draftVersion = version;
3885
4122
  }
3886
- if (isDraft) {
3887
- const publishedVersion = otherDocumentStatuses?.find((d) => d.publishedAt !== null);
3888
- if (!publishedVersion) {
3889
- return CONTENT_MANAGER_STATUS.DRAFT;
3890
- }
4123
+ const otherVersion = otherDocumentStatuses?.at(0);
4124
+ if (otherVersion?.publishedAt) {
4125
+ publishedVersion = otherVersion;
4126
+ } else if (otherVersion) {
4127
+ draftVersion = otherVersion;
3891
4128
  }
3892
- if (areDatesEqual(version.updatedAt, otherDocumentStatuses.at(0)?.updatedAt, 500)) {
4129
+ if (!draftVersion)
3893
4130
  return CONTENT_MANAGER_STATUS.PUBLISHED;
3894
- }
3895
- return CONTENT_MANAGER_STATUS.MODIFIED;
4131
+ if (!publishedVersion)
4132
+ return CONTENT_MANAGER_STATUS.DRAFT;
4133
+ const isDraftModified = getIsVersionLatestModification(draftVersion, publishedVersion);
4134
+ return isDraftModified ? CONTENT_MANAGER_STATUS.MODIFIED : CONTENT_MANAGER_STATUS.PUBLISHED;
3896
4135
  },
4136
+ // TODO is it necessary to return metadata on every page of the CM
4137
+ // We could refactor this so the locales are only loaded when they're
4138
+ // needed. e.g. in the bulk locale action modal.
3897
4139
  async getMetadata(uid2, version, { availableLocales = true, availableStatus = true } = {}) {
4140
+ const populate = getValidatableFieldsPopulate(uid2);
3898
4141
  const versions = await strapi2.db.query(uid2).findMany({
3899
4142
  where: { documentId: version.documentId },
3900
- select: ["createdAt", "updatedAt", "locale", "publishedAt", "documentId"],
3901
4143
  populate: {
4144
+ // Populate only fields that require validation for bulk locale actions
4145
+ ...populate,
4146
+ // NOTE: creator fields are selected in this way to avoid exposing sensitive data
3902
4147
  createdBy: {
3903
4148
  select: ["id", "firstname", "lastname", "email"]
3904
4149
  },
@@ -3907,7 +4152,7 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3907
4152
  }
3908
4153
  }
3909
4154
  });
3910
- const availableLocalesResult = availableLocales ? this.getAvailableLocales(uid2, version, versions) : [];
4155
+ const availableLocalesResult = availableLocales ? await this.getAvailableLocales(uid2, version, versions, Object.keys(populate)) : [];
3911
4156
  const availableStatusResult = availableStatus ? this.getAvailableStatus(version, versions) : null;
3912
4157
  return {
3913
4158
  availableLocales: availableLocalesResult,
@@ -3920,8 +4165,15 @@ const documentMetadata = ({ strapi: strapi2 }) => ({
3920
4165
  * - Available status of the document for the current locale
3921
4166
  */
3922
4167
  async formatDocumentWithMetadata(uid2, document, opts = {}) {
3923
- if (!document)
3924
- return document;
4168
+ if (!document) {
4169
+ return {
4170
+ data: document,
4171
+ meta: {
4172
+ availableLocales: [],
4173
+ availableStatus: []
4174
+ }
4175
+ };
4176
+ }
3925
4177
  const hasDraftAndPublish = contentTypes$1.hasDraftAndPublish(strapi2.getModel(uid2));
3926
4178
  if (!hasDraftAndPublish) {
3927
4179
  opts.availableStatus = false;
@@ -3971,26 +4223,9 @@ const sumDraftCounts = (entity, uid2) => {
3971
4223
  }, 0);
3972
4224
  };
3973
4225
  const { ApplicationError } = errors;
3974
- const { ENTRY_PUBLISH, ENTRY_UNPUBLISH } = ALLOWED_WEBHOOK_EVENTS;
3975
4226
  const { PUBLISHED_AT_ATTRIBUTE } = contentTypes$1.constants;
3976
4227
  const omitPublishedAtField = omit(PUBLISHED_AT_ATTRIBUTE);
3977
4228
  const omitIdField = omit("id");
3978
- const emitEvent = async (uid2, event, document) => {
3979
- const modelDef = strapi.getModel(uid2);
3980
- const sanitizedDocument = await sanitize.sanitizers.defaultSanitizeOutput(
3981
- {
3982
- schema: modelDef,
3983
- getModel(uid22) {
3984
- return strapi.getModel(uid22);
3985
- }
3986
- },
3987
- document
3988
- );
3989
- strapi.eventHub.emit(event, {
3990
- model: modelDef.modelName,
3991
- entry: sanitizedDocument
3992
- });
3993
- };
3994
4229
  const documentManager = ({ strapi: strapi2 }) => {
3995
4230
  return {
3996
4231
  async findOne(id, uid2, opts = {}) {
@@ -4009,6 +4244,9 @@ const documentManager = ({ strapi: strapi2 }) => {
4009
4244
  } else if (opts.locale && opts.locale !== "*") {
4010
4245
  where.locale = opts.locale;
4011
4246
  }
4247
+ if (typeof opts.isPublished === "boolean") {
4248
+ where.publishedAt = { $notNull: opts.isPublished };
4249
+ }
4012
4250
  return strapi2.db.query(uid2).findMany({ populate: opts.populate, where });
4013
4251
  },
4014
4252
  async findMany(opts, uid2) {
@@ -4016,20 +4254,16 @@ const documentManager = ({ strapi: strapi2 }) => {
4016
4254
  return strapi2.documents(uid2).findMany(params);
4017
4255
  },
4018
4256
  async findPage(opts, uid2) {
4019
- const page = Number(opts?.page) || 1;
4020
- const pageSize = Number(opts?.pageSize) || 10;
4257
+ const params = pagination.withDefaultPagination(opts || {}, {
4258
+ maxLimit: 1e3
4259
+ });
4021
4260
  const [documents, total = 0] = await Promise.all([
4022
- strapi2.documents(uid2).findMany(opts),
4023
- strapi2.documents(uid2).count(opts)
4261
+ strapi2.documents(uid2).findMany(params),
4262
+ strapi2.documents(uid2).count(params)
4024
4263
  ]);
4025
4264
  return {
4026
4265
  results: documents,
4027
- pagination: {
4028
- page,
4029
- pageSize,
4030
- pageCount: Math.ceil(total / pageSize),
4031
- total
4032
- }
4266
+ pagination: pagination.transformPagedPaginationInfo(params, total)
4033
4267
  };
4034
4268
  },
4035
4269
  async create(uid2, opts = {}) {
@@ -4046,10 +4280,7 @@ const documentManager = ({ strapi: strapi2 }) => {
4046
4280
  async clone(id, body, uid2) {
4047
4281
  const populate = await buildDeepPopulate(uid2);
4048
4282
  const params = {
4049
- data: {
4050
- ...omitIdField(body),
4051
- [PUBLISHED_AT_ATTRIBUTE]: null
4052
- },
4283
+ data: omitIdField(body),
4053
4284
  populate
4054
4285
  };
4055
4286
  return strapi2.documents(uid2).clone({ ...params, documentId: id }).then((result) => result?.entries.at(0));
@@ -4075,70 +4306,36 @@ const documentManager = ({ strapi: strapi2 }) => {
4075
4306
  return {};
4076
4307
  },
4077
4308
  // FIXME: handle relations
4078
- async deleteMany(opts, uid2) {
4079
- const docs = await strapi2.documents(uid2).findMany(opts);
4080
- for (const doc of docs) {
4081
- await strapi2.documents(uid2).delete({ documentId: doc.documentId });
4082
- }
4083
- return { count: docs.length };
4309
+ async deleteMany(documentIds, uid2, opts = {}) {
4310
+ const deletedEntries = await strapi2.db.transaction(async () => {
4311
+ return Promise.all(documentIds.map(async (id) => this.delete(id, uid2, opts)));
4312
+ });
4313
+ return { count: deletedEntries.length };
4084
4314
  },
4085
4315
  async publish(id, uid2, opts = {}) {
4086
4316
  const populate = await buildDeepPopulate(uid2);
4087
4317
  const params = { ...opts, populate };
4088
- return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries.at(0));
4318
+ return strapi2.documents(uid2).publish({ ...params, documentId: id }).then((result) => result?.entries);
4089
4319
  },
4090
- async publishMany(entities, uid2) {
4091
- if (!entities.length) {
4092
- return null;
4093
- }
4094
- await Promise.all(
4095
- entities.map((document) => {
4096
- return strapi2.entityValidator.validateEntityCreation(
4097
- strapi2.getModel(uid2),
4098
- document,
4099
- void 0,
4100
- // @ts-expect-error - FIXME: entity here is unnecessary
4101
- document
4102
- );
4103
- })
4104
- );
4105
- const entitiesToPublish = entities.filter((doc) => !doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4106
- const filters = { id: { $in: entitiesToPublish } };
4107
- const data = { [PUBLISHED_AT_ATTRIBUTE]: /* @__PURE__ */ new Date() };
4108
- const populate = await buildDeepPopulate(uid2);
4109
- const publishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4110
- where: filters,
4111
- data
4112
- });
4113
- const publishedEntities = await strapi2.db.query(uid2).findMany({
4114
- where: filters,
4115
- populate
4320
+ async publishMany(uid2, documentIds, locale) {
4321
+ return strapi2.db.transaction(async () => {
4322
+ const results = await Promise.all(
4323
+ documentIds.map((documentId) => this.publish(documentId, uid2, { locale }))
4324
+ );
4325
+ const publishedEntitiesCount = results.flat().filter(Boolean).length;
4326
+ return publishedEntitiesCount;
4116
4327
  });
4117
- await Promise.all(
4118
- publishedEntities.map((doc) => emitEvent(uid2, ENTRY_PUBLISH, doc))
4119
- );
4120
- return publishedEntitiesCount;
4121
4328
  },
4122
- async unpublishMany(documents, uid2) {
4123
- if (!documents.length) {
4124
- return null;
4125
- }
4126
- const entitiesToUnpublish = documents.filter((doc) => doc[PUBLISHED_AT_ATTRIBUTE]).map((doc) => doc.id);
4127
- const filters = { id: { $in: entitiesToUnpublish } };
4128
- const data = { [PUBLISHED_AT_ATTRIBUTE]: null };
4129
- const populate = await buildDeepPopulate(uid2);
4130
- const unpublishedEntitiesCount = await strapi2.db.query(uid2).updateMany({
4131
- where: filters,
4132
- data
4133
- });
4134
- const unpublishedEntities = await strapi2.db.query(uid2).findMany({
4135
- where: filters,
4136
- populate
4329
+ async unpublishMany(documentIds, uid2, opts = {}) {
4330
+ const unpublishedEntries = await strapi2.db.transaction(async () => {
4331
+ return Promise.all(
4332
+ documentIds.map(
4333
+ (id) => strapi2.documents(uid2).unpublish({ ...opts, documentId: id }).then((result) => result?.entries)
4334
+ )
4335
+ );
4137
4336
  });
4138
- await Promise.all(
4139
- unpublishedEntities.map((doc) => emitEvent(uid2, ENTRY_UNPUBLISH, doc))
4140
- );
4141
- return unpublishedEntitiesCount;
4337
+ const unpublishedEntitiesCount = unpublishedEntries.flat().filter(Boolean).length;
4338
+ return { count: unpublishedEntitiesCount };
4142
4339
  },
4143
4340
  async unpublish(id, uid2, opts = {}) {
4144
4341
  const populate = await buildDeepPopulate(uid2);
@@ -4163,16 +4360,20 @@ const documentManager = ({ strapi: strapi2 }) => {
4163
4360
  }
4164
4361
  return sumDraftCounts(document, uid2);
4165
4362
  },
4166
- async countManyEntriesDraftRelations(ids, uid2, locale) {
4363
+ async countManyEntriesDraftRelations(documentIds, uid2, locale) {
4167
4364
  const { populate, hasRelations } = getDeepPopulateDraftCount(uid2);
4168
4365
  if (!hasRelations) {
4169
4366
  return 0;
4170
4367
  }
4368
+ let localeFilter = {};
4369
+ if (locale) {
4370
+ localeFilter = Array.isArray(locale) ? { locale: { $in: locale } } : { locale };
4371
+ }
4171
4372
  const entities = await strapi2.db.query(uid2).findMany({
4172
4373
  populate,
4173
4374
  where: {
4174
- id: { $in: ids },
4175
- ...locale ? { locale } : {}
4375
+ documentId: { $in: documentIds },
4376
+ ...localeFilter
4176
4377
  }
4177
4378
  });
4178
4379
  const totalNumberDraftRelations = entities.reduce(