diff --git a/Api/Extension/SelectionContainerCodeExtension.php b/Api/Extension/SelectionContainerCodeExtension.php new file mode 100644 index 0000000..a3333ad --- /dev/null +++ b/Api/Extension/SelectionContainerCodeExtension.php @@ -0,0 +1,49 @@ +requestStack->getCurrentRequest()?->query->get('container_code'); + + if (null === $containerCode || '' === $containerCode) { + return; + } + + $query + ->useSelectionContainerAssociatedSelectionQuery() + ->useSelectionContainerQuery() + ->filterByCode($containerCode) + ->endUse() + ->endUse(); + } +} diff --git a/Api/Resource/Selection.php b/Api/Resource/Selection.php new file mode 100644 index 0000000..59c2a24 --- /dev/null +++ b/Api/Resource/Selection.php @@ -0,0 +1,187 @@ + [self::GROUP_FRONT_READ, self::GROUP_FRONT_READ_SINGLE, Content::GROUP_FRONT_READ]], + ), + ], + normalizationContext: ['groups' => [self::GROUP_FRONT_READ, Content::GROUP_FRONT_READ]], +)] +#[ApiFilter( + filterClass: SearchFilter::class, + properties: [ + 'id', + 'code', + ], +)] +#[ApiFilter( + filterClass: BooleanFilter::class, + properties: [ + 'visible', + ], +)] +#[ApiFilter( + filterClass: OrderFilter::class, + properties: [ + 'position', + ], +)] +class Selection extends AbstractTranslatableResource +{ + public const GROUP_FRONT_READ = 'front:selection:read'; + public const GROUP_FRONT_READ_SINGLE = 'front:selection:read:single'; + + #[Groups([self::GROUP_FRONT_READ])] + public ?int $id = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?string $code = null; + + #[Groups([self::GROUP_FRONT_READ])] + public bool $visible; + + #[Groups([self::GROUP_FRONT_READ])] + public ?int $position = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?\DateTime $createdAt = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?\DateTime $updatedAt = null; + + #[Groups([self::GROUP_FRONT_READ])] + public I18nCollection $i18ns; + + /** + * Related contents, through the selection_content join table. + * + * @var SelectionContent[] + */ + #[Relation(targetResource: SelectionContent::class)] + #[Groups([self::GROUP_FRONT_READ])] + public array $selectionContents = []; + + public function getId(): ?int + { + return $this->id; + } + + public function setId(?int $id): self + { + $this->id = $id; + + return $this; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(?string $code): self + { + $this->code = $code; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + public function setVisible(bool $visible): self + { + $this->visible = $visible; + + return $this; + } + + public function getPosition(): ?int + { + return $this->position; + } + + public function setPosition(?int $position): self + { + $this->position = $position; + + return $this; + } + + public function getCreatedAt(): ?\DateTime + { + return $this->createdAt; + } + + public function setCreatedAt(?\DateTime $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTime + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTime $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + /** + * @return SelectionContent[] + */ + public function getSelectionContents(): array + { + return $this->selectionContents; + } + + /** + * @param SelectionContent[] $selectionContents + */ + public function setSelectionContents(array $selectionContents): self + { + $this->selectionContents = $selectionContents; + + return $this; + } + + public static function getPropelRelatedTableMap(): ?TableMap + { + return new SelectionTableMap(); + } + + public static function getI18nResourceClass(): string + { + return SelectionI18n::class; + } +} diff --git a/Api/Resource/SelectionContainer.php b/Api/Resource/SelectionContainer.php new file mode 100644 index 0000000..72a226a --- /dev/null +++ b/Api/Resource/SelectionContainer.php @@ -0,0 +1,158 @@ + [self::GROUP_FRONT_READ, self::GROUP_FRONT_READ_SINGLE]], + ), + ], + normalizationContext: ['groups' => [self::GROUP_FRONT_READ]], +)] +#[ApiFilter( + filterClass: SearchFilter::class, + properties: [ + 'id', + 'code', + ], +)] +#[ApiFilter( + filterClass: BooleanFilter::class, + properties: [ + 'visible', + ], +)] +#[ApiFilter( + filterClass: OrderFilter::class, + properties: [ + 'position', + ], +)] +class SelectionContainer extends AbstractTranslatableResource +{ + public const GROUP_FRONT_READ = 'front:selection_container:read'; + public const GROUP_FRONT_READ_SINGLE = 'front:selection_container:read:single'; + + #[Groups([self::GROUP_FRONT_READ])] + public ?int $id = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?string $code = null; + + #[Groups([self::GROUP_FRONT_READ])] + public bool $visible; + + #[Groups([self::GROUP_FRONT_READ])] + public ?int $position = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?\DateTime $createdAt = null; + + #[Groups([self::GROUP_FRONT_READ])] + public ?\DateTime $updatedAt = null; + + #[Groups([self::GROUP_FRONT_READ])] + public I18nCollection $i18ns; + + public function getId(): ?int + { + return $this->id; + } + + public function setId(?int $id): self + { + $this->id = $id; + + return $this; + } + + public function getCode(): ?string + { + return $this->code; + } + + public function setCode(?string $code): self + { + $this->code = $code; + + return $this; + } + + public function isVisible(): bool + { + return $this->visible; + } + + public function setVisible(bool $visible): self + { + $this->visible = $visible; + + return $this; + } + + public function getPosition(): ?int + { + return $this->position; + } + + public function setPosition(?int $position): self + { + $this->position = $position; + + return $this; + } + + public function getCreatedAt(): ?\DateTime + { + return $this->createdAt; + } + + public function setCreatedAt(?\DateTime $createdAt): self + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getUpdatedAt(): ?\DateTime + { + return $this->updatedAt; + } + + public function setUpdatedAt(?\DateTime $updatedAt): self + { + $this->updatedAt = $updatedAt; + + return $this; + } + + public static function getPropelRelatedTableMap(): ?TableMap + { + return new SelectionContainerTableMap(); + } + + public static function getI18nResourceClass(): string + { + return SelectionContainerI18n::class; + } +} diff --git a/Api/Resource/SelectionContainerI18n.php b/Api/Resource/SelectionContainerI18n.php new file mode 100644 index 0000000..91593cf --- /dev/null +++ b/Api/Resource/SelectionContainerI18n.php @@ -0,0 +1,116 @@ +title; + } + + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } + + public function getChapo(): ?string + { + return $this->chapo; + } + + public function setChapo(?string $chapo): self + { + $this->chapo = $chapo; + + return $this; + } + + public function getPostscriptum(): ?string + { + return $this->postscriptum; + } + + public function setPostscriptum(?string $postscriptum): self + { + $this->postscriptum = $postscriptum; + + return $this; + } + + public function getMetaTitle(): ?string + { + return $this->metaTitle; + } + + public function setMetaTitle(?string $metaTitle): self + { + $this->metaTitle = $metaTitle; + + return $this; + } + + public function getMetaDescription(): ?string + { + return $this->metaDescription; + } + + public function setMetaDescription(?string $metaDescription): self + { + $this->metaDescription = $metaDescription; + + return $this; + } + + public function getMetaKeywords(): ?string + { + return $this->metaKeywords; + } + + public function setMetaKeywords(?string $metaKeywords): self + { + $this->metaKeywords = $metaKeywords; + + return $this; + } +} diff --git a/Api/Resource/SelectionContent.php b/Api/Resource/SelectionContent.php new file mode 100644 index 0000000..8a1338d --- /dev/null +++ b/Api/Resource/SelectionContent.php @@ -0,0 +1,84 @@ + [self::GROUP_FRONT_READ, Content::GROUP_FRONT_READ]], +)] +#[CompositeIdentifiers(['selection', 'content'])] +class SelectionContent implements PropelResourceInterface +{ + use PropelResourceTrait; + + public const GROUP_FRONT_READ = 'front:selection_content:read'; + + #[Relation(targetResource: Selection::class)] + #[Groups([self::GROUP_FRONT_READ])] + public Selection $selection; + + #[Relation(targetResource: Content::class)] + #[Groups([self::GROUP_FRONT_READ, Selection::GROUP_FRONT_READ])] + public Content $content; + + #[Groups([self::GROUP_FRONT_READ, Selection::GROUP_FRONT_READ])] + public ?int $position = null; + + public function getSelection(): Selection + { + return $this->selection; + } + + public function setSelection(Selection $selection): self + { + $this->selection = $selection; + + return $this; + } + + public function getContent(): Content + { + return $this->content; + } + + public function setContent(Content $content): self + { + $this->content = $content; + + return $this; + } + + public function getPosition(): ?int + { + return $this->position; + } + + public function setPosition(?int $position): self + { + $this->position = $position; + + return $this; + } + + public static function getPropelRelatedTableMap(): ?TableMap + { + return new SelectionContentTableMap(); + } +} diff --git a/Api/Resource/SelectionI18n.php b/Api/Resource/SelectionI18n.php new file mode 100644 index 0000000..1d5ff64 --- /dev/null +++ b/Api/Resource/SelectionI18n.php @@ -0,0 +1,116 @@ +title; + } + + public function setTitle(?string $title): self + { + $this->title = $title; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } + + public function getChapo(): ?string + { + return $this->chapo; + } + + public function setChapo(?string $chapo): self + { + $this->chapo = $chapo; + + return $this; + } + + public function getPostscriptum(): ?string + { + return $this->postscriptum; + } + + public function setPostscriptum(?string $postscriptum): self + { + $this->postscriptum = $postscriptum; + + return $this; + } + + public function getMetaTitle(): ?string + { + return $this->metaTitle; + } + + public function setMetaTitle(?string $metaTitle): self + { + $this->metaTitle = $metaTitle; + + return $this; + } + + public function getMetaDescription(): ?string + { + return $this->metaDescription; + } + + public function setMetaDescription(?string $metaDescription): self + { + $this->metaDescription = $metaDescription; + + return $this; + } + + public function getMetaKeywords(): ?string + { + return $this->metaKeywords; + } + + public function setMetaKeywords(?string $metaKeywords): self + { + $this->metaKeywords = $metaKeywords; + + return $this; + } +} diff --git a/Controller/SelectionUpdateController.php b/Controller/SelectionUpdateController.php index 7e2edec..a110fed 100644 --- a/Controller/SelectionUpdateController.php +++ b/Controller/SelectionUpdateController.php @@ -11,6 +11,7 @@ use Selection\Model\Selection as SelectionModel; use Selection\Model\SelectionContainerAssociatedSelection; use Selection\Model\SelectionContentQuery; +use Selection\Model\SelectionI18nQuery; use Selection\Model\SelectionProductQuery; use Selection\Model\SelectionQuery; use Selection\Selection; @@ -349,7 +350,7 @@ protected function getObjectFromEvent($event): mixed protected function getExistingObject(): ?\Propel\Runtime\ActiveRecord\ActiveRecordInterface { $selection = SelectionQuery::create() - ->findPk($this->getRequest()->request->get('selectionId', $this->getRequest()->query->get('selectionId', 0))); + ->findPk($this->getRequest()->attributes->get('selectionId')); if (null !== $selection) { $selection->setLocale($this->getCurrentEditionLocale()); @@ -393,12 +394,13 @@ protected function renderListTemplate($currentOrder): Response protected function renderEditionTemplate(): Response { $request = $this->getRequest(); - $selectionId = $request->query->get('selectionId', $request->request->get('selectionId')); - $currentTab = $request->query->get('current_tab', $request->request->get('current_tab')); + $selectionId = $request->attributes->get('selectionId'); + $currentTab = $request->attributes->get('current_tab'); + $locale = $this->getCurrentEditionLocale(); + + $selection = $this->getExistingObject(); - $selection = SelectionQuery::create()->findPk($selectionId); $form = $this->hydrateObjectForm($this->getParserContext(), $selection); - $locale = $this->getCurrentEditionLocale(); return $this->renderTwig('selection-edit.html.twig', [ 'selection_id' => $selectionId, @@ -543,8 +545,7 @@ public function processUpdateSeoAction( Request $request, ParserContext $parserContext, EventDispatcherInterface $eventDispatcher - ): Response - { + ): Response { $selectionId = $request->query->get('current_id'); $request->request->set("selectionId", $selectionId); return parent::processUpdateSeoAction($request, $parserContext, $eventDispatcher); diff --git a/Readme.md b/Readme.md index 2068442..5be2c4b 100644 --- a/Readme.md +++ b/Readme.md @@ -36,7 +36,72 @@ front of the selection you wish to make visible or invisible. of the selection you wish to edit. - Delete a selection by clicking on the cog button then on the trash button in front of the selection you wish to delete. -You may then display your selection on your website by calling the selection_list loop. +You may then display your selection on your website by calling the selection_list loop, + +## REST API (API Platform) + +The module exposes two read-only front resources. They return the i18n content of every +available locale, and the selections also embed their related contents. + +| Method | URL | Description | +|---|---|---| +| GET | `/api/front/selections` | List of selections (with related contents) | +| GET | `/api/front/selections/{id}` | A single selection | +| GET | `/api/front/selection-containers` | List of selection containers | +| GET | `/api/front/selection-containers/{id}` | A single selection container | + +### Filters + +Both collections accept: + +| Parameter | Example | Description | +|---|---|---| +| `id` | `?id=1` | Filter by id (exact) | +| `code` | `?code=HOME` | Filter by code (exact) | +| `visible` | `?visible=true` | Filter by visibility | +| `order[position]` | `?order[position]=asc` | Order by position | + +The selections collection additionally accepts: + +| Parameter | Example | Description | +|---|---|---| +| `container_code` | `?container_code=MAIN` | Keep only the selections belonging to the container with this code | + +```http +GET /api/front/selections?container_code=MAIN&order[position]=asc +``` + +## Twig functions + +The module registers two Twig functions (available in any active front theme) that query +the API front endpoints above through Thelia's `DataAccessService`. They accept the same +filters as an associative array. + +| Function | Endpoint | +|---|---| +| `getSelection(params = [])` | `/api/front/selections` | +| `getSelectionContainer(params = [])` | `/api/front/selection-containers` | + +```twig +{# All selections, with their i18n content and related contents #} +{% set selections = getSelection() %} + +{# Filtered by code #} +{% set home = getSelection({code: 'HOME'}) %} + +{# Selections of a given container #} +{% set mainSelections = getSelection({container_code: 'MAIN'}) %} + +{# Selection containers #} +{% set containers = getSelectionContainer() %} + +{% for selection in selections %} +