add blog category CRUD
This commit is contained in:
parent
f952403f83
commit
e0d58349de
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,6 +5,7 @@
|
||||||
/.env.*.local
|
/.env.*.local
|
||||||
/config/secrets/prod/prod.decrypt.private.php
|
/config/secrets/prod/prod.decrypt.private.php
|
||||||
/public/bundles/
|
/public/bundles/
|
||||||
|
/src/Command/TestCommand.php
|
||||||
/var/
|
/var/
|
||||||
/vendor/
|
/vendor/
|
||||||
###< symfony/framework-bundle ###
|
###< symfony/framework-bundle ###
|
||||||
|
|
|
@ -5,7 +5,7 @@ module.exports = function() {
|
||||||
|
|
||||||
tinymce.init({
|
tinymce.init({
|
||||||
selector: '*[data-tinymce]',
|
selector: '*[data-tinymce]',
|
||||||
base_url: '/nm/tinymce/',
|
base_url: '/vendor/tinymce/',
|
||||||
cache_suffix: '?v=4.1.6',
|
cache_suffix: '?v=4.1.6',
|
||||||
language: 'fr_FR',
|
language: 'fr_FR',
|
||||||
plugins: 'print preview importcss searchreplace visualblocks visualchars fullscreen template table charmap hr pagebreak nonbreaking toc insertdatetime advlist lists wordcount textpattern noneditable help charmap quickbars',
|
plugins: 'print preview importcss searchreplace visualblocks visualchars fullscreen template table charmap hr pagebreak nonbreaking toc insertdatetime advlist lists wordcount textpattern noneditable help charmap quickbars',
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
twig:
|
twig:
|
||||||
default_path: '%kernel.project_dir%/templates'
|
default_path: '%kernel.project_dir%/templates'
|
||||||
|
form_themes: ['bootstrap_4_layout.html.twig']
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"jquery": "^3.6.0",
|
"jquery": "^3.6.0",
|
||||||
"popper.js": "^1.16.0",
|
"popper.js": "^1.16.0",
|
||||||
"qrcodejs": "^1.0.0",
|
"qrcodejs": "^1.0.0",
|
||||||
"tinymce": "^5.2.0",
|
"tinymce": "^5.7.1",
|
||||||
"vanillajs-datepicker": "^1.1.2",
|
"vanillajs-datepicker": "^1.1.2",
|
||||||
"zxcvbn": "^4.4.2"
|
"zxcvbn": "^4.4.2"
|
||||||
}
|
}
|
||||||
|
|
1
public/vendor/tinymce
vendored
Symbolic link
1
public/vendor/tinymce
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../node_modules/tinymce
|
|
@ -3,6 +3,13 @@
|
||||||
namespace App\Controller\Blog;
|
namespace App\Controller\Blog;
|
||||||
|
|
||||||
use App\Controller\Admin\AdminController;
|
use App\Controller\Admin\AdminController;
|
||||||
|
use App\Entity\Blog\Category as Entity;
|
||||||
|
use App\Factory\Blog\CategoryFactory as EntityFactory;
|
||||||
|
use App\Form\Blog\CategoryType as EntityType;
|
||||||
|
use App\Manager\EntityManager;
|
||||||
|
use App\Repository\Blog\CategoryRepositoryQuery as RepositoryQuery;
|
||||||
|
use App\Repository\Blog\PostRepositoryQuery;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\Routing\Annotation\Route;
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
@ -12,14 +19,102 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||||
class CategoryAdminController extends AdminController
|
class CategoryAdminController extends AdminController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @Route("/", name="admin_blog_category_index")
|
* @Route("/{page}", name="admin_blog_category_index", requirements={"page": "\d+"})
|
||||||
*/
|
*/
|
||||||
public function index(): Response
|
public function index(int $page = 1, RepositoryQuery $query, Request $request): Response
|
||||||
{
|
{
|
||||||
|
$pager = $query->paginate($page);
|
||||||
|
|
||||||
return $this->render('blog/category_admin/index.html.twig', [
|
return $this->render('blog/category_admin/index.html.twig', [
|
||||||
|
'pager' => $pager,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/new", name="admin_blog_category_new")
|
||||||
|
*/
|
||||||
|
public function new(EntityFactory $factory, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$entity = $factory->create();
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->create($entity)->flush()->clear();
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_blog_category_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('blog/category_admin/new.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/edit/{entity}", name="admin_blog_category_edit")
|
||||||
|
*/
|
||||||
|
public function edit(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
$form = $this->createForm(EntityType::class, $entity);
|
||||||
|
|
||||||
|
if ($request->isMethod('POST')) {
|
||||||
|
$form->handleRequest($request);
|
||||||
|
|
||||||
|
if ($form->isValid()) {
|
||||||
|
$entityManager->update($entity)->flush()->clear();
|
||||||
|
$this->addFlash('success', 'Donnée enregistrée.');
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_blog_category_edit', [
|
||||||
|
'entity' => $entity->getId(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->addFlash('warning', 'Le formulaire est invalide.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->render('blog/category_admin/edit.html.twig', [
|
||||||
|
'form' => $form->createView(),
|
||||||
|
'entity' => $entity,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/show/{entity}", name="admin_blog_category_show")
|
||||||
|
*/
|
||||||
|
public function show(Entity $entity, PostRepositoryQuery $postQuery): Response
|
||||||
|
{
|
||||||
|
$posts = $postQuery->create()
|
||||||
|
->orderBy('.publishedAt', 'DESC')
|
||||||
|
->inCategory($entity)
|
||||||
|
->paginate(1, 10)
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this->render('blog/category_admin/show.html.twig', [
|
||||||
|
'entity' => $entity,
|
||||||
|
'posts' => $posts,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Route("/delete/{entity}", name="admin_blog_category_delete", methods={"DELETE"})
|
||||||
|
*/
|
||||||
|
public function delete(Entity $entity, EntityManager $entityManager, Request $request): Response
|
||||||
|
{
|
||||||
|
if ($this->isCsrfTokenValid('delete'.$entity->getId(), $request->request->get('_token'))) {
|
||||||
|
$entityManager->delete($entity)->flush()->clear();
|
||||||
|
|
||||||
|
$this->addFlash('success', 'Données supprimées.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->redirectToRoute('admin_blog_category_index');
|
||||||
|
}
|
||||||
|
|
||||||
public function getSection(): string
|
public function getSection(): string
|
||||||
{
|
{
|
||||||
return 'blog_category';
|
return 'blog_category';
|
||||||
|
|
|
@ -38,7 +38,7 @@ trait Timestampable
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCreatedAt():?\DateTime
|
public function getCreatedAt(): ?\DateTime
|
||||||
{
|
{
|
||||||
return $this->createdAt;
|
return $this->createdAt;
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ trait Timestampable
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUpdatedAt():?\DateTime
|
public function getUpdatedAt(): ?\DateTime
|
||||||
{
|
{
|
||||||
return $this->updatedAt;
|
return $this->updatedAt;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@
|
||||||
|
|
||||||
namespace App\Entity\Blog;
|
namespace App\Entity\Blog;
|
||||||
|
|
||||||
|
use App\Entity\EntityInterface;
|
||||||
use App\Repository\Blog\CategoryRepository;
|
use App\Repository\Blog\CategoryRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=CategoryRepository::class)
|
* @ORM\Entity(repositoryClass=CategoryRepository::class)
|
||||||
*/
|
*/
|
||||||
class Category
|
class Category implements EntityInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
|
@ -37,6 +40,16 @@ class Category
|
||||||
*/
|
*/
|
||||||
private $slug;
|
private $slug;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=Post::class, mappedBy="categories")
|
||||||
|
*/
|
||||||
|
private $posts;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->posts = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -89,4 +102,31 @@ class Category
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Post[]
|
||||||
|
*/
|
||||||
|
public function getPosts(): Collection
|
||||||
|
{
|
||||||
|
return $this->posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addPost(Post $post): self
|
||||||
|
{
|
||||||
|
if (!$this->posts->contains($post)) {
|
||||||
|
$this->posts[] = $post;
|
||||||
|
$post->addCategory($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removePost(Post $post): self
|
||||||
|
{
|
||||||
|
if ($this->posts->removeElement($post)) {
|
||||||
|
$post->removeCategory($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,16 +2,20 @@
|
||||||
|
|
||||||
namespace App\Entity\Blog;
|
namespace App\Entity\Blog;
|
||||||
|
|
||||||
|
use App\Doctrine\Timestampable;
|
||||||
|
use App\Entity\EntityInterface;
|
||||||
use App\Entity\User;
|
use App\Entity\User;
|
||||||
use App\Repository\Blog\PostRepository;
|
use App\Repository\Blog\PostRepository;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
use App\Doctrine\Timestampable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=PostRepository::class)
|
* @ORM\Entity(repositoryClass=PostRepository::class)
|
||||||
*/
|
*/
|
||||||
class Post
|
class Post implements EntityInterface
|
||||||
{
|
{
|
||||||
|
use Timestampable;
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
|
@ -69,7 +73,15 @@ class Post
|
||||||
*/
|
*/
|
||||||
private $author;
|
private $author;
|
||||||
|
|
||||||
use Timestampable;
|
/**
|
||||||
|
* @ORM\ManyToMany(targetEntity=Category::class, inversedBy="posts")
|
||||||
|
*/
|
||||||
|
private $categories;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->categories = new ArrayCollection();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
|
@ -195,4 +207,28 @@ class Post
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Collection|Category[]
|
||||||
|
*/
|
||||||
|
public function getCategories(): Collection
|
||||||
|
{
|
||||||
|
return $this->categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCategory(Category $category): self
|
||||||
|
{
|
||||||
|
if (!$this->categories->contains($category)) {
|
||||||
|
$this->categories[] = $category;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeCategory(Category $category): self
|
||||||
|
{
|
||||||
|
$this->categories->removeElement($category);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
interface Entity
|
interface EntityInterface
|
||||||
{
|
{
|
||||||
}
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use App\Doctrine\Timestampable;
|
||||||
use App\Entity\Blog\Post;
|
use App\Entity\Blog\Post;
|
||||||
use App\Repository\UserRepository;
|
use App\Repository\UserRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
@ -10,14 +11,14 @@ use Doctrine\ORM\Mapping as ORM;
|
||||||
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
|
use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface;
|
||||||
use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration;
|
use Scheb\TwoFactorBundle\Model\Totp\TotpConfiguration;
|
||||||
use Symfony\Component\Security\Core\User\UserInterface;
|
use Symfony\Component\Security\Core\User\UserInterface;
|
||||||
use App\Doctrine\Timestampable;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Entity(repositoryClass=UserRepository::class)
|
* @ORM\Entity(repositoryClass=UserRepository::class)
|
||||||
* @ORM\Table(name="`user`")
|
* @ORM\Table(name="`user`")
|
||||||
*/
|
*/
|
||||||
class User implements UserInterface, TwoFactorInterface, Entity
|
class User implements UserInterface, TwoFactorInterface, EntityInterface
|
||||||
{
|
{
|
||||||
|
use Timestampable;
|
||||||
/**
|
/**
|
||||||
* @ORM\Id
|
* @ORM\Id
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
|
@ -76,8 +77,6 @@ class User implements UserInterface, TwoFactorInterface, Entity
|
||||||
$this->posts = new ArrayCollection();
|
$this->posts = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
use Timestampable;
|
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Event\EntityManager;
|
namespace App\Event\EntityManager;
|
||||||
|
|
||||||
use App\Entity\Entity;
|
use App\Entity\EntityInterface;
|
||||||
use Symfony\Contracts\EventDispatcher\Event;
|
use Symfony\Contracts\EventDispatcher\Event;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -16,14 +16,14 @@ class EntityManagerEvent extends Event
|
||||||
const UPDATE_EVENT = 'entity_manager_event.update';
|
const UPDATE_EVENT = 'entity_manager_event.update';
|
||||||
const DELETE_EVENT = 'entity_manager_event.delete';
|
const DELETE_EVENT = 'entity_manager_event.delete';
|
||||||
|
|
||||||
protected Entity $entity;
|
protected EntityInterface $entity;
|
||||||
|
|
||||||
public function __construct(Entity $entity)
|
public function __construct(EntityInterface $entity)
|
||||||
{
|
{
|
||||||
$this->entity = $entity;
|
$this->entity = $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getEntity(): Entity
|
public function getEntity(): EntityInterface
|
||||||
{
|
{
|
||||||
return $this->entity;
|
return $this->entity;
|
||||||
}
|
}
|
||||||
|
|
18
src/Factory/Blog/CategoryFactory.php
Normal file
18
src/Factory/Blog/CategoryFactory.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Factory\Blog;
|
||||||
|
|
||||||
|
use App\Entity\Blog\Category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class CategoryFactory.
|
||||||
|
*
|
||||||
|
* @author Simon Vieille <simon@deblan.fr>
|
||||||
|
*/
|
||||||
|
class CategoryFactory
|
||||||
|
{
|
||||||
|
public function create(): Category
|
||||||
|
{
|
||||||
|
return new Category();
|
||||||
|
}
|
||||||
|
}
|
79
src/Form/Blog/CategoryType.php
Normal file
79
src/Form/Blog/CategoryType.php
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Form\Blog;
|
||||||
|
|
||||||
|
use App\Entity\Blog\Category;
|
||||||
|
use Symfony\Component\Form\AbstractType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||||
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||||
|
use Symfony\Component\Validator\Constraints\NotBlank;
|
||||||
|
|
||||||
|
class CategoryType extends AbstractType
|
||||||
|
{
|
||||||
|
public function buildForm(FormBuilderInterface $builder, array $options)
|
||||||
|
{
|
||||||
|
$builder->add(
|
||||||
|
'title',
|
||||||
|
TextType::class,
|
||||||
|
[
|
||||||
|
'label' => 'Titre',
|
||||||
|
'required' => true,
|
||||||
|
'attr' => [
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
new NotBlank(),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$builder->add(
|
||||||
|
'subTitle',
|
||||||
|
TextareaType::class,
|
||||||
|
[
|
||||||
|
'label' => 'Sous-titre',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$builder->add(
|
||||||
|
'description',
|
||||||
|
TextareaType::class,
|
||||||
|
[
|
||||||
|
'label' => 'Description',
|
||||||
|
'required' => false,
|
||||||
|
'attr' => [
|
||||||
|
'data-tinymce' => '',
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$builder->add(
|
||||||
|
'slug',
|
||||||
|
TextType::class,
|
||||||
|
[
|
||||||
|
'label' => 'Slug',
|
||||||
|
'required' => false,
|
||||||
|
'help' => 'Laisser vide pour une génération automatique',
|
||||||
|
'attr' => [
|
||||||
|
],
|
||||||
|
'constraints' => [
|
||||||
|
],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function configureOptions(OptionsResolver $resolver)
|
||||||
|
{
|
||||||
|
$resolver->setDefaults([
|
||||||
|
'data_class' => Category::class,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace App\Manager;
|
namespace App\Manager;
|
||||||
|
|
||||||
use App\Entity\Entity;
|
use App\Entity\EntityInterface;
|
||||||
use App\Event\EntityManager\EntityManagerEvent;
|
use App\Event\EntityManager\EntityManagerEvent;
|
||||||
use Doctrine\ORM\EntityManager as DoctrineEntityManager;
|
use Doctrine\ORM\EntityManager as DoctrineEntityManager;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
|
@ -25,7 +25,7 @@ class EntityManager
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create(Entity $entity): self
|
public function create(EntityInterface $entity): self
|
||||||
{
|
{
|
||||||
$this->persist($entity);
|
$this->persist($entity);
|
||||||
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);
|
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::CREATE_EVENT);
|
||||||
|
@ -33,7 +33,7 @@ class EntityManager
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Entity $entity): self
|
public function update(EntityInterface $entity): self
|
||||||
{
|
{
|
||||||
$this->persist($entity);
|
$this->persist($entity);
|
||||||
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);
|
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::UPDATE_EVENT);
|
||||||
|
@ -41,9 +41,9 @@ class EntityManager
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Entity $entity): self
|
public function delete(EntityInterface $entity): self
|
||||||
{
|
{
|
||||||
$this->remove($entity);
|
$this->entityManager->remove($entity);
|
||||||
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
|
$this->eventDispatcher->dispatch(new EntityManagerEvent($entity), EntityManagerEvent::DELETE_EVENT);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
|
@ -63,7 +63,7 @@ class EntityManager
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function persist(Entity $entity)
|
protected function persist(EntityInterface $entity)
|
||||||
{
|
{
|
||||||
$this->entityManager->persist($entity);
|
$this->entityManager->persist($entity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
namespace App\Repository\Blog;
|
namespace App\Repository\Blog;
|
||||||
|
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
|
||||||
use App\Repository\Blog\PostRepository;
|
|
||||||
use App\Repository\RepositoryQuery;
|
use App\Repository\RepositoryQuery;
|
||||||
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class CategoryRepositoryQuery.
|
* class CategoryRepositoryQuery.
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace App\Repository\Blog;
|
namespace App\Repository\Blog;
|
||||||
|
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
use App\Entity\Blog\Category;
|
||||||
use App\Repository\Blog\PostRepository;
|
|
||||||
use App\Repository\RepositoryQuery;
|
use App\Repository\RepositoryQuery;
|
||||||
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class PostRepositoryQuery.
|
* class PostRepositoryQuery.
|
||||||
|
@ -17,4 +17,17 @@ class PostRepositoryQuery extends RepositoryQuery
|
||||||
{
|
{
|
||||||
parent::__construct($repository, 'p', $paginator);
|
parent::__construct($repository, 'p', $paginator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function inCategory(Category $category)
|
||||||
|
{
|
||||||
|
$c = 'c'.mt_rand();
|
||||||
|
|
||||||
|
$this
|
||||||
|
->innerJoin('p.categories', $c)
|
||||||
|
->andWhere($c.'.id = :category')
|
||||||
|
->setParameter(':category', $category->getId())
|
||||||
|
;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
namespace App\Repository;
|
namespace App\Repository;
|
||||||
|
|
||||||
use App\Criteria\CriteriaMap;
|
|
||||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||||
use Doctrine\ORM\QueryBuilder;
|
use Doctrine\ORM\QueryBuilder;
|
||||||
use Knp\Component\Pager\PaginatorInterface;
|
use Knp\Component\Pager\PaginatorInterface;
|
||||||
|
@ -27,27 +26,20 @@ abstract class RepositoryQuery
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function create()
|
|
||||||
{
|
|
||||||
$class = get_called_class();
|
|
||||||
|
|
||||||
return new $class($this->repository, $this->paginator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __call(string $name, $params): self
|
public function __call(string $name, $params): self
|
||||||
{
|
{
|
||||||
$fn = function(&$data) {
|
$fn = function (&$data) {
|
||||||
if (is_string($data)) {
|
if (is_string($data)) {
|
||||||
$words = explode(' ', $data);
|
$words = explode(' ', $data);
|
||||||
|
|
||||||
foreach ($words as $k => $v) {
|
foreach ($words as $k => $v) {
|
||||||
if (isset($v[0]) && $v[0] === '.') {
|
if (isset($v[0]) && '.' === $v[0]) {
|
||||||
$words[$k] = $this->id.$v;
|
$words[$k] = $this->id.$v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = implode(' ', $words);
|
$data = implode(' ', $words);
|
||||||
} else {
|
} elseif (is_array($data)) {
|
||||||
foreach ($data as $k => $v) {
|
foreach ($data as $k => $v) {
|
||||||
$fn($data[$k]);
|
$fn($data[$k]);
|
||||||
}
|
}
|
||||||
|
@ -56,8 +48,7 @@ abstract class RepositoryQuery
|
||||||
return $data;
|
return $data;
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach ($params as $key => $value)
|
foreach ($params as $key => $value) {
|
||||||
{
|
|
||||||
$fn($params[$key]);
|
$fn($params[$key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,6 +57,13 @@ abstract class RepositoryQuery
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$class = get_called_class();
|
||||||
|
|
||||||
|
return new $class($this->repository, $this->paginator);
|
||||||
|
}
|
||||||
|
|
||||||
public function call(callable $fn): self
|
public function call(callable $fn): self
|
||||||
{
|
{
|
||||||
$fn($this->query, $this);
|
$fn($this->query, $this);
|
||||||
|
|
|
@ -104,7 +104,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block js %}
|
{% block js %}
|
||||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>
|
<script src="{{ asset('vendor/tinymce/tinymce.min.js') }}"></script>
|
||||||
|
|
||||||
{{ encore_entry_script_tags('admin') }}
|
{{ encore_entry_script_tags('admin') }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
17
templates/blog/category_admin/_form.html.twig
Normal file
17
templates/blog/category_admin/_form.html.twig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 p-3">
|
||||||
|
<div class="row">
|
||||||
|
{% for item in ['title', 'subTitle', 'slug'] %}
|
||||||
|
<div class="col-12">
|
||||||
|
{{ form_row(form[item]) }}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 p-3">
|
||||||
|
{% for item in ['description'] %}
|
||||||
|
{{ form_row(form[item]) }}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
57
templates/blog/category_admin/edit.html.twig
Normal file
57
templates/blog/category_admin/edit.html.twig
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends 'admin/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="mr-auto">
|
||||||
|
<h1 class="display-8">{{ entity.title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{{ path('admin_blog_category_index') }}" class="btn btn-light">
|
||||||
|
<span class="fa fa-list pr-1"></span>
|
||||||
|
Retour à la liste
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('admin_blog_category_show', {entity: entity.id}) }}" class="btn btn-secondary">
|
||||||
|
<span class="fa fa-eye pr-1"></span>
|
||||||
|
Voir
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="submit" form="form-main" class="btn btn-primary">
|
||||||
|
<span class="fa fa-save pr-1"></span>
|
||||||
|
Enregistrer
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="button" class="btn btn-white dropdown-toggle dropdown-toggle-hide-after" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
<span class="font-weight-bold">
|
||||||
|
⋅⋅⋅
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right">
|
||||||
|
<button type="submit" form="form-delete" class="dropdown-item">
|
||||||
|
Supprimer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ app.request.uri }}" method="post" id="form-main">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active">
|
||||||
|
<div class="tab-form">
|
||||||
|
{{ include('blog/category_admin/_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="{{ path('admin_blog_category_delete', {entity: entity.id}) }}" id="form-delete" data-form-confirm>
|
||||||
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ entity.id) }}">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -1,16 +1,23 @@
|
||||||
{% extends 'admin/layout.html.twig' %}
|
{% extends 'admin/layout.html.twig' %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
<div class="bg-light pl-5 pr-4 pt-5 {% if pager.totalItemCount == 0 %}pb-5{% endif %}">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="mr-auto">
|
<div class="mr-auto">
|
||||||
<h1 class="display-8">Catégories</h1>
|
<h1 class="display-8">Catégories</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ml-auto">
|
<div class="ml-auto">
|
||||||
<button type="button" class="btn btn-primary">Nouveau</button>
|
<div class="btn-group">
|
||||||
|
<a href="{{ path('admin_blog_category_new') }}" class="btn btn-primary">
|
||||||
|
<span class="fa fa-plus pr-1"></span>
|
||||||
|
Nouveau
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{ knp_pagination_render(pager) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table" data-table-fixed>
|
<table class="table" data-table-fixed>
|
||||||
|
@ -20,28 +27,51 @@
|
||||||
<th class="col-4">Articles</th>
|
<th class="col-4">Articles</th>
|
||||||
<th class="col-2 text-right">Actions</th>
|
<th class="col-2 text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for item in range(1, 4) %}
|
{% for item in pager %}
|
||||||
<tr>
|
{% set edit = path('admin_blog_category_edit', {entity: item.id}) %}
|
||||||
<td class="col-6">
|
{% set show = path('admin_blog_category_show', {entity: item.id}) %}
|
||||||
<span class="font-weight-bold">Titre de la catégories</span> <br>
|
|
||||||
</td>
|
<tr data-dblclick="{{ edit }}">
|
||||||
<td class="col-4">
|
<td class="col-6">
|
||||||
<a href="" class="btn btn-sm btn-light">
|
<a href="{{ show }}" class="font-weight-bold">
|
||||||
<span class="font-weight-bold">5</span> articles
|
{{ item.title }}
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td class="col-2 text-right">
|
<td class="col-4">
|
||||||
<button type="button" class="btn btn-sm btn-primary mr-1">
|
<a href="" class="btn btn-sm btn-light">
|
||||||
<span class="fa fa-edit"></span>
|
{% set postsCount = item.posts|length %}
|
||||||
</button>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger">
|
<span class="font-weight-bold">{{ postsCount }}</span> {{ postsCount < 2 ? 'article' : 'articles' }}
|
||||||
<span class="fa fa-trash"></span>
|
</a>
|
||||||
</button>
|
</td>
|
||||||
</td>
|
<td class="col-2 text-right">
|
||||||
</tr>
|
<a href="{{ edit }}" class="btn btn-sm btn-primary mr-1">
|
||||||
{% endfor %}
|
<span class="fa fa-edit"></span>
|
||||||
|
</a>
|
||||||
|
<button type="submit" form="form-delete-{{ item.id }}" class="btn btn-sm btn-danger">
|
||||||
|
<span class="fa fa-trash"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<form method="post" action="{{ path('admin_blog_category_delete', {entity: item.id}) }}" id="form-delete-{{ item.id }}" data-form-confirm>
|
||||||
|
<input type="hidden" name="_method" value="DELETE">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ item.id) }}">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td class="col-12 text-center p-4 text-black-50">
|
||||||
|
<div class="display-1">
|
||||||
|
<span class="fa fa-search"></span>
|
||||||
|
</div>
|
||||||
|
<div class="display-5 mt-3">
|
||||||
|
Aucun résultat
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
39
templates/blog/category_admin/new.html.twig
Normal file
39
templates/blog/category_admin/new.html.twig
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends 'admin/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="mr-auto">
|
||||||
|
<h1 class="display-8">Nouvelle catégorie</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{{ path('admin_blog_category_index') }}" class="btn btn-light">
|
||||||
|
<span class="fa fa-list pr-1"></span>
|
||||||
|
|
||||||
|
Retour à la liste
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button type="submit" form="form-main" class="btn btn-primary">
|
||||||
|
<span class="fa fa-save pr-1"></span>
|
||||||
|
|
||||||
|
Enregistrer
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ app.request.uri }}" method="post" id="form-main">
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active">
|
||||||
|
<div class="tab-form">
|
||||||
|
{{ include('blog/category_admin/_form.html.twig') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ form_rest(form) }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
83
templates/blog/category_admin/show.html.twig
Normal file
83
templates/blog/category_admin/show.html.twig
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
{% extends 'admin/layout.html.twig' %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="bg-light pl-5 pr-4 pt-5 pb-5">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="mr-auto">
|
||||||
|
<h1 class="display-8">{{ entity.title }}</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ml-auto">
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{{ path('admin_blog_category_index') }}" class="btn btn-secondary">
|
||||||
|
<span class="fa fa-list pr-1"></span>
|
||||||
|
|
||||||
|
Retour à la liste
|
||||||
|
</a>
|
||||||
|
<a href="{{ path('admin_blog_category_edit', {entity: entity.id}) }}" class="btn btn-primary">
|
||||||
|
<span class="fa fa-edit pr-1"></span>
|
||||||
|
|
||||||
|
Éditer
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4 p-3">
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2">Titre</span><br>
|
||||||
|
|
||||||
|
{{ entity.title }}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2">Sous-titre</span><br>
|
||||||
|
|
||||||
|
{{ entity.subTitle }}
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<span class="font-weight-bold pb-2">URL</span><br>
|
||||||
|
|
||||||
|
{{ absolute_url('/' ~ entity.slug) }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-8 p-3">
|
||||||
|
<div class="font-weight-bold pb-2">Description</div>
|
||||||
|
|
||||||
|
{{ entity.description|raw|nl2br }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12">
|
||||||
|
<table class="table">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th>Derniers articles</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in posts %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ item.post }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center p-4 text-black-50">
|
||||||
|
<div class="display-1">
|
||||||
|
<span class="fa fa-search"></span>
|
||||||
|
</div>
|
||||||
|
<div class="display-5 mt-3">
|
||||||
|
Aucun résultat
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -5771,10 +5771,10 @@ timsort@^0.3.0:
|
||||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||||
|
|
||||||
tinymce@^5.2.0:
|
tinymce@^5.7.1:
|
||||||
version "5.7.0"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.7.0.tgz#bc565877e4041db83848a330dfa7916993110cba"
|
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.7.1.tgz#658a6fb4c7d53a8496cc00f8da33f4b8290da06d"
|
||||||
integrity sha512-WikgMpJbqYPaucV3lfstCj+Y4NquZlK61gyuJ2eqDUBlSU+4fFh6rpwnelVTxuvtEyJsAVOl8HZmbzBfDJiLsQ==
|
integrity sha512-1gY8RClc734srSlkYwY0MQzmkS1j73PuPC+nYtNtrrQVPY9VNcZ4bOiRwzTbdjPPD8GOtv6BAk8Ww/H2RiqKpA==
|
||||||
|
|
||||||
tmp@^0.2.1:
|
tmp@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
|
|
Loading…
Reference in a new issue