vendor/ibexa/content-forms/src/lib/Form/Processor/ContentFormProcessor.php line 247

Open in your IDE?
  1. <?php
  2. /**
  3. * @copyright Copyright (C) Ibexa AS. All rights reserved.
  4. * @license For full copyright and license information view LICENSE file distributed with this source code.
  5. */
  6. declare(strict_types=1);
  7. namespace Ibexa\ContentForms\Form\Processor;
  8. use Ibexa\ContentForms\Data\Content\ContentCreateData;
  9. use Ibexa\ContentForms\Data\Content\ContentUpdateData;
  10. use Ibexa\ContentForms\Data\NewnessCheckable;
  11. use Ibexa\ContentForms\Event\ContentFormEvents;
  12. use Ibexa\ContentForms\Event\FormActionEvent;
  13. use Ibexa\Contracts\Core\Repository\ContentService;
  14. use Ibexa\Contracts\Core\Repository\LocationService;
  15. use Ibexa\Contracts\Core\Repository\Values\Content\Content;
  16. use Ibexa\Contracts\Core\Repository\Values\Content\ContentStruct;
  17. use Ibexa\Contracts\Core\Repository\Values\Content\Location;
  18. use Ibexa\Contracts\Core\Repository\Values\Content\VersionInfo;
  19. use Ibexa\Core\Base\Exceptions\InvalidArgumentException;
  20. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  21. use Symfony\Component\HttpFoundation\RedirectResponse;
  22. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  23. use Symfony\Component\Routing\RouterInterface;
  24. /**
  25. * Listens for and processes RepositoryForm events: publish, remove draft, save draft...
  26. */
  27. class ContentFormProcessor implements EventSubscriberInterface
  28. {
  29. /** @var \Ibexa\Contracts\Core\Repository\ContentService */
  30. private $contentService;
  31. /** @var \Ibexa\Contracts\Core\Repository\LocationService */
  32. private $locationService;
  33. /** @var \Symfony\Component\Routing\RouterInterface */
  34. private $router;
  35. /**
  36. * @param \Ibexa\Contracts\Core\Repository\ContentService $contentService
  37. * @param \Ibexa\Contracts\Core\Repository\LocationService $locationService
  38. * @param \Symfony\Component\Routing\RouterInterface $router
  39. * @param \Ibexa\Contracts\Core\Repository\URLAliasService $urlAliasService
  40. */
  41. public function __construct(
  42. ContentService $contentService,
  43. LocationService $locationService,
  44. RouterInterface $router
  45. ) {
  46. $this->contentService = $contentService;
  47. $this->locationService = $locationService;
  48. $this->router = $router;
  49. }
  50. /**
  51. * @return array
  52. */
  53. public static function getSubscribedEvents(): array
  54. {
  55. return [
  56. ContentFormEvents::CONTENT_PUBLISH => ['processPublish', 10],
  57. ContentFormEvents::CONTENT_PUBLISH_AND_EDIT => ['processPublishAndEdit', 10],
  58. ContentFormEvents::CONTENT_CANCEL => ['processCancel', 10],
  59. ContentFormEvents::CONTENT_SAVE_DRAFT => ['processSaveDraft', 10],
  60. ContentFormEvents::CONTENT_SAVE_DRAFT_AND_CLOSE => ['processSaveDraftAndClose', 10],
  61. ContentFormEvents::CONTENT_CREATE_DRAFT => ['processCreateDraft', 10],
  62. ];
  63. }
  64. /**
  65. * @param \Ibexa\ContentForms\Event\FormActionEvent $event
  66. *
  67. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  68. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException
  69. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException
  70. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  71. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
  72. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  73. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  74. */
  75. public function processSaveDraft(FormActionEvent $event)
  76. {
  77. /** @var \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data */
  78. $data = $event->getData();
  79. $form = $event->getForm();
  80. $formConfig = $form->getConfig();
  81. $languageCode = $formConfig->getOption('languageCode');
  82. $draft = $this->saveDraft($data, $languageCode, []);
  83. $referrerLocation = $event->getOption('referrerLocation');
  84. $contentLocation = $this->resolveLocation($draft, $referrerLocation, $data);
  85. $event->setPayload('content', $draft);
  86. $event->setPayload('is_new', $draft->contentInfo->isDraft());
  87. $defaultUrl = $this->router->generate('ibexa.content.draft.edit', [
  88. 'contentId' => $draft->id,
  89. 'versionNo' => $draft->getVersionInfo()->versionNo,
  90. 'language' => $languageCode,
  91. 'locationId' => null !== $contentLocation ? $contentLocation->id : null,
  92. ]);
  93. $event->setResponse(new RedirectResponse($formConfig->getAction() ?: $defaultUrl));
  94. }
  95. /**
  96. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  97. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException
  98. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException
  99. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  100. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
  101. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  102. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  103. */
  104. public function processSaveDraftAndClose(FormActionEvent $event): void
  105. {
  106. /** @var \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data */
  107. $data = $event->getData();
  108. $form = $event->getForm();
  109. $formConfig = $form->getConfig();
  110. $languageCode = $formConfig->getOption('languageCode');
  111. $draft = $this->saveDraft($data, $languageCode, []);
  112. $referrerLocation = $event->getOption('referrerLocation');
  113. if ($referrerLocation === null) {
  114. $versionInfo = $data->contentDraft->getVersionInfo();
  115. $contentInfo = $versionInfo->getContentInfo();
  116. $currentVersion = $this->contentService->loadContentByContentInfo($contentInfo);
  117. if ($currentVersion->getVersionInfo()->status === VersionInfo::STATUS_PUBLISHED) {
  118. $publishedContentInfo = $currentVersion->getVersionInfo()->getContentInfo();
  119. $redirectionLocationId = $publishedContentInfo->mainLocationId;
  120. $redirectionContentId = $publishedContentInfo->getId();
  121. } else {
  122. $parentLocation = $this->locationService->loadParentLocationsForDraftContent($versionInfo)[0];
  123. $redirectionLocationId = $parentLocation->id;
  124. $redirectionContentId = $parentLocation->contentId;
  125. }
  126. } else {
  127. $redirectionLocationId = $referrerLocation->id;
  128. $redirectionContentId = $referrerLocation->contentId;
  129. }
  130. $event->setPayload('content', $draft);
  131. $event->setPayload('is_new', $draft->contentInfo->isDraft());
  132. $defaultUrl = $this->router->generate(
  133. 'ibexa.content.view',
  134. [
  135. 'contentId' => $redirectionContentId,
  136. 'locationId' => $redirectionLocationId,
  137. ]
  138. );
  139. $event->setResponse(new RedirectResponse($formConfig->getAction() ?: $defaultUrl));
  140. }
  141. /**
  142. * @param \Ibexa\ContentForms\Event\FormActionEvent $event
  143. *
  144. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  145. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException
  146. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException
  147. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  148. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  149. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  150. */
  151. public function processPublish(FormActionEvent $event)
  152. {
  153. /** @var \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data */
  154. $data = $event->getData();
  155. $form = $event->getForm();
  156. $referrerLocation = $event->getOption('referrerLocation');
  157. $draft = $this->saveDraft($data, $form->getConfig()->getOption('languageCode'));
  158. $versionInfo = $draft->versionInfo;
  159. $content = $this->contentService->publishVersion($versionInfo, [$versionInfo->initialLanguageCode]);
  160. $event->setPayload('content', $content);
  161. $event->setPayload('is_new', $draft->contentInfo->isDraft());
  162. $locationId = $referrerLocation !== null && $data instanceof ContentUpdateData
  163. ? $referrerLocation->id
  164. : $content->contentInfo->mainLocationId;
  165. $contentId = $content->id;
  166. $redirectUrl = $form['redirectUrlAfterPublish']->getData() ?: $this->router->generate(
  167. 'ibexa.content.view',
  168. [
  169. 'contentId' => $contentId,
  170. 'locationId' => $locationId,
  171. 'publishedContentId' => $contentId,
  172. ]
  173. );
  174. $event->setResponse(new RedirectResponse($redirectUrl));
  175. }
  176. /**
  177. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  178. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException
  179. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException
  180. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  181. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  182. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  183. */
  184. public function processPublishAndEdit(FormActionEvent $event)
  185. {
  186. /** @var \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data */
  187. $data = $event->getData();
  188. $form = $event->getForm();
  189. $referrerLocation = $event->getOption('referrerLocation');
  190. $formConfig = $form->getConfig();
  191. $languageCode = $formConfig->getOption('languageCode');
  192. $draft = $this->saveDraft($data, $languageCode);
  193. $versionInfo = $draft->versionInfo;
  194. $content = $this->contentService->publishVersion($versionInfo, [$versionInfo->initialLanguageCode]);
  195. $contentInfo = $content->contentInfo;
  196. $contentVersionInfo = $content->getVersionInfo();
  197. $newDraft = $this->contentService->createContentDraft($contentInfo, $contentVersionInfo);
  198. $event->setPayload('content', $newDraft);
  199. $event->setPayload('is_new', $newDraft->contentInfo->isDraft());
  200. $redirectUrl = $this->router->generate('ibexa.content.draft.edit', [
  201. 'contentId' => $newDraft->id,
  202. 'versionNo' => $newDraft->getVersionInfo()->versionNo,
  203. 'language' => $newDraft->contentInfo->mainLanguageCode,
  204. 'locationId' => null !== $referrerLocation ? $referrerLocation->id : null,
  205. ]);
  206. $event->setResponse(new RedirectResponse($redirectUrl));
  207. }
  208. /**
  209. * @param \Ibexa\ContentForms\Event\FormActionEvent $event
  210. *
  211. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  212. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  213. */
  214. public function processCancel(FormActionEvent $event)
  215. {
  216. /** @var \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data */
  217. $data = $event->getData();
  218. if ($data->isNew()) {
  219. $parentLocation = $this->locationService->loadLocation($data->getLocationStructs()[0]->parentLocationId);
  220. $response = new RedirectResponse($this->router->generate(
  221. 'ibexa.content.view',
  222. [
  223. 'contentId' => $parentLocation->contentId,
  224. 'locationId' => $parentLocation->id,
  225. ]
  226. ));
  227. $event->setResponse($response);
  228. return;
  229. }
  230. $content = $data->contentDraft;
  231. $contentInfo = $content->contentInfo;
  232. $versionInfo = $data->contentDraft->getVersionInfo();
  233. $event->setPayload('content', $content);
  234. // if there is only one version you have to remove whole content instead of a version itself
  235. if (1 === count($this->contentService->loadVersions($contentInfo))) {
  236. $parentLocation = $this->locationService->loadParentLocationsForDraftContent($versionInfo)[0];
  237. $redirectionLocationId = $parentLocation->id;
  238. $redirectionContentId = $parentLocation->contentId;
  239. } else {
  240. $redirectionLocationId = $contentInfo->mainLocationId;
  241. $redirectionContentId = $contentInfo->id;
  242. }
  243. $this->contentService->deleteVersion($versionInfo);
  244. $url = $this->router->generate(
  245. 'ibexa.content.view',
  246. [
  247. 'contentId' => $redirectionContentId,
  248. 'locationId' => $redirectionLocationId,
  249. ],
  250. UrlGeneratorInterface::ABSOLUTE_URL
  251. );
  252. $event->setResponse(new RedirectResponse($url));
  253. }
  254. /**
  255. * @param \Ibexa\ContentForms\Event\FormActionEvent $event
  256. *
  257. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
  258. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  259. */
  260. public function processCreateDraft(FormActionEvent $event)
  261. {
  262. /** @var $createContentDraft \Ibexa\ContentForms\Data\Content\CreateContentDraftData */
  263. $createContentDraft = $event->getData();
  264. $contentInfo = $this->contentService->loadContentInfo((int)$createContentDraft->contentId);
  265. $versionInfo = $this->contentService->loadVersionInfo($contentInfo, (int)$createContentDraft->fromVersionNo);
  266. $contentDraft = $this->contentService->createContentDraft($contentInfo, $versionInfo);
  267. $referrerLocation = $event->getOption('referrerLocation');
  268. $event->setPayload('content', $contentDraft);
  269. $event->setPayload('is_new', $contentDraft->contentInfo->isDraft());
  270. $contentEditUrl = $this->router->generate('ibexa.content.draft.edit', [
  271. 'contentId' => $contentDraft->id,
  272. 'versionNo' => $contentDraft->getVersionInfo()->versionNo,
  273. 'language' => $contentDraft->contentInfo->mainLanguageCode,
  274. 'locationId' => null !== $referrerLocation ? $referrerLocation->id : null,
  275. ]);
  276. $event->setResponse(new RedirectResponse($contentEditUrl));
  277. }
  278. /**
  279. * Saves content draft corresponding to $data.
  280. * Depending on the nature of $data (create or update data), the draft will either be created or simply updated.
  281. *
  282. * @param \Ibexa\Contracts\Core\Repository\Values\Content\ContentStruct|\Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data
  283. * @param $languageCode
  284. *
  285. * @return \Ibexa\Contracts\Core\Repository\Values\Content\Content
  286. *
  287. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\BadStateException
  288. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentFieldValidationException
  289. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\ContentValidationException
  290. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\InvalidArgumentException
  291. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  292. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  293. */
  294. private function saveDraft(ContentStruct $data, string $languageCode, ?array $fieldIdentifiersToValidate = null)
  295. {
  296. $mainLanguageCode = $this->resolveMainLanguageCode($data);
  297. foreach ($data->fieldsData as $fieldDefIdentifier => $fieldData) {
  298. if ($mainLanguageCode != $languageCode && !$fieldData->fieldDefinition->isTranslatable) {
  299. continue;
  300. }
  301. $data->setField($fieldDefIdentifier, $fieldData->value, $languageCode);
  302. }
  303. if ($data->isNew()) {
  304. $contentDraft = $this->contentService->createContent($data, $data->getLocationStructs(), $fieldIdentifiersToValidate);
  305. } else {
  306. $contentDraft = $this->contentService->updateContent($data->contentDraft->getVersionInfo(), $data, $fieldIdentifiersToValidate);
  307. }
  308. return $contentDraft;
  309. }
  310. /**
  311. * @param \Ibexa\ContentForms\Data\Content\ContentCreateData|\Ibexa\ContentForms\Data\Content\ContentUpdateData $data
  312. *
  313. * @return string
  314. *
  315. * @throws \Ibexa\Core\Base\Exceptions\InvalidArgumentException
  316. */
  317. private function resolveMainLanguageCode($data): string
  318. {
  319. if (!$data instanceof ContentUpdateData && !$data instanceof ContentCreateData) {
  320. throw new InvalidArgumentException(
  321. '$data',
  322. 'Expected ContentUpdateData or ContentCreateData'
  323. );
  324. }
  325. return $data->isNew()
  326. ? $data->mainLanguageCode
  327. : $data->contentDraft->getVersionInfo()->getContentInfo()->mainLanguageCode;
  328. }
  329. /**
  330. * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content
  331. * @param \Ibexa\Contracts\Core\Repository\Values\Content\Location|null $referrerLocation
  332. * @param \Ibexa\ContentForms\Data\NewnessCheckable $data
  333. *
  334. * @return \Ibexa\Contracts\Core\Repository\Values\Content\Location|null
  335. *
  336. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\NotFoundException
  337. * @throws \Ibexa\Contracts\Core\Repository\Exceptions\UnauthorizedException
  338. */
  339. private function resolveLocation(Content $content, ?Location $referrerLocation, NewnessCheckable $data): ?Location
  340. {
  341. if ($data->isNew() || (!$content->contentInfo->published && null === $content->contentInfo->mainLocationId)) {
  342. return null; // no location exists until new content is published
  343. }
  344. return $referrerLocation ?? $this->locationService->loadLocation($content->contentInfo->mainLocationId);
  345. }
  346. }
  347. class_alias(ContentFormProcessor::class, 'EzSystems\EzPlatformContentForms\Form\Processor\ContentFormProcessor');