Skip to content

Commit ed36080

Browse files
authored
Merge pull request #5 from etienne-monsieurbiz/feat/blog-ui-elements
2 parents 97e76d4 + 3945a3e commit ed36080

20 files changed

+792
-15
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Monsieur Biz's Blog plugin for Sylius.
5+
* (c) Monsieur Biz <[email protected]>
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace MonsieurBiz\SyliusBlogPlugin\Form\Type;
13+
14+
use MonsieurBiz\SyliusBlogPlugin\Entity\Article;
15+
use MonsieurBiz\SyliusBlogPlugin\Entity\ArticleInterface;
16+
use MonsieurBiz\SyliusBlogPlugin\Repository\ArticleRepositoryInterface;
17+
use Sylius\Bundle\ResourceBundle\Form\DataTransformer\ResourceToIdentifierTransformer;
18+
use Sylius\Component\Channel\Context\ChannelContextInterface;
19+
use Sylius\Component\Locale\Context\LocaleContextInterface;
20+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
21+
use Symfony\Component\Form\AbstractType;
22+
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
23+
use Symfony\Component\Form\FormBuilderInterface;
24+
use Symfony\Component\Form\ReversedTransformer;
25+
use Symfony\Component\Validator\Constraints as Assert;
26+
27+
final class ArticleSelectionElementType extends AbstractType
28+
{
29+
public function __construct(
30+
private readonly ArticleRepositoryInterface $articleRepository,
31+
private readonly ChannelContextInterface $channelContext,
32+
private readonly LocaleContextInterface $localeContext,
33+
) {
34+
}
35+
36+
/**
37+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
38+
*/
39+
public function buildForm(FormBuilderInterface $builder, array $options): void
40+
{
41+
$builder
42+
->add('article', EntityType::class, [
43+
'class' => Article::class,
44+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.article',
45+
'choice_label' => fn (Article $article) => $article->getTitle(),
46+
'choice_value' => fn (?Article $article) => $article?->getId(),
47+
'required' => true,
48+
'query_builder' => function (ArticleRepositoryInterface $articleRepository) {
49+
return $articleRepository->createShopListQueryBuilderByType(
50+
$this->localeContext->getLocaleCode(),
51+
ArticleInterface::BLOG_TYPE,
52+
$this->channelContext->getChannel(),
53+
null
54+
)->orderBy('translation.title');
55+
},
56+
])
57+
->add('position', IntegerType::class, [
58+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.position',
59+
'required' => true,
60+
'constraints' => [
61+
new Assert\NotBlank(),
62+
new Assert\GreaterThan(0),
63+
],
64+
])
65+
;
66+
67+
$builder->get('article')->addModelTransformer(
68+
new ReversedTransformer(new ResourceToIdentifierTransformer($this->articleRepository, 'id')),
69+
);
70+
}
71+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Monsieur Biz's Blog plugin for Sylius.
5+
* (c) Monsieur Biz <[email protected]>
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace MonsieurBiz\SyliusBlogPlugin\Form\Type;
13+
14+
use Symfony\Component\Form\AbstractType;
15+
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
16+
use Symfony\Component\Form\FormBuilderInterface;
17+
18+
final class ArticlesDisplayType extends AbstractType
19+
{
20+
public const MULTIPLE_WITH_IMAGE = 'multiple_with_image';
21+
22+
public const MULTIPLE_WITHOUT_IMAGE = 'multiple_without_image';
23+
24+
public const SINGLE = 'single';
25+
26+
/**
27+
* @SuppressWarnings(PHPMD.UnusedFormalParameters)
28+
*/
29+
public function buildForm(FormBuilderInterface $builder, array $options): void
30+
{
31+
$builder
32+
->add('display', ChoiceType::class, [
33+
'label' => 'monsieurbiz_blog.articles.display.label',
34+
'required' => true,
35+
'choices' => [
36+
'monsieurbiz_blog.articles.display.choices.multiple_with_image' => self::MULTIPLE_WITH_IMAGE,
37+
'monsieurbiz_blog.articles.display.choices.multiple_without_image' => self::MULTIPLE_WITHOUT_IMAGE,
38+
'monsieurbiz_blog.articles.display.choices.single' => self::SINGLE,
39+
],
40+
])
41+
;
42+
}
43+
}

src/Form/Type/CaseStudyElementType.php

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,21 @@ public function __construct(
3838
*/
3939
public function buildForm(FormBuilderInterface $builder, array $options): void
4040
{
41-
$caseStudies = $this->articleRepository->createShopListQueryBuilderByType(
42-
$this->localeContext->getLocaleCode(),
43-
ArticleInterface::CASE_STUDY_TYPE,
44-
$this->channelContext->getChannel(),
45-
null
46-
);
47-
48-
$caseStudies = $caseStudies->orderBy('translation.title')->getQuery()->getResult();
49-
5041
$builder
5142
->add('case_study', EntityType::class, [
5243
'class' => Article::class,
5344
'label' => 'monsieurbiz_blog.ui_element.case_studies_ui_element.fields.case_study',
5445
'choice_label' => fn (Article $caseStudy) => $caseStudy->getTitle(),
55-
'choice_value' => fn (?Article $caseStudy) => $caseStudy ? $caseStudy->getId() : null,
46+
'choice_value' => fn (?Article $caseStudy) => $caseStudy?->getId(),
5647
'required' => true,
57-
'choices' => $caseStudies,
48+
'query_builder' => function (ArticleRepositoryInterface $articleRepository) {
49+
return $articleRepository->createShopListQueryBuilderByType(
50+
$this->localeContext->getLocaleCode(),
51+
ArticleInterface::CASE_STUDY_TYPE,
52+
$this->channelContext->getChannel(),
53+
null
54+
)->orderBy('translation.title');
55+
},
5856
])
5957
->add('position', IntegerType::class, [
6058
'label' => 'monsieurbiz_blog.ui_element.case_studies_ui_element.fields.position',
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Monsieur Biz's Blog plugin for Sylius.
5+
* (c) Monsieur Biz <[email protected]>
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace MonsieurBiz\SyliusBlogPlugin\Form\Type\UiElement;
13+
14+
use MonsieurBiz\SyliusBlogPlugin\Entity\Tag;
15+
use MonsieurBiz\SyliusBlogPlugin\Form\Type\ArticlesDisplayType;
16+
use MonsieurBiz\SyliusBlogPlugin\Repository\TagRepositoryInterface;
17+
use MonsieurBiz\SyliusRichEditorPlugin\Attribute\AsUiElement;
18+
use MonsieurBiz\SyliusRichEditorPlugin\Attribute\TemplatesUiElement;
19+
use MonsieurBiz\SyliusRichEditorPlugin\Form\Type\LinkType;
20+
use Sylius\Component\Locale\Context\LocaleContextInterface;
21+
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
22+
use Symfony\Component\Form\AbstractType;
23+
use Symfony\Component\Form\CallbackTransformer;
24+
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
25+
use Symfony\Component\Form\Extension\Core\Type\TextType;
26+
use Symfony\Component\Form\FormBuilderInterface;
27+
use Symfony\Component\Validator\Constraints as Assert;
28+
29+
#[AsUiElement(
30+
code: 'monsieurbiz_blog.articles_by_tags_ui_element',
31+
icon: 'tags',
32+
title: 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.title',
33+
description: 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.description',
34+
uiElement: 'MonsieurBiz\SyliusBlogPlugin\UiElement\ArticlesByTagsUiElement',
35+
templates: new TemplatesUiElement(
36+
adminRender: '@MonsieurBizSyliusBlogPlugin/Admin/UiElement/articles_by_tags.html.twig',
37+
frontRender: '@MonsieurBizSyliusBlogPlugin/Shop/UiElement/articles_by_tags.html.twig',
38+
),
39+
wireframe: 'articles-by-tags',
40+
tags: [],
41+
)]
42+
class ArticlesByTagsUiElementType extends AbstractType
43+
{
44+
public function __construct(
45+
private readonly TagRepositoryInterface $tagRepository,
46+
private readonly LocaleContextInterface $localeContext,
47+
) {
48+
}
49+
50+
/**
51+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
52+
*/
53+
public function buildForm(FormBuilderInterface $builder, array $options): void
54+
{
55+
$builder
56+
->add('title', TextType::class, [
57+
'label' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.fields.title',
58+
'required' => false,
59+
])
60+
->add('display', ArticlesDisplayType::class, [
61+
'label' => false, // already defined in the ArticlesDisplayType
62+
])
63+
->add('tags', EntityType::class, [
64+
'label' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.fields.tags',
65+
'required' => false,
66+
'class' => Tag::class,
67+
'choice_label' => fn (Tag $tag) => $tag->getName(),
68+
'choice_value' => fn (?Tag $tag) => $tag?->getId(),
69+
'query_builder' => function (TagRepositoryInterface $tagRepository) {
70+
return $tagRepository->createListQueryBuilder(
71+
$this->localeContext->getLocaleCode(),
72+
)->orderBy('translation.name');
73+
},
74+
'multiple' => true,
75+
])
76+
->add('limit', IntegerType::class, [
77+
'label' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.fields.limit',
78+
'help' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.help.limit',
79+
'required' => true,
80+
'constraints' => [
81+
new Assert\NotBlank(),
82+
new Assert\GreaterThan(0),
83+
],
84+
])
85+
->add('buttonLabel', TextType::class, [
86+
'label' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.fields.button_label',
87+
'required' => false,
88+
])
89+
->add('buttonUrl', LinkType::class, [
90+
'label' => 'monsieurbiz_blog.ui_element.articles_by_tags_ui_element.fields.button_url',
91+
'required' => false,
92+
])
93+
;
94+
95+
$builder->get('tags')->addModelTransformer(
96+
new CallbackTransformer(
97+
function ($tagsAsArray) {
98+
return $this->tagRepository->findBy(['id' => $tagsAsArray ?? []]);
99+
},
100+
function ($tagsAsString) {
101+
$tags = [];
102+
foreach ($tagsAsString as $tag) {
103+
$tags[] = $tag->getId();
104+
}
105+
106+
return $tags;
107+
}
108+
),
109+
);
110+
}
111+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Monsieur Biz's Blog plugin for Sylius.
5+
* (c) Monsieur Biz <[email protected]>
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
declare(strict_types=1);
11+
12+
namespace MonsieurBiz\SyliusBlogPlugin\Form\Type\UiElement;
13+
14+
use MonsieurBiz\SyliusBlogPlugin\Form\Type\ArticlesDisplayType;
15+
use MonsieurBiz\SyliusBlogPlugin\Form\Type\ArticleSelectionElementType;
16+
use MonsieurBiz\SyliusRichEditorPlugin\Attribute\AsUiElement;
17+
use MonsieurBiz\SyliusRichEditorPlugin\Attribute\TemplatesUiElement;
18+
use MonsieurBiz\SyliusRichEditorPlugin\Form\Type\LinkType;
19+
use Symfony\Component\Form\AbstractType;
20+
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
21+
use Symfony\Component\Form\Extension\Core\Type\TextType;
22+
use Symfony\Component\Form\FormBuilderInterface;
23+
use Symfony\Component\Form\FormInterface;
24+
use Symfony\Component\Form\FormView;
25+
use Symfony\Component\Validator\Constraints as Assert;
26+
27+
#[AsUiElement(
28+
code: 'monsieurbiz_blog.articles_selection_ui_element',
29+
icon: 'newspaper',
30+
title: 'monsieurbiz_blog.ui_element.articles_selection_ui_element.title',
31+
description: 'monsieurbiz_blog.ui_element.articles_selection_ui_element.description',
32+
uiElement: 'MonsieurBiz\SyliusBlogPlugin\UiElement\ArticlesSelectionUiElement',
33+
templates: new TemplatesUiElement(
34+
adminRender: '@MonsieurBizSyliusBlogPlugin/Admin/UiElement/articles_selection.html.twig',
35+
frontRender: '@MonsieurBizSyliusBlogPlugin/Shop/UiElement/articles_selection.html.twig',
36+
),
37+
wireframe: 'articles-selection',
38+
tags: [],
39+
)]
40+
class ArticlesSelectionUiElementType extends AbstractType
41+
{
42+
/**
43+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
44+
*/
45+
public function buildForm(FormBuilderInterface $builder, array $options): void
46+
{
47+
$builder
48+
->add('title', TextType::class, [
49+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.title',
50+
'required' => false,
51+
])
52+
->add('display', ArticlesDisplayType::class, [
53+
'label' => false, // already defined in the ArticlesDisplayType
54+
])
55+
->add('articles', CollectionType::class, [
56+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.articles',
57+
'entry_type' => ArticleSelectionElementType::class,
58+
'prototype_name' => '__article_selection__',
59+
'allow_add' => true,
60+
'allow_delete' => true,
61+
'by_reference' => false,
62+
'delete_empty' => true,
63+
'attr' => [
64+
'class' => 'ui segment secondary collection--flex',
65+
],
66+
'constraints' => [
67+
new Assert\Count(['min' => 1]),
68+
],
69+
])
70+
->add('buttonLabel', TextType::class, [
71+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.button_label',
72+
'required' => false,
73+
])
74+
->add('buttonUrl', LinkType::class, [
75+
'label' => 'monsieurbiz_blog.ui_element.articles_selection_ui_element.fields.button_url',
76+
'required' => false,
77+
])
78+
;
79+
}
80+
81+
/**
82+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
83+
*/
84+
public function finishView(FormView $view, FormInterface $form, array $options): void
85+
{
86+
usort($view['articles']->children, function (FormView $articleA, FormView $articleB) {
87+
return match (true) {
88+
!$articleA->offsetExists('position') => -1,
89+
!$articleB->offsetExists('position') => 1,
90+
default => $articleA['position']->vars['data'] <=> $articleB['position']->vars['data']
91+
};
92+
});
93+
}
94+
}

src/Form/Type/UiElement/CaseStudiesUiElementType.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@
2323
#[AsUiElement(
2424
code: 'monsieurbiz_blog.case_studies_ui_element',
2525
icon: 'crosshairs',
26-
uiElement: 'MonsieurBiz\SyliusBlogPlugin\UiElement\CaseStudiesUiElement',
2726
title: 'monsieurbiz_blog.ui_element.case_studies_ui_element.title',
2827
description: 'monsieurbiz_blog.ui_element.case_studies_ui_element.description',
28+
uiElement: 'MonsieurBiz\SyliusBlogPlugin\UiElement\CaseStudiesUiElement',
2929
templates: new TemplatesUiElement(
3030
adminRender: '@MonsieurBizSyliusBlogPlugin/Admin/UiElement/case_studies.html.twig',
3131
frontRender: '@MonsieurBizSyliusBlogPlugin/Shop/UiElement/case_studies.html.twig',
3232
),
33-
tags: [],
3433
wireframe: 'case-studies',
34+
tags: [],
3535
)]
3636
class CaseStudiesUiElementType extends AbstractType
3737
{

0 commit comments

Comments
 (0)