From 6e41654fe45fc6fdbfec5a5b6fe1b2a9472c75d4 Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Thu, 19 Dec 2024 07:22:59 +0100 Subject: [PATCH 01/11] Transition to enum statuses WIP Signed-off-by: Sergio Mendolia --- migrations/Version20241218154423.php | 38 +++++ migrations/Version20241218154713.php | 31 ++++ src/Controller/AuthorController.php | 41 +++++ src/Controller/BookController.php | 25 ++- src/Controller/DefaultController.php | 3 +- src/Controller/SerieController.php | 48 ++++++ src/Entity/Book.php | 10 +- src/Entity/BookInteraction.php | 79 +++++++++ src/Entity/User.php | 7 - src/Enum/AgeCategory.php | 12 ++ src/Enum/ReadStatus.php | 12 ++ src/Enum/ReadingList.php | 12 ++ src/Form/BookFilterType.php | 58 +++---- src/Form/InlineInteractionType.php | 39 ----- src/Repository/BookRepository.php | 54 ++++++ src/Service/BookProgressionService.php | 11 +- src/Twig/Components/AddBookToShelf.php | 80 --------- src/Twig/Components/InlineEditBook.php | 2 +- src/Twig/Components/InlineEditInteraction.php | 67 +++----- src/Twig/FilteredBookUrl.php | 26 +++ templates/author/detail.html.twig | 44 +++++ templates/book/index.html.twig | 1 - templates/components/AddBookToShelf.html.twig | 35 ---- .../InlineEditInteraction.html.twig | 159 +++++++++--------- templates/default/index.html.twig | 4 + templates/serie/detail.html.twig | 40 +++++ tests/Service/BookProgressionServiceTest.php | 7 +- 27 files changed, 603 insertions(+), 342 deletions(-) create mode 100644 migrations/Version20241218154423.php create mode 100644 migrations/Version20241218154713.php create mode 100644 src/Controller/AuthorController.php create mode 100644 src/Controller/SerieController.php create mode 100644 src/Enum/AgeCategory.php create mode 100644 src/Enum/ReadStatus.php create mode 100644 src/Enum/ReadingList.php delete mode 100644 src/Form/InlineInteractionType.php delete mode 100644 src/Twig/Components/AddBookToShelf.php create mode 100644 templates/author/detail.html.twig delete mode 100644 templates/components/AddBookToShelf.html.twig create mode 100644 templates/serie/detail.html.twig diff --git a/migrations/Version20241218154423.php b/migrations/Version20241218154423.php new file mode 100644 index 00000000..d0aebb6c --- /dev/null +++ b/migrations/Version20241218154423.php @@ -0,0 +1,38 @@ +addSql('ALTER TABLE book_interaction ADD read_status VARCHAR(255) DEFAULT \'rs-not-started\' NOT NULL, ADD reading_list VARCHAR(255) DEFAULT \'rl-undefined\' NOT NULL'); + + $this->addSql('UPDATE book_interaction SET read_status = \'rs-finished\' WHERE finished =1'); + $this->addSql('UPDATE book_interaction SET read_status = \'rs-started\' WHERE finished =0 and read_pages > 0'); + $this->addSql('UPDATE book_interaction SET read_status = \'rs-not-started\' WHERE finished =0 and read_pages = 0'); + + $this->addSql('UPDATE book_interaction SET reading_list = \'rl-ignored\' WHERE hidden =1'); + $this->addSql('UPDATE book_interaction SET reading_list = \'rl-to-read\' WHERE hidden =0 and favorite = 1'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE book_interaction DROP read_status, DROP reading_list'); + } +} diff --git a/migrations/Version20241218154713.php b/migrations/Version20241218154713.php new file mode 100644 index 00000000..0a7c3098 --- /dev/null +++ b/migrations/Version20241218154713.php @@ -0,0 +1,31 @@ +addSql('ALTER TABLE book_interaction ADD rating INT DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE book_interaction DROP rating'); + } +} diff --git a/src/Controller/AuthorController.php b/src/Controller/AuthorController.php new file mode 100644 index 00000000..6e1ea935 --- /dev/null +++ b/src/Controller/AuthorController.php @@ -0,0 +1,41 @@ +findByAuthor($name); + + $series = []; + $otherBooks = []; + foreach ($books as $book) { + if ($book->getSerie() !== null) { + $series[$book->getSerie()] = $book->getSerie(); + } else { + $otherBooks[] = $book; + } + } + + $firstBooks = []; + foreach ($series as $serie) { + $firstUnreadBook = $bookRepository->getFirstUnreadBook($serie); + $firstBooks[$serie] = $firstUnreadBook; + } + + return $this->render('author/detail.html.twig', [ + 'author' => $name, + 'firstBooks' => $firstBooks, + 'otherBooks' => $otherBooks, + ]); + } +} diff --git a/src/Controller/BookController.php b/src/Controller/BookController.php index c2d59c53..2ea88686 100644 --- a/src/Controller/BookController.php +++ b/src/Controller/BookController.php @@ -5,6 +5,7 @@ use App\Entity\Book; use App\Entity\BookInteraction; use App\Entity\User; +use App\Enum\ReadStatus; use App\Repository\BookRepository; use App\Security\Voter\BookVoter; use App\Security\Voter\RelocationVoter; @@ -198,14 +199,17 @@ public function read( $page = max(1, $page); - if (!$interaction->isFinished() && $interaction->getReadPages() < $page) { + if ($interaction->getReadStatus() !== ReadStatus::Finished && $interaction->getReadPages() < $page) { $interaction->setReadPages($page); } $manager->persist($interaction); $manager->flush(); - if (!$interaction->isFinished() && $page === $book->getPageNumber()) { - $interaction->setFinished(true); + if ($interaction->getReadStatus() !== ReadStatus::Finished && $page === $book->getPageNumber()) { + $interaction->setReadStatus(ReadStatus::Finished); + + //TODO Add next unread in serie to reading list? + $interaction->setFinishedDate(new \DateTime()); $this->addFlash('success', 'Book finished! Congratulations!'); $manager->flush(); @@ -382,12 +386,17 @@ public function consume(Request $request, BookFileSystemManagerInterface $fileSy #[Route('/relocate/{id}/files', name: 'app_book_relocate')] public function relocate(Request $request, Book $book, BookFileSystemManagerInterface $fileSystemManager, EntityManagerInterface $entityManager): Response { - if (!$this->isGranted(RelocationVoter::RELOCATE, $book)) { - throw $this->createAccessDeniedException('Book relocation is not allowed'); + try { + if (!$this->isGranted(RelocationVoter::RELOCATE, $book)) { + throw $this->createAccessDeniedException('Book relocation is not allowed'); + } + $book = $fileSystemManager->renameFiles($book); + $entityManager->persist($book); + $entityManager->flush(); + $this->addFlash('success', 'Book relocated.'); + } catch (\Exception $e) { + $this->addFlash('danger', 'Error during relocation: ' . $e->getMessage()); } - $book = $fileSystemManager->renameFiles($book); - $entityManager->persist($book); - $entityManager->flush(); return $this->redirect($request->headers->get('referer') ?? '/'); } diff --git a/src/Controller/DefaultController.php b/src/Controller/DefaultController.php index 0d2f6209..57cdce7c 100644 --- a/src/Controller/DefaultController.php +++ b/src/Controller/DefaultController.php @@ -4,6 +4,7 @@ use Andante\PageFilterFormBundle\PageFilterFormTrait; use App\Form\BookFilterType; +use App\Enum\ReadStatus; use App\Repository\BookInteractionRepository; use App\Repository\BookRepository; use App\Service\FilteredBookUrlGenerator; @@ -64,7 +65,7 @@ public function readingList(BookRepository $bookRepository, BookInteractionRepos 'finished' => [], ]; foreach ($readList as $bookInteraction) { - if ($bookInteraction->isFinished()) { + if ($bookInteraction->getReadStatus() === ReadStatus::Finished) { $statuses['finished'][] = $bookInteraction->getBook(); } else { $statuses['unread'][] = $bookInteraction->getBook(); diff --git a/src/Controller/SerieController.php b/src/Controller/SerieController.php new file mode 100644 index 00000000..36e1f256 --- /dev/null +++ b/src/Controller/SerieController.php @@ -0,0 +1,48 @@ +findBySerie($name); + + if ($books === []) { + throw $this->createNotFoundException('No books found for this serie'); + } + + $user = $this->getUser(); + + if (!$user instanceof User) { + throw $this->createAccessDeniedException('Invalid user'); + } + + $authors = []; + + $firstUnreadBook = $bookRepository->getFirstUnreadBook($name); + + foreach ($books as $book) { + foreach ($book->getAuthors() as $author) { + $authors[$author] = $author; + } + } + + + return $this->render('serie/detail.html.twig', [ + 'serie' => $name, + 'books' => $books, + 'authors' => $authors, + 'firstUnreadBook' => $firstUnreadBook + ]); + } +} diff --git a/src/Entity/Book.php b/src/Entity/Book.php index cfbc84ff..0ac184c4 100644 --- a/src/Entity/Book.php +++ b/src/Entity/Book.php @@ -2,6 +2,8 @@ namespace App\Entity; +use App\Enum\AgeCategory; +use App\Enum\ReadingList; use App\Repository\BookRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -109,8 +111,8 @@ class Book #[ORM\ManyToMany(targetEntity: Shelf::class, mappedBy: 'books', cascade: ['persist'])] private Collection $shelves; - #[ORM\Column(nullable: true)] - private ?int $ageCategory = null; + #[ORM\Column(enumType: AgeCategory::class, nullable: true)] + private ?AgeCategory $ageCategory = null; /** * @var Collection @@ -504,12 +506,12 @@ public function removeShelf(Shelf $shelf): static return $this; } - public function getAgeCategory(): ?int + public function getAgeCategory(): ?AgeCategory { return $this->ageCategory; } - public function setAgeCategory(?int $ageCategory): static + public function setAgeCategory(?AgeCategory $ageCategory): static { $this->ageCategory = $ageCategory; diff --git a/src/Entity/BookInteraction.php b/src/Entity/BookInteraction.php index 347b13a0..5bfada27 100644 --- a/src/Entity/BookInteraction.php +++ b/src/Entity/BookInteraction.php @@ -2,6 +2,8 @@ namespace App\Entity; +use App\Enum\ReadStatus; +use App\Enum\ReadingList; use App\Repository\BookInteractionRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -23,12 +25,19 @@ class BookInteraction #[ORM\JoinColumn(nullable: false)] private ?Book $book = null; + /** + * @deprecated Use ReadStatus enum instead + */ #[ORM\Column] private bool $finished = false; #[ORM\Column(nullable: true)] private ?int $readPages = null; + + /** + * @deprecated Use RedingList enum instead + */ #[ORM\Column(nullable: false)] private bool $favorite = false; @@ -43,9 +52,21 @@ class BookInteraction #[Gedmo\Timestampable(on: 'update')] private ?\DateTimeImmutable $updated = null; + /** + * @deprecated Use ReadStatus enum instead + */ #[ORM\Column] private bool $hidden = false; + #[ORM\Column(enumType: ReadStatus::class, options: ['default' => ReadStatus::NotStarted])] + private ReadStatus $readStatus = ReadStatus::NotStarted; + + #[ORM\Column(enumType: ReadingList::class, options: ['default' => ReadingList::NotDefined])] + private ReadingList $readingList = ReadingList::NotDefined; + + #[ORM\Column(nullable: true)] + private ?int $rating = null; + public function getId(): ?int { return $this->id; @@ -75,11 +96,17 @@ public function setBook(?Book $book): static return $this; } + /** + * @deprecated Use ReadStatus enum instead + */ public function isFinished(): bool { return $this->finished; } + /** + * @deprecated Use ReadStatus enum instead + */ public function setFinished(bool $finished): static { $this->finished = $finished; @@ -87,11 +114,17 @@ public function setFinished(bool $finished): static return $this; } + /** + * @deprecated Use readingList enum instead + */ public function isFavorite(): bool { return $this->favorite; } + /** + * @deprecated Use readingList enum instead + */ public function setFavorite(bool $favorite): static { $this->favorite = $favorite; @@ -123,6 +156,10 @@ public function getUpdated(): ?\DateTimeImmutable public function getReadPages(): ?int { + if ($this->readStatus !== ReadStatus::Started) { + return null; + } + return $this->readPages; } @@ -136,15 +173,57 @@ public function setUpdated(?\DateTimeImmutable $updated): void $this->updated = $updated; } + /** + * @deprecated Use readinglist enum instead + */ public function isHidden(): bool { return $this->hidden; } + /** + * @deprecated Use readinglist enum instead + */ public function setHidden(bool $hidden): static { $this->hidden = $hidden; return $this; } + + public function getReadStatus(): ReadStatus + { + return $this->readStatus; + } + + public function setReadStatus(ReadStatus $readStatus): static + { + $this->readStatus = $readStatus; + + return $this; + } + + public function getReadingList(): ReadingList + { + return $this->readingList; + } + + public function setReadingList(ReadingList $readingList): static + { + $this->readingList = $readingList; + + return $this; + } + + public function getRating(): ?int + { + return $this->rating; + } + + public function setRating(?int $rating): static + { + $this->rating = $rating; + + return $this; + } } diff --git a/src/Entity/User.php b/src/Entity/User.php index f8ae49fe..6a83d43a 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -15,13 +15,6 @@ #[ORM\Table(name: '`user`')] class User implements UserInterface, PasswordAuthenticatedUserInterface { - public const AGE_CATEGORIES = [ - 'E (Everyone)' => '1', - 'E10+ (10 and more)' => '2', - 'T (13 and more)' => '3', - 'M (17 and more)' => '4', - 'A (Adults only)' => '5', - ]; #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column] diff --git a/src/Enum/AgeCategory.php b/src/Enum/AgeCategory.php new file mode 100644 index 00000000..624556ea --- /dev/null +++ b/src/Enum/AgeCategory.php @@ -0,0 +1,12 @@ +add('read', ChoiceType::class, [ - 'choices' => [ - 'filter.read.any' => '', - 'filter.read.yes' => 'read', - 'filter.read.no' => 'unread', - ], + $builder->add('read', EnumType::class, [ 'required' => false, + 'class' => ReadStatus::class, 'label' => 'filter.read', 'mapped' => false, - 'target_callback' => function (QueryBuilder $qb, ?string $readValue): void { - switch ($readValue) { - case 'read': - $qb->andWhere('bookInteraction.finished = true'); - break; - case 'unread': - $qb->andWhere('(bookInteraction.finished = false OR bookInteraction.finished IS NULL)'); - break; + 'target_callback' => function (QueryBuilder $qb, ?ReadStatus $readValue): void { + if ($readValue !== null) { + $qb->andWhere('bookInteraction.readStatus = :readstatus'); + $qb->setParameter('readstatus', $readValue); } }, ]); @@ -245,44 +241,32 @@ public function buildForm(FormBuilderInterface $builder, array $options): void }, ]); - $builder->add('age', ChoiceType::class, [ - 'choices' => User::AGE_CATEGORIES + ['filter.age.notset' => 'null'], + $builder->add('age', EnumType::class, [ + 'class' => AgeCategory::class, 'required' => false, 'mapped' => false, 'expanded' => false, 'label' => 'filter.age', 'multiple' => false, - 'target_callback' => function (QueryBuilder $qb, ?string $readValue): void { - if ($readValue === null) { - return; - } + 'target_callback' => function (QueryBuilder $qb, ?AgeCategory $readValue): void { - if ($readValue === 'null') { - $qb->andWhere('book.ageCategory is null'); - } else { + if ($readValue !== null) { $qb->andWhere($qb->expr()->in('book.ageCategory', ':ageCategory')); $qb->setParameter('ageCategory', $readValue); } }, ]); - $builder->add('favorite', ChoiceType::class, [ - 'choices' => [ - 'filter.favorite.any' => '', - 'filter.favorite.yes' => 'favorite', - 'filter.favorite.no' => 'notfavorite', - ], + $builder->add('favorite', EnumType::class, [ + 'class' => ReadingList::class, 'required' => false, 'label' => 'filter.favorite', 'mapped' => false, - 'target_callback' => function (QueryBuilder $qb, ?string $readValue): void { - switch ($readValue) { - case 'favorite': - $qb->andWhere('bookInteraction.favorite = true'); - break; - case 'notfavorite': - $qb->andWhere('(bookInteraction.favorite = false OR bookInteraction.favorite IS NULL)'); - break; + 'target_callback' => function (QueryBuilder $qb, ?ReadingList $readValue): void { + + if ($readValue !== null) { + $qb->andWhere('bookInteraction.readingList = :readinglist'); + $qb->setParameter('readinglist', $readValue); } }, ]); diff --git a/src/Form/InlineInteractionType.php b/src/Form/InlineInteractionType.php deleted file mode 100644 index 297fbc20..00000000 --- a/src/Form/InlineInteractionType.php +++ /dev/null @@ -1,39 +0,0 @@ -add('finished', CheckboxType::class, [ - 'required' => false, - ]) - ->add('favorite', CheckboxType::class, [ - 'required' => false, - ]) - ->add('finishedDate', null, [ - 'widget' => 'single_text', - 'html5' => true, - 'required' => false, - ]) - ->add('submit', SubmitType::class) - ; - } - - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefaults([ - 'data_class' => BookInteraction::class, - 'csrf_protection' => false, - ]); - } -} diff --git a/src/Repository/BookRepository.php b/src/Repository/BookRepository.php index 9a89c317..15d9e8fc 100644 --- a/src/Repository/BookRepository.php +++ b/src/Repository/BookRepository.php @@ -6,6 +6,7 @@ use App\Entity\BookInteraction; use App\Entity\KoboDevice; use App\Entity\User; +use App\Enum\ReadStatus; use App\Kobo\SyncToken; use App\Service\ShelfManager; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; @@ -13,6 +14,7 @@ use Doctrine\ORM\QueryBuilder; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * @extends ServiceEntityRepository @@ -248,6 +250,58 @@ public function findByTag(string $tag, ?int $limit = null): mixed return $qb->getQuery()->getResult(); } + /*** + * @param string $serie + * @return Book[] + */ + public function findBySerie(string $serie): array + { + $qb = $this->getAllBooksQueryBuilder(); + + $qb->andWhere('book.serie=:serie'); + $qb->setParameter('serie', $serie); + + $qb->orderBy('book.serieIndex', 'ASC'); + + + $user = $this->security->getUser(); + if ($user instanceof User) { + $qb->andWhere('COALESCE(book.ageCategory,1) <= COALESCE(:ageCategory,10)'); + $qb->setParameter('ageCategory', $user->getMaxAgeCategory()); + } + + $result = $qb->getQuery()->getResult(); + if (!is_array($result)) { + return []; + } + return $result; + } + + public function getFirstUnreadBook(string $serie): Book + { + $books = $this->findBySerie($serie); + if ($books === []) { + throw new NotFoundHttpException('No books found for this serie'); + } + $firstUnreadBook = null; + foreach ($books as $book) { + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new \RuntimeException('Invalid user'); + } + $li = $book->getLastInteraction($user); + if ($firstUnreadBook === null && ($li === null || $li->getReadStatus() !== ReadStatus::Finished)) { + $firstUnreadBook = $book; + } + } + + if ($firstUnreadBook === null) { + $firstUnreadBook = $books[0]; + } + + return $firstUnreadBook; + } + public function getAllSeries(): Query { return $this->createQueryBuilder('serie') diff --git a/src/Service/BookProgressionService.php b/src/Service/BookProgressionService.php index 595b6bf0..cc6db88a 100644 --- a/src/Service/BookProgressionService.php +++ b/src/Service/BookProgressionService.php @@ -5,6 +5,7 @@ use App\Entity\Book; use App\Entity\BookInteraction; use App\Entity\User; +use App\Enum\ReadStatus; use Doctrine\ORM\EntityManagerInterface; use Kiwilan\Ebook\Ebook; use Psr\Log\LoggerInterface; @@ -43,7 +44,7 @@ public function setProgression(Book $book, User $user, ?float $progress): self $interaction = $book->getLastInteraction($user); if ($interaction instanceof BookInteraction) { $interaction->setReadPages(null); - $interaction->setFinished(false); + $interaction->setReadStatus(ReadStatus::NotStarted); } return $this; @@ -66,8 +67,12 @@ public function setProgression(Book $book, User $user, ?float $progress): self $this->em->persist($interaction); $book->addBookInteraction($interaction); } - $interaction->setReadPages(intval($readPages)); - $interaction->setFinished($progress >= 1.0); + $interaction->setReadPages((int)$readPages); + if ($progress >= 1.0) { + $interaction->setReadStatus(ReadStatus::Finished); + } else { + $interaction->setReadStatus(ReadStatus::Started); + } return $this; } diff --git a/src/Twig/Components/AddBookToShelf.php b/src/Twig/Components/AddBookToShelf.php deleted file mode 100644 index 987aae6a..00000000 --- a/src/Twig/Components/AddBookToShelf.php +++ /dev/null @@ -1,80 +0,0 @@ -getRepository(Shelf::class); - - $this->shelves = $shelfRepository->findBy(['user' => $security->getUser()]); - $this->shelves = array_filter($this->shelves, static fn ($item) => $item->getQueryString() === null); - } - - #[LiveAction] - public function add(EntityManagerInterface $entityManager, #[LiveArg] int $shelf): void - { - $shelfRepository = $entityManager->getRepository(Shelf::class); - - $shelf = $shelfRepository->find($shelf); - - if (null === $shelf) { - throw new \RuntimeException('Shelf not found'); - } - - $this->book->addShelf($shelf); - - $entityManager->flush(); - - $this->flashMessage = 'Added to shelf'; - } - - #[LiveAction] - public function remove(EntityManagerInterface $entityManager, #[LiveArg] int $shelf): void - { - $shelfRepository = $entityManager->getRepository(Shelf::class); - - $shelf = $shelfRepository->find($shelf); - - if (null === $shelf) { - throw new \RuntimeException('Shelf not found'); - } - - $this->book->removeShelf($shelf); - - $entityManager->flush(); - - $this->flashMessage = 'Removed from shelf'; - } -} diff --git a/src/Twig/Components/InlineEditBook.php b/src/Twig/Components/InlineEditBook.php index f1f024a2..b5433675 100644 --- a/src/Twig/Components/InlineEditBook.php +++ b/src/Twig/Components/InlineEditBook.php @@ -21,7 +21,7 @@ class InlineEditBook extends AbstractController use ValidatableComponentTrait; use ComponentToolsTrait; - #[LiveProp(writable: ['title', 'serie', 'serieIndex', 'publisher', 'verified', 'summary', 'authors', 'tags', 'ageCategory', 'pageNumber'])] + #[LiveProp(writable: ['title', 'serie', 'serieIndex', 'publisher', 'verified', 'summary', 'authors', 'tags', 'pageNumber'])] public Book $book; #[LiveProp()] diff --git a/src/Twig/Components/InlineEditInteraction.php b/src/Twig/Components/InlineEditInteraction.php index 03ce16fa..48dfc302 100644 --- a/src/Twig/Components/InlineEditInteraction.php +++ b/src/Twig/Components/InlineEditInteraction.php @@ -4,8 +4,10 @@ use App\Entity\Book; use App\Entity\BookInteraction; +use App\Entity\Shelf; use App\Entity\User; -use App\Form\InlineInteractionType; +use App\Enum\ReadingList; +use App\Enum\ReadStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormFactoryInterface; @@ -17,16 +19,16 @@ use Symfony\UX\LiveComponent\ComponentWithFormTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\ValidatableComponentTrait; +use Symfony\UX\TwigComponent\Attribute\PostMount; #[AsLiveComponent(method: 'get')] class InlineEditInteraction extends AbstractController { use DefaultActionTrait; use ValidatableComponentTrait; - use ComponentWithFormTrait; use ComponentToolsTrait; - #[LiveProp(writable: ['finished', 'favorite'])] + #[LiveProp(writable: true)] public ?BookInteraction $interaction = null; #[LiveProp()] @@ -36,14 +38,25 @@ class InlineEditInteraction extends AbstractController public ?string $flashMessage = null; public ?string $flashMessageFav = null; + /** + * @var Shelf[] + */ + public array $shelves = []; public function __construct(private EntityManagerInterface $entityManager, private FormFactoryInterface $formFactory) { + } - protected function instantiateForm(): FormInterface + #[PostMount] + public function postMount(): void { - return $this->formFactory->createNamed(uniqid('interactionform-', false), InlineInteractionType::class, $this->getInteraction(), ['method' => 'POST']); + $this->interaction = $this->getInteraction(); + + $shelfRepository = $this->entityManager->getRepository(Shelf::class); + + $this->shelves = $shelfRepository->findBy(['user' => $this->user]); + $this->shelves = array_filter($this->shelves, static fn($item) => $item->getQueryString() === null); } private function getInteraction(): BookInteraction @@ -57,7 +70,6 @@ private function getInteraction(): BookInteraction $interaction->setUser($this->user); $interaction->setBook($this->book); $interaction->setReadPages(0); - $interaction->setFinishedDate(new \DateTime('now')); } } @@ -65,61 +77,30 @@ private function getInteraction(): BookInteraction } #[LiveAction] - public function toggle(): void + public function toggleReadStatus(): void { $interaction = $this->getInteraction(); - $interaction->setFinished(!$interaction->isFinished()); + $interaction->setReadStatus(ReadStatus::toggle($interaction->getReadStatus())); $this->entityManager->persist($interaction); $this->entityManager->flush(); $this->interaction = $interaction; - $this->flashMessage = 'Good Job!'; - } - - #[LiveAction] - public function saveInteraction(): void - { - $this->submitForm(); - - $interaction = $this->getForm()->getData(); - - if (!$interaction instanceof BookInteraction) { - throw new \RuntimeException('Invalid data'); - } - - $this->entityManager->persist($interaction); - $this->entityManager->flush(); - $this->flashMessageFav = 'Saved'; - $this->dispatchBrowserEvent('manager:flush'); - } - - #[LiveAction] - public function toggleFavorite(EntityManagerInterface $entityManager): void - { - $interaction = $this->getInteraction(); - - $interaction->setFavorite(!$interaction->isFavorite()); - - $entityManager->persist($interaction); - $entityManager->flush(); - $this->interaction = $interaction; - - $this->flashMessageFav = 'Add to readlist'; + $this->flashMessage = 'Read status updated'; } #[LiveAction] - public function toggleHidden(EntityManagerInterface $entityManager): void + public function toggleReadingList(EntityManagerInterface $entityManager): void { $interaction = $this->getInteraction(); - $interaction->setHidden(!$interaction->isHidden()); + $interaction->setReadingList(ReadingList::toggle($interaction->getReadingList())); $entityManager->persist($interaction); $entityManager->flush(); $this->interaction = $interaction; - $this->flashMessageFav = 'Hidden'; + $this->flashMessageFav = 'Reading list updated'; } } diff --git a/src/Twig/FilteredBookUrl.php b/src/Twig/FilteredBookUrl.php index a66a6b02..17950a09 100644 --- a/src/Twig/FilteredBookUrl.php +++ b/src/Twig/FilteredBookUrl.php @@ -26,6 +26,32 @@ public function getFunctions(): array public function filteredBookUrl(array $params): string { $params = $this->filteredBookUrlGenerator->getParametersArray($params); + $defaultParams = $this->filteredBookUrlGenerator::FIELDS_DEFAULT_VALUE; + + $modif = array_udiff_uassoc($params, $defaultParams, static function ($a, $b) { + if ($a === $b) { + return 0; + } + return 1; + }, static function ($a, $b) { + if ($a === $b) { + return 0; + } + return 1; + }); + unset($modif['submit'], $modif['orderBy']); + + if (count($modif) === 1) { + $key = array_key_first($modif); + $route = match ($key) { + 'authors' => 'app_author_detail', + 'serie' => 'app_serie_detail', + default => 'app_allbooks', + }; + if ($route !== 'app_allbooks') { + return $this->router->generate($route, ['name' => $modif[$key]]); + } + } return $this->router->generate('app_allbooks', $params); } diff --git a/templates/author/detail.html.twig b/templates/author/detail.html.twig new file mode 100644 index 00000000..d7776a20 --- /dev/null +++ b/templates/author/detail.html.twig @@ -0,0 +1,44 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ author }}{% endblock %} + +{% block body %} + +
+ +
+

{{ author }}

+
+
+ +
+
+

{{ 'Series'|trans }}

+
+ {% for serie, book in firstBooks %} +
+

{{ serie }}

+ + {% include 'book/_teaser.html.twig' with {book: book} %} +
+ {% endfor %} +
+
+
+ +
+
+

{{ 'Other books'|trans }}

+
+ {% for book in otherBooks %} +
+ {% include 'book/_teaser.html.twig' with {book: book} %} +
+ {% endfor %} +
+
+
+ + + +{% endblock %} diff --git a/templates/book/index.html.twig b/templates/book/index.html.twig index 50208da4..3f29c47a 100644 --- a/templates/book/index.html.twig +++ b/templates/book/index.html.twig @@ -30,7 +30,6 @@ {% if not book.verified %} {{ component('UploadBookPicture',{'book':book}) }} {% endif %} - {{ component('AddBookToShelf',{'book':book, 'user':app.user}) }}
diff --git a/templates/components/AddBookToShelf.html.twig b/templates/components/AddBookToShelf.html.twig deleted file mode 100644 index 9c6c2972..00000000 --- a/templates/components/AddBookToShelf.html.twig +++ /dev/null @@ -1,35 +0,0 @@ -
-
Shelves
-
- {% for shelf in shelves %} - - {% if shelf in book.shelves %} - - {% else %} - - {% endif %} - {% endfor %} -
- {% if flashMessage %} -
- {{ flashMessage }} -
- {% endif %} - -
diff --git a/templates/components/InlineEditInteraction.html.twig b/templates/components/InlineEditInteraction.html.twig index 6b05ffc8..4aee2682 100644 --- a/templates/components/InlineEditInteraction.html.twig +++ b/templates/components/InlineEditInteraction.html.twig @@ -1,87 +1,86 @@
+ +
+ + - {% set hidden=false %} - {% if interaction is not null %} - {% if interaction.hidden %} - {% set hidden=true %} - {% endif %} - {% endif %} +
- - - {% set favorite = false %} - {% if interaction is not null %} - {% if interaction.favorite %} - {% set favorite = true %} - {% endif %} - {% endif %} - - - - - - - {% component BootstrapModal with {id: modalId} %} - {% block modal_header %} -
{{ book.title }}
- - {% endblock %} - {% block modal_body %} - - {{ form(form,{ - attr: { - 'novalidate': true, - 'class': '', - 'data-action': 'live#action', - 'data-live-action-param': 'prevent|saveInteraction', - } - }) }} - {% endblock %} - - {% endcomponent %}
\ No newline at end of file diff --git a/templates/default/index.html.twig b/templates/default/index.html.twig index cbdc657a..563da799 100644 --- a/templates/default/index.html.twig +++ b/templates/default/index.html.twig @@ -66,6 +66,10 @@ {{ form_row(form.orderBy) }} +
+ {{ form_row(form.age) }} + +
{{ form_rest(form) }} {{ form_end(form) }} diff --git a/templates/serie/detail.html.twig b/templates/serie/detail.html.twig new file mode 100644 index 00000000..3da796ba --- /dev/null +++ b/templates/serie/detail.html.twig @@ -0,0 +1,40 @@ +{% extends 'base.html.twig' %} + +{% block title %}{{ serie }}{% endblock %} + +{% block body %} + +
+
+ {{ firstUnreadBook.title }} + Livre +
+
+

{{ serie }}

+

{% for author in authors %} + {{ author }}{% endfor %}

+
+
+ {{ dump(stats) }} +
+
+

{{ 'books'|trans }}

+
+ {% for book in books %} +
+ {% include 'book/_teaser.html.twig' with {book: book} %} +
+
+

{{ book.title }}

+
{{ book.serie }} #{{ book.serieIndex }}
+
+ {% endfor %} +
+
+
+ + + +{% endblock %} diff --git a/tests/Service/BookProgressionServiceTest.php b/tests/Service/BookProgressionServiceTest.php index 21e41f7f..fc3373db 100644 --- a/tests/Service/BookProgressionServiceTest.php +++ b/tests/Service/BookProgressionServiceTest.php @@ -7,6 +7,7 @@ use App\Entity\Book; use App\Entity\BookInteraction; use App\Entity\User; +use App\Enum\ReadStatus; use App\Repository\BookRepository; use App\Repository\UserRepository; use App\Service\BookProgressionService; @@ -63,7 +64,7 @@ public function testProgressMax(): void $service->setProgression($book, $this->getUser(), 1); $lastInteraction = $book->getLastInteraction($this->getUser()); self::assertNotNull($lastInteraction, 'Interaction should be created'); - self::assertTrue($lastInteraction->isFinished(), 'Book should be finished'); + self::assertSame($lastInteraction->getReadStatus(), ReadStatus::Finished, 'Book should be finished'); self::assertSame(30, $lastInteraction->getReadPages(), 'Book should have all page read'); } @@ -77,7 +78,7 @@ public function testProgressReading(): void $lastInteraction = $book->getLastInteraction($this->getUser()); self::assertNotNull($lastInteraction, 'Interaction should be created'); - self::assertFalse($lastInteraction->isFinished(), 'Book should not be finished'); + self::assertSame($lastInteraction->getReadStatus(), ReadStatus::Finished, 'Book should not be finished'); self::assertSame(15, $lastInteraction->getReadPages(), 'Book should have half page read'); } @@ -96,7 +97,7 @@ public function testMarkAsUnread(): void $lastInteraction = $book->getLastInteraction($this->getUser()); self::assertNotNull($lastInteraction, 'Interaction should be created'); - self::assertFalse($lastInteraction->isFinished(), 'Book should not be finished'); + self::assertNotSame($lastInteraction->getReadStatus(), ReadStatus::Finished, 'Book should not be finished'); self::assertNull($lastInteraction->getReadPages(), 'Book should have null page read'); } From 668a8b42d564497d7c422da0e48cb8e215ffb79a Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Thu, 9 Jan 2025 10:03:28 +0100 Subject: [PATCH 02/11] Refactor enums and interactions for Books and UI enhancements. Introduced consistent enums for age categories, reading status, and reading lists with labels for improved translation and UI display. Refactored interactions logic in repositories for better query uniformity. Updated templates for cleaner rendering and added missing inline editing capabilities for age categories. ``` --- config/packages/acseo_typesense.yaml | 4 +- config/packages/framework.yaml | 1 + src/Entity/Book.php | 12 ++- src/Enum/AgeCategory.php | 29 ++++++-- src/Enum/ReadStatus.php | 13 ++++ src/Enum/ReadingList.php | 13 ++++ src/Repository/BookInteractionRepository.php | 27 +++---- src/Repository/BookRepository.php | 74 ++++++++++--------- src/Service/Search/SearchHelper.php | 2 +- src/Twig/Components/AiSuggestion.php | 2 +- src/Twig/Components/InlineEditBook.php | 24 ++++++ src/Twig/FilteredBookUrl.php | 39 ++++------ templates/book/_teaser.html.twig | 36 +++++---- templates/components/InlineEditBook.html.twig | 17 ++--- templates/components/_facet.html.twig | 6 +- templates/serie/detail.html.twig | 39 ++++++---- translations/messages+intl-icu.en.yaml | 15 ++-- translations/messages+intl-icu.fr.yaml | 15 ++-- 18 files changed, 230 insertions(+), 138 deletions(-) diff --git a/config/packages/acseo_typesense.yaml b/config/packages/acseo_typesense.yaml index 8141cbea..90db9835 100644 --- a/config/packages/acseo_typesense.yaml +++ b/config/packages/acseo_typesense.yaml @@ -55,8 +55,8 @@ acseo_typesense: name: age type: string facet: true - optional: true - entity_attribute: ageCategory + optional: false + entity_attribute: ageCategoryLabel user.read: name: read optional: true diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 32fb174e..95d3e294 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -5,6 +5,7 @@ framework: secret: '%env(APP_SECRET)%' trusted_proxies: '%env(TRUSTED_PROXIES)%' #csrf_protection: true + ide: 'phpstorm://open?file=%%f&line=%%l&/var/www/html/>/Users/sergiomendolia/IdeaProjects/biblioteca/' http_method_override: false handle_all_throwables: true http_cache: true diff --git a/src/Entity/Book.php b/src/Entity/Book.php index e48561d7..5dc61495 100644 --- a/src/Entity/Book.php +++ b/src/Entity/Book.php @@ -4,6 +4,7 @@ use App\Enum\AgeCategory; use App\Enum\ReadingList; +use App\Enum\ReadStatus; use App\Repository\BookRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -518,6 +519,11 @@ public function setAgeCategory(?AgeCategory $ageCategory): static return $this; } + public function getAgeCategoryLabel(): ?string + { + return $this->ageCategory?->label()??'enum.agecategories.notset'; + } + public function getUuid(): string { if ($this->uuid === null) { @@ -604,13 +610,13 @@ public function getUsers(): object continue; } $userId = $user->getId(); - if ($interaction->isFinished()) { + if ($interaction->getReadStatus()===ReadStatus::Finished) { $return['read'][] = $userId; } - if ($interaction->isFavorite()) { + if ($interaction->getReadingList()===ReadingList::ToRead) { $return['favorite'][] = $userId; } - if ($interaction->isHidden()) { + if ($interaction->getReadingList()===ReadingList::Ignored) { $return['hidden'][] = $userId; } } diff --git a/src/Enum/AgeCategory.php b/src/Enum/AgeCategory.php index 624556ea..3526a525 100644 --- a/src/Enum/AgeCategory.php +++ b/src/Enum/AgeCategory.php @@ -4,9 +4,28 @@ enum AgeCategory: int { - case E = 1; - case E10 = 2; - case T = 3; - case M = 4; - case A = 5; + case Everyone = 1; + case TenPlus = 2; + case ThirteenPlus = 3; + case SixteenPlus = 4; + case Adult = 5; + + public function label(): string + { + return self::getLabel($this); + } + + public static function getLabel(?self $value): string + { + if (null === $value) { + return 'enum.agecategories.notset'; + } + return match ($value) { + self::Adult=>'enum.agecategories.adults', + self::Everyone=>'enum.agecategories.everyone', + self::SixteenPlus=>'enum.agecategories.sixteenplus', + self::TenPlus=>'enum.agecategories.tenplus', + self::ThirteenPlus=>'enum.agecategories.thirteenplus', + }; + } } diff --git a/src/Enum/ReadStatus.php b/src/Enum/ReadStatus.php index 1d76ce20..b18d0692 100644 --- a/src/Enum/ReadStatus.php +++ b/src/Enum/ReadStatus.php @@ -9,4 +9,17 @@ enum ReadStatus: string case Started = 'rs-started'; case Finished = 'rs-finished'; + public function label(): string + { + return self::getLabel($this); + } + + public static function getLabel(self $value): string + { + return match ($value) { + self::NotStarted=>'enum.readstatus.not-started', + self::Started=>'enum.readstatus.started', + self::Finished=>'enum.readstatus.finished', + }; + } } diff --git a/src/Enum/ReadingList.php b/src/Enum/ReadingList.php index f5e257f3..7058caa9 100644 --- a/src/Enum/ReadingList.php +++ b/src/Enum/ReadingList.php @@ -9,4 +9,17 @@ enum ReadingList: string case Ignored = 'rl-ignored'; case NotDefined = 'rl-undefined'; + public function label(): string + { + return self::getLabel($this); + } + + public static function getLabel(self $value): string + { + return match ($value) { + self::ToRead=>'enum.readinglist.toread', + self::Ignored=>'enum.readinglist.ignored', + self::NotDefined=>'enum.readinglist.notdefined', + }; + } } diff --git a/src/Repository/BookInteractionRepository.php b/src/Repository/BookInteractionRepository.php index 22a61d73..be912858 100644 --- a/src/Repository/BookInteractionRepository.php +++ b/src/Repository/BookInteractionRepository.php @@ -3,6 +3,8 @@ namespace App\Repository; use App\Entity\BookInteraction; +use App\Enum\ReadingList; +use App\Enum\ReadStatus; use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; use Doctrine\Persistence\ManagerRegistry; use Symfony\Bundle\SecurityBundle\Security; @@ -26,10 +28,12 @@ public function getStartedBooks(): array { $results = $this->createQueryBuilder('b') ->andWhere('b.readPages >0') - ->andWhere('b.finished = false') - ->andWhere('b.hidden = false') + ->andWhere('b.readStatus = :read_status') + ->andWhere('b.readingList != :read_status') ->andWhere('b.user = :val') ->setParameter('val', $this->security->getUser()) + ->setParameter('read_status', ReadStatus::Started) + ->setParameter('reading_list', ReadingList::Ignored) ->orderBy('b.id', 'ASC') ->setMaxResults(10) ->getQuery() @@ -48,12 +52,15 @@ public function getStartedBooks(): array public function getFavourite(?int $max = null, bool $hideFinished = true): array { $qb = $this->createQueryBuilder('b') - ->andWhere('b.favorite = true'); + ->andWhere('b.readingList = :to_read'); + $qb->setParameter('to_read', ReadingList::ToRead); if ($hideFinished) { - $qb->andWhere('b.finished = false'); + $qb->andWhere('b.readingList != :finished'); + $qb->setParameter('finished',ReadStatus::Finished); } - $qb->andWhere('b.hidden = false') + $qb->andWhere('b.readingList != :hidden') + ->setParameter('hidden', ReadingList::Ignored) ->andWhere('b.user = :val') ->setParameter('val', $this->security->getUser()) ->orderBy('b.created', 'ASC') @@ -66,14 +73,4 @@ public function getFavourite(?int $max = null, bool $hideFinished = true): array return $results; } - - // public function findOneBySomeField($value): ?BookInteraction - // { - // return $this->createQueryBuilder('b') - // ->andWhere('b.exampleField = :val') - // ->setParameter('val', $value) - // ->getQuery() - // ->getOneOrNullResult() - // ; - // } } diff --git a/src/Repository/BookRepository.php b/src/Repository/BookRepository.php index 15d9e8fc..97f05536 100644 --- a/src/Repository/BookRepository.php +++ b/src/Repository/BookRepository.php @@ -6,6 +6,7 @@ use App\Entity\BookInteraction; use App\Entity\KoboDevice; use App\Entity\User; +use App\Enum\ReadingList; use App\Enum\ReadStatus; use App\Kobo\SyncToken; use App\Service\ShelfManager; @@ -304,27 +305,27 @@ public function getFirstUnreadBook(string $serie): Book public function getAllSeries(): Query { - return $this->createQueryBuilder('serie') + $qb = $this->createQueryBuilder('serie') ->select('serie.serie as item') ->addSelect('COUNT(serie.id) as bookCount') ->addSelect('MAX(serie.serieIndex) as lastBookIndex') ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->where('serie.serie IS NOT NULL') - ->leftJoin('serie.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user= :user') - ->setParameter('user', $this->security->getUser()) - ->addGroupBy('serie.serie')->getQuery(); + ->where('serie.serie IS NOT NULL'); + $qb = $this->joinInteractions($qb, 'serie'); + + return $qb->addGroupBy('serie.serie')->getQuery(); } public function getIncompleteSeries(): Query { - return $this->createQueryBuilder('serie') + $qb= $this->createQueryBuilder('serie') ->select('serie.serie as item') ->addSelect('COUNT(serie.id) as bookCount') ->addSelect('MAX(serie.serieIndex) as lastBookIndex') - ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->leftJoin('serie.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user= :user') - ->where('serie.serie IS NOT NULL') - ->setParameter('user', $this->security->getUser()) + ->addSelect('COUNT(bookInteraction.finished) as booksFinished'); + $qb = $this->joinInteractions($qb, 'serie'); + + return $qb->where('serie.serie IS NOT NULL') ->addGroupBy('serie.serie') ->having('count(serie.id) != max(serie.serieIndex)') ->getQuery(); @@ -332,29 +333,38 @@ public function getIncompleteSeries(): Query public function getStartedSeries(): Query { - return $this->createQueryBuilder('serie') + $qb = $this->createQueryBuilder('serie') ->select('serie.serie as item') ->addSelect('COUNT(serie.id) as bookCount') ->addSelect('MAX(serie.serieIndex) as lastBookIndex') ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->leftJoin('serie.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user= :user') - ->where('serie.serie IS NOT NULL') - ->setParameter('user', $this->security->getUser()) - ->addGroupBy('serie.serie') - ->having('COUNT(bookInteraction.finished)>0 AND COUNT(bookInteraction.finished)where('serie.serie IS NOT NULL'); + $qb = $this->joinInteractions($qb,'serie'); + + return $qb->addGroupBy('serie.serie') + ->having('COUNT(bookInteraction.readStatus)>0 AND COUNT(bookInteraction.readStatus)getQuery(); } public function getAllPublishers(): Query { - return $this->createQueryBuilder('publisher') + $qb = $this->createQueryBuilder('publisher') ->select('publisher.publisher as item') ->addSelect('COUNT(publisher.id) as bookCount') ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->where('publisher.publisher IS NOT NULL') - ->leftJoin('publisher.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user= :user') - ->setParameter('user', $this->security->getUser()) - ->addGroupBy('publisher.publisher')->getQuery(); + ->where('publisher.publisher IS NOT NULL'); + $qb = $this->joinInteractions($qb, 'publisher'); + + return $qb->addGroupBy('publisher.publisher')->getQuery(); + } + + private function joinInteractions(QueryBuilder $qb, string $alias='book'): QueryBuilder + { + return $qb->leftJoin($alias.'.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.readStatus = :status_finished or + bookInteraction.readingList=:list_ignored) and bookInteraction.user= :user') + ->setParameter('status_finished', ReadStatus::Finished) + ->setParameter('list_ignored', ReadingList::Ignored) + ->setParameter('user', $this->security->getUser()); } /** @@ -365,13 +375,12 @@ public function getAllAuthors(): array $qb = $this->createQueryBuilder('author') ->select('author.authors as item') ->addSelect('COUNT(author.id) as bookCount') - ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->leftJoin('author.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user=:user') - ->setParameter('user', $this->security->getUser()) - ->addGroupBy('author.authors') - ->getQuery(); + ->addSelect('COUNT(bookInteraction.finished) as booksFinished'); + $qb = $this->joinInteractions($qb, 'author'); - return $this->convertResults($qb->getResult()); + $qb->addGroupBy('author.authors'); + + return $this->convertResults($qb->getQuery()->getResult()); } /** @@ -382,13 +391,12 @@ public function getAllTags(): array $qb = $this->createQueryBuilder('tag') ->select('tag.tags as item') ->addSelect('COUNT(tag.id) as bookCount') - ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->leftJoin('tag.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user=:user') - ->setParameter('user', $this->security->getUser()) - ->addGroupBy('tag.tags') - ->getQuery(); + ->addSelect('COUNT(bookInteraction.finished) as booksFinished'); + $qb = $this->joinInteractions($qb, 'tag'); + + $qb->addGroupBy('tag.tags'); - return $this->convertResults($qb->getResult()); + return $this->convertResults($qb->getQuery()->getResult()); } /** diff --git a/src/Service/Search/SearchHelper.php b/src/Service/Search/SearchHelper.php index a7f3f481..fa72b887 100644 --- a/src/Service/Search/SearchHelper.php +++ b/src/Service/Search/SearchHelper.php @@ -28,7 +28,7 @@ public function prepareQuery(string $queryBy, ?string $filterBy = null, ?string $query->sortBy($sortBy ?? ''); $query->numTypos(2); $query->page($page); - $query->facetBy('authors,serie,tags'); + $query->facetBy('authors,serie,tags,age'); $query->addParameter('facet_strategy', 'exhaustive'); $this->query = $query; diff --git a/src/Twig/Components/AiSuggestion.php b/src/Twig/Components/AiSuggestion.php index 169a749b..474b20e2 100644 --- a/src/Twig/Components/AiSuggestion.php +++ b/src/Twig/Components/AiSuggestion.php @@ -23,7 +23,7 @@ final class AiSuggestion { use DefaultActionTrait; - #[LiveProp(writable: ['title', 'serie', 'serieIndex', 'publisher', 'verified', 'summary', 'authors', 'tags', 'ageCategory'])] + #[LiveProp(writable: ['title', 'serie', 'serieIndex', 'publisher', 'verified', 'summary', 'authors', 'tags'])] public Book $book; #[LiveProp()] diff --git a/src/Twig/Components/InlineEditBook.php b/src/Twig/Components/InlineEditBook.php index 86a187ff..7109558c 100644 --- a/src/Twig/Components/InlineEditBook.php +++ b/src/Twig/Components/InlineEditBook.php @@ -3,6 +3,8 @@ namespace App\Twig\Components; use App\Entity\Book; +use App\Entity\Shelf; +use App\Enum\AgeCategory; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; @@ -14,6 +16,7 @@ use Symfony\UX\LiveComponent\ComponentToolsTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\ValidatableComponentTrait; +use Symfony\UX\TwigComponent\Attribute\PostMount; #[AsLiveComponent(method: 'get')] class InlineEditBook extends AbstractController @@ -25,6 +28,9 @@ class InlineEditBook extends AbstractController #[LiveProp(writable: ['title', 'serie', 'serieIndex', 'publisher', 'verified', 'summary', 'authors', 'tags', 'pageNumber'])] public Book $book; + #[LiveProp(writable: true)] + public ?AgeCategory $ageCategory=null; + #[LiveProp()] public bool $isEditing = false; @@ -41,6 +47,16 @@ class InlineEditBook extends AbstractController public ?string $flashMessage = null; + public function __construct(private readonly EntityManagerInterface $entityManager) + { + } + + #[PostMount] + public function postMount(): void + { + $this->ageCategory = $this->book->getAgeCategory(); + } + #[LiveAction] public function activateEditing(): void { @@ -104,6 +120,14 @@ public function save(Request $request, EntityManagerInterface $entityManager): v $this->book->setSerieIndex(null); } + if (array_key_exists('updated', $data) && is_array($data['updated']) && array_key_exists('ageCategory', $data['updated'])) { + if($data['updated']['ageCategory'] !== '') { + $this->book->setAgeCategory(AgeCategory::tryFrom($data['updated']['ageCategory'])); + }else{ + $this->book->setAgeCategory(null); + } + } + $entityManager->flush(); $this->dispatchBrowserEvent('manager:flush'); $this->isEditing = false; diff --git a/src/Twig/FilteredBookUrl.php b/src/Twig/FilteredBookUrl.php index 23cb3301..0d3389bc 100644 --- a/src/Twig/FilteredBookUrl.php +++ b/src/Twig/FilteredBookUrl.php @@ -29,32 +29,23 @@ public function getFunctions(): array public function filteredBookUrl(array $params): string { $params = $this->filteredBookUrlGenerator->getParametersArray($params); - $defaultParams = $this->filteredBookUrlGenerator::FIELDS_DEFAULT_VALUE; - - $modif = array_udiff_uassoc($params, $defaultParams, static function ($a, $b) { - if ($a === $b) { - return 0; - } - return 1; - }, static function ($a, $b) { - if ($a === $b) { - return 0; - } - return 1; - }); - unset($modif['submit'], $modif['orderBy']); - - if (count($modif) === 1) { - $key = array_key_first($modif); - $route = match ($key) { - 'authors' => 'app_author_detail', - 'serie' => 'app_serie_detail', - default => 'app_allbooks', - }; - if ($route !== 'app_allbooks') { - return $this->router->generate($route, ['name' => $modif[$key]]); + + $key = ''; + $value=''; + if (array_key_exists('filterQuery', $params)) { + if (preg_match('/^(serie|authors):=`(.+)`[ ]+$/', $params['filterQuery'], $matches)) { + $key = $matches[1]; + $value = $matches[2]; } } + $route = match ($key) { + 'authors' => 'app_author_detail', + 'serie' => 'app_serie_detail', + default => 'app_allbooks', + }; + if ($route !== 'app_allbooks') { + return $this->router->generate($route, ['name' => $value]); + } return $this->router->generate('app_allbooks', $params); } diff --git a/templates/book/_teaser.html.twig b/templates/book/_teaser.html.twig index 92ffcd50..b968541e 100644 --- a/templates/book/_teaser.html.twig +++ b/templates/book/_teaser.html.twig @@ -1,4 +1,7 @@ {# @var book App\Entity\Book #} +{% if withDetail is not defined %} +{% set withDetail = true %} +{% endif %} {% set read = false %} {% for interaction in book.bookInteractions|default([]) %} {% if interaction.user.id == app.user.id and interaction.finished %} @@ -22,7 +25,7 @@ {% else %} {% set icon='bi-file-binary-fill' %} {% endif %} -
+ \ No newline at end of file diff --git a/templates/components/InlineEditBook.html.twig b/templates/components/InlineEditBook.html.twig index 9b009f36..355ae356 100644 --- a/templates/components/InlineEditBook.html.twig +++ b/templates/components/InlineEditBook.html.twig @@ -64,13 +64,12 @@ {% elseif field=='pageNumber' %} {% endfor %} {% elseif field=='serie' and book.serie is not null %} - +  {{ book.serie }} {% elseif field=='ageCategory' %} - {% for key, value in constant('App\\Entity\\User::AGE_CATEGORIES')|filter((v,k)=>v==book.ageCategory) %} - {{ key }} + {% for value in enum('App\\Enum\\AgeCategory').cases|filter((v,k)=>v==book.ageCategory) %} + {{ value.label|trans }} {% else %} - - + {{ "enum.agecategories.notset"|trans }} {% endfor %} {% else %} {{ attribute(book, "get"~field) }} diff --git a/templates/components/_facet.html.twig b/templates/components/_facet.html.twig index 2e660412..f8661d50 100644 --- a/templates/components/_facet.html.twig +++ b/templates/components/_facet.html.twig @@ -10,7 +10,11 @@ data-live-value-param='{{ value }}' > - {{ label }} + {% if "enum." in label %} + {{ label|trans }} + {% else %} + {{ label }} + {% endif %} {% if count.count is not null %} {{ count.count }} {{ "search.records"|trans }} diff --git a/templates/serie/detail.html.twig b/templates/serie/detail.html.twig index 3da796ba..dd7050f8 100644 --- a/templates/serie/detail.html.twig +++ b/templates/serie/detail.html.twig @@ -5,32 +5,41 @@ {% block body %}
-
+ -
+

{{ serie }}

-

{% for author in authors %} +

{% for author in authors %} {{ author }}{% endfor %}

- {{ dump(stats) }}
-

{{ 'books'|trans }}

+

{{ 'books.in-serie'|trans }}

- {% for book in books %} -
- {% include 'book/_teaser.html.twig' with {book: book} %} -
-
-

{{ book.title }}

-
{{ book.serie }} #{{ book.serieIndex }}
-
- {% endfor %} +
+
+ {% for book in books %} + + + + + + {% endfor %} +
+ {% include 'book/_teaser.html.twig' with {book: book, withDetail: false} %} + +

{{ book.title }}

+
{{ book.serie }} #{{ book.serieIndex }}
+
+ {{ component('InlineEditInteraction', {book:book, user: app.user}) }} +
+
diff --git a/translations/messages+intl-icu.en.yaml b/translations/messages+intl-icu.en.yaml index 1c60a52d..693b430e 100644 --- a/translations/messages+intl-icu.en.yaml +++ b/translations/messages+intl-icu.en.yaml @@ -92,6 +92,12 @@ dashboard.hello: Aloha dashboard.need-inspiration: Need some inspiration? dashboard.reading-list: In your reading list dashboard.series-in-progress: Series that you started +enum.agecategories.notset: Not defined +enum.agecategories.adults: Adults +enum.agecategories.everyone: Everyone +enum.agecategories.sixteenplus: 16+ Years old +enum.agecategories.tenplus: 10+ Years old +enum.agecategories.thirteenplus: 13+ Years old error.error-happened: An error happend error.stack-trace: Stack trace generic.are-you-sure-you-want-to-delete-this-item: Are you sure you want to delete this item? @@ -224,20 +230,15 @@ shelf.query: Query shelf.shelves: Shelves timeline.timeline-for: Timeline for timeline.title: Title -user.agecategories.adults: Adults -user.agecategories.everyone: Everyone -user.agecategories.sixteenplus: 16+ Years old -user.agecategories.tenplus: 10+ Years old -user.agecategories.thirteenplus: 13+ Years old user.edit-user: Edit user -user.form.roles.user: User -user.form.roles.admin: Administrator user.form.language.english: English user.form.language.french: Français user.form.language: Language user.form.leave-blank-to-keep-current-password: Leave blank to keep current password user.form.maxagecategory: Max age category allowed user.form.plainpassword: Password +user.form.roles.admin: Administrator +user.form.roles.user: User user.form.roles: Roles user.form.username: Username user.last-login: Last login diff --git a/translations/messages+intl-icu.fr.yaml b/translations/messages+intl-icu.fr.yaml index 84c6f0d7..19113374 100644 --- a/translations/messages+intl-icu.fr.yaml +++ b/translations/messages+intl-icu.fr.yaml @@ -92,6 +92,12 @@ dashboard.hello: Aloha dashboard.need-inspiration: Besoin d'inspiration? dashboard.reading-list: Dans votre liste de lecture dashboard.series-in-progress: Séries entamées +enum.agecategories.notset: Non défini +enum.agecategories.adults: Adultes +enum.agecategories.everyone: Tout le monde +enum.agecategories.sixteenplus: 16+ ans +enum.agecategories.tenplus: 10+ ans +enum.agecategories.thirteenplus: 13+ ans error.error-happened: Une erreur est survenue error.stack-trace: Stack trace generic.are-you-sure-you-want-to-delete-this-item: Etes-vous sûr(e) de vouloir supprimer cet élément? @@ -224,20 +230,15 @@ shelf.query: Requête shelf.shelves: Etagères timeline.timeline-for: Historique pour timeline.title: Titre -user.agecategories.adults: Adultes -user.agecategories.everyone: Tout le monde -user.agecategories.sixteenplus: 16+ ans -user.agecategories.tenplus: 10+ ans -user.agecategories.thirteenplus: 13+ ans user.edit-user: Modifier l'utilisateur -user.form.roles.user: Utilisateur -user.form.roles.admin: Administrateur user.form.language.english: English user.form.language.french: Français user.form.language: Langue user.form.leave-blank-to-keep-current-password: Laissez vide pour conserver votre mode de passe actuel user.form.maxagecategory: Catégorie d'âge maximale user.form.plainpassword: Mot de passe +user.form.roles.admin: Administrateur +user.form.roles.user: Utilisateur user.form.roles: Roles user.form.username: Nom d'utilisateur(ice) user.last-login: Dernière connexion From fa565f4867fbbb84ba918eeedf97973f82907bd8 Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Fri, 17 Jan 2025 18:34:33 +0100 Subject: [PATCH 03/11] Update templates, dependencies, and repository logic Refactored templates to adjust Twig parameters and corrected logic in the `BookInteractionRepository`. Updated composer and npm package dependencies, including `bootstrap-icons`. Fixed package lock file details and improved maintainability. --- .gitignore | 1 - composer.lock | 108 ++++++++++--------- package-lock.json | 7 +- package.json | 1 + src/Repository/BookInteractionRepository.php | 2 +- templates/author/detail.html.twig | 5 +- templates/book/_teaser.html.twig | 2 +- 7 files changed, 69 insertions(+), 57 deletions(-) diff --git a/.gitignore b/.gitignore index a15fd278..8f4769f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ - ###> symfony/framework-bundle ### /.env.local /.env.local.php diff --git a/composer.lock b/composer.lock index a4c16db2..dcc29419 100644 --- a/composer.lock +++ b/composer.lock @@ -649,16 +649,16 @@ }, { "name": "doctrine/dbal", - "version": "3.9.3", + "version": "3.9.4", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba" + "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", - "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", + "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", "shasum": "" }, "require": { @@ -674,15 +674,13 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.12.6", - "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.20", - "psalm/plugin-phpunit": "0.18.4", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "9.6.22", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" + "symfony/console": "^4.4|^5.4|^6.0|^7.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -742,7 +740,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.3" + "source": "https://github.com/doctrine/dbal/tree/3.9.4" }, "funding": [ { @@ -758,7 +756,7 @@ "type": "tidelift" } ], - "time": "2024-10-10T17:56:43+00:00" + "time": "2025-01-16T08:28:55+00:00" }, { "name": "doctrine/deprecations", @@ -807,16 +805,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.13.1", + "version": "2.13.2", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "2740ad8b8739b39ab37d409c972b092f632b025a" + "reference": "2363c43d9815a11657e452625cd64172d5587486" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2740ad8b8739b39ab37d409c972b092f632b025a", - "reference": "2740ad8b8739b39ab37d409c972b092f632b025a", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2363c43d9815a11657e452625cd64172d5587486", + "reference": "2363c43d9815a11657e452625cd64172d5587486", "shasum": "" }, "require": { @@ -830,7 +828,7 @@ "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.46 || ^6.4.3 || ^7.0.3", + "symfony/doctrine-bridge": "^5.4.46 || ~6.3.12 || ^6.4.3 || ^7.0.3", "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" @@ -846,13 +844,14 @@ "doctrine/deprecations": "^1.0", "doctrine/orm": "^2.17 || ^3.0", "friendsofphp/proxy-manager-lts": "^1.0", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "^9.5.26", - "psalm/plugin-phpunit": "^0.18.4", - "psalm/plugin-symfony": "^5", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^6.1 || ^7.0", "symfony/property-info": "^5.4 || ^6.0 || ^7.0", - "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0", "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", "symfony/string": "^5.4 || ^6.0 || ^7.0", @@ -861,8 +860,7 @@ "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0", - "vimeo/psalm": "^5.15" + "twig/twig": "^1.34 || ^2.12 || ^3.0" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -907,7 +905,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.1" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.2" }, "funding": [ { @@ -923,7 +921,7 @@ "type": "tidelift" } ], - "time": "2024-11-08T23:27:54+00:00" + "time": "2025-01-15T11:12:38+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -4869,16 +4867,16 @@ }, { "name": "spatie/temporary-directory", - "version": "2.2.1", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", - "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", "shasum": "" }, "require": { @@ -4914,7 +4912,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" + "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" }, "funding": [ { @@ -4926,7 +4924,7 @@ "type": "github" } ], - "time": "2023-12-25T11:46:58+00:00" + "time": "2025-01-13T13:04:43+00:00" }, { "name": "symfony/apache-pack", @@ -11596,16 +11594,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.67.0", + "version": "v3.68.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "0ad34c75d1172f7d30320460e803887981830cbf" + "reference": "73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/0ad34c75d1172f7d30320460e803887981830cbf", - "reference": "0ad34c75d1172f7d30320460e803887981830cbf", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c", + "reference": "73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c", "shasum": "" }, "require": { @@ -11687,7 +11685,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.67.0" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.0" }, "funding": [ { @@ -11695,7 +11693,7 @@ "type": "github" } ], - "time": "2025-01-08T10:17:40+00:00" + "time": "2025-01-13T17:01:01+00:00" }, { "name": "myclabs/deep-copy", @@ -13295,12 +13293,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "a717959d5f0bf7c9a881efdbb7ec0da4454a14ac" + "reference": "e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a717959d5f0bf7c9a881efdbb7ec0da4454a14ac", - "reference": "a717959d5f0bf7c9a881efdbb7ec0da4454a14ac", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec", + "reference": "e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec", "shasum": "" }, "conflict": { @@ -13536,6 +13534,7 @@ "gilacms/gila": "<=1.15.4", "gleez/cms": "<=1.3|==2", "globalpayments/php-sdk": "<2", + "goalgorilla/open_social": "<12.3.8|>=12.4,<12.4.5|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", "gogentooss/samlbase": "<1.2.7", "google/protobuf": "<3.15", "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", @@ -13583,6 +13582,7 @@ "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", "ipl/web": "<0.10.1", + "islandora/crayfish": "<4.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", @@ -13665,6 +13665,7 @@ "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", "mediawiki/cargo": "<3.6.1", "mediawiki/core": "<1.39.5|==1.40", + "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "melisplatform/melis-asset-manager": "<5.0.1", @@ -13860,7 +13861,7 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.2.16", + "silverstripe/framework": "<5.3.8", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", @@ -13992,13 +13993,20 @@ "twig/twig": "<3.11.2|>=3.12,<3.14.1", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1", - "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", + "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-beuser": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.48|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", + "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8", + "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", + "typo3/cms-lowlevel": ">=11,<=11.5.41", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", + "typo3/cms-scheduler": ">=11,<=11.5.41", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", @@ -14146,7 +14154,7 @@ "type": "tidelift" } ], - "time": "2025-01-08T21:04:52+00:00" + "time": "2025-01-15T23:05:13+00:00" }, { "name": "sebastian/cli-parser", @@ -15323,16 +15331,16 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.61.0", + "version": "v1.62.1", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18" + "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/a3b7f14d349f8f44ed752d4dde2263f77510cc18", - "reference": "a3b7f14d349f8f44ed752d4dde2263f77510cc18", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/468ff2708200c95ebc0d85d3174b6c6711b8a590", + "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590", "shasum": "" }, "require": { @@ -15395,7 +15403,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.61.0" + "source": "https://github.com/symfony/maker-bundle/tree/v1.62.1" }, "funding": [ { @@ -15411,7 +15419,7 @@ "type": "tidelift" } ], - "time": "2024-08-29T22:50:23+00:00" + "time": "2025-01-15T00:21:40+00:00" }, { "name": "symfony/phpunit-bridge", diff --git a/package-lock.json b/package-lock.json index afed0c2e..af534762 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "html", + "name": "biblioteca", "lockfileVersion": 2, "requires": true, "packages": { @@ -22,6 +22,7 @@ "@vue/eslint-config-prettier": "^10.1.0", "autoprefixer": "^10.4.20", "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", "core-js": "^3.39.0", "eslint": "^9.17", "eslint-plugin-vue": "^9.32.0", @@ -4292,6 +4293,7 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "dev": true, "funding": [ { "type": "github", @@ -12676,7 +12678,8 @@ "bootstrap-icons": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==" + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", + "dev": true }, "brace-expansion": { "version": "1.1.11", diff --git a/package.json b/package.json index e771ac3d..c8bfd2fb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@vue/eslint-config-prettier": "^10.1.0", "autoprefixer": "^10.4.20", "bootstrap": "^5.3.3", + "bootstrap-icons": "^1.11.3", "core-js": "^3.39.0", "eslint": "^9.17", "eslint-plugin-vue": "^9.32.0", diff --git a/src/Repository/BookInteractionRepository.php b/src/Repository/BookInteractionRepository.php index be912858..baf9fdcd 100644 --- a/src/Repository/BookInteractionRepository.php +++ b/src/Repository/BookInteractionRepository.php @@ -29,7 +29,7 @@ public function getStartedBooks(): array $results = $this->createQueryBuilder('b') ->andWhere('b.readPages >0') ->andWhere('b.readStatus = :read_status') - ->andWhere('b.readingList != :read_status') + ->andWhere('b.readingList != :reading_list') ->andWhere('b.user = :val') ->setParameter('val', $this->security->getUser()) ->setParameter('read_status', ReadStatus::Started) diff --git a/templates/author/detail.html.twig b/templates/author/detail.html.twig index d7776a20..d496ef04 100644 --- a/templates/author/detail.html.twig +++ b/templates/author/detail.html.twig @@ -17,9 +17,10 @@
{% for serie, book in firstBooks %}
-

{{ serie }}

- {% include 'book/_teaser.html.twig' with {book: book} %} + + {% include 'book/_teaser.html.twig' with {book: book, withDetail:false} %} +

{{ serie }}

{% endfor %}
diff --git a/templates/book/_teaser.html.twig b/templates/book/_teaser.html.twig index b968541e..bf39be8d 100644 --- a/templates/book/_teaser.html.twig +++ b/templates/book/_teaser.html.twig @@ -43,7 +43,7 @@ {{ book.title }} {% for author in book.authors %} - +  {{ author }} {% endfor %} From ada54e077abc15815520f54e117625c1b0ccc8e2 Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Mon, 20 Jan 2025 06:17:45 +0100 Subject: [PATCH 04/11] Refactor Reading status and reading list into enums --- composer.lock | 1697 ++++------------- package-lock.json | 134 +- package.json | 7 +- src/Controller/AuthorController.php | 41 - src/Controller/SerieController.php | 48 - src/Twig/Components/AddBookToShelf.php | 80 + src/Twig/FilteredBookUrl.php | 17 - templates/author/detail.html.twig | 45 - templates/book/_teaser.html.twig | 36 +- templates/book/index.html.twig | 33 +- templates/components/AddBookToShelf.html.twig | 37 + .../InlineEditInteraction.html.twig | 159 +- templates/serie/detail.html.twig | 49 - 13 files changed, 687 insertions(+), 1696 deletions(-) delete mode 100644 src/Controller/AuthorController.php delete mode 100644 src/Controller/SerieController.php create mode 100644 src/Twig/Components/AddBookToShelf.php delete mode 100644 templates/author/detail.html.twig create mode 100644 templates/components/AddBookToShelf.html.twig delete mode 100644 templates/serie/detail.html.twig diff --git a/composer.lock b/composer.lock index dcc29419..722141e2 100644 --- a/composer.lock +++ b/composer.lock @@ -649,16 +649,16 @@ }, { "name": "doctrine/dbal", - "version": "3.9.4", + "version": "3.9.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959" + "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/ec16c82f20be1a7224e65ac67144a29199f87959", - "reference": "ec16c82f20be1a7224e65ac67144a29199f87959", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", + "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", "shasum": "" }, "require": { @@ -674,13 +674,15 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "2.1.1", - "phpstan/phpstan-strict-rules": "^2", - "phpunit/phpunit": "9.6.22", + "phpstan/phpstan": "1.12.6", + "phpstan/phpstan-strict-rules": "^1.6", + "phpunit/phpunit": "9.6.20", + "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.10.2", "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0" + "symfony/console": "^4.4|^5.4|^6.0|^7.0", + "vimeo/psalm": "4.30.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." @@ -740,7 +742,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.4" + "source": "https://github.com/doctrine/dbal/tree/3.9.3" }, "funding": [ { @@ -756,7 +758,7 @@ "type": "tidelift" } ], - "time": "2025-01-16T08:28:55+00:00" + "time": "2024-10-10T17:56:43+00:00" }, { "name": "doctrine/deprecations", @@ -805,16 +807,16 @@ }, { "name": "doctrine/doctrine-bundle", - "version": "2.13.2", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/doctrine/DoctrineBundle.git", - "reference": "2363c43d9815a11657e452625cd64172d5587486" + "reference": "2740ad8b8739b39ab37d409c972b092f632b025a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2363c43d9815a11657e452625cd64172d5587486", - "reference": "2363c43d9815a11657e452625cd64172d5587486", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/2740ad8b8739b39ab37d409c972b092f632b025a", + "reference": "2740ad8b8739b39ab37d409c972b092f632b025a", "shasum": "" }, "require": { @@ -828,7 +830,7 @@ "symfony/console": "^5.4 || ^6.0 || ^7.0", "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", "symfony/deprecation-contracts": "^2.1 || ^3", - "symfony/doctrine-bridge": "^5.4.46 || ~6.3.12 || ^6.4.3 || ^7.0.3", + "symfony/doctrine-bridge": "^5.4.46 || ^6.4.3 || ^7.0.3", "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/polyfill-php80": "^1.15", "symfony/service-contracts": "^1.1.1 || ^2.0 || ^3" @@ -844,14 +846,13 @@ "doctrine/deprecations": "^1.0", "doctrine/orm": "^2.17 || ^3.0", "friendsofphp/proxy-manager-lts": "^1.0", - "phpstan/phpstan": "2.1.1", - "phpstan/phpstan-phpunit": "2.0.3", - "phpstan/phpstan-strict-rules": "^2", "phpunit/phpunit": "^9.5.26", + "psalm/plugin-phpunit": "^0.18.4", + "psalm/plugin-symfony": "^5", "psr/log": "^1.1.4 || ^2.0 || ^3.0", "symfony/phpunit-bridge": "^6.1 || ^7.0", "symfony/property-info": "^5.4 || ^6.0 || ^7.0", - "symfony/proxy-manager-bridge": "^5.4 || ^6.0", + "symfony/proxy-manager-bridge": "^5.4 || ^6.0 || ^7.0", "symfony/security-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", "symfony/string": "^5.4 || ^6.0 || ^7.0", @@ -860,7 +861,8 @@ "symfony/var-exporter": "^5.4 || ^6.2 || ^7.0", "symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0", "symfony/yaml": "^5.4 || ^6.0 || ^7.0", - "twig/twig": "^1.34 || ^2.12 || ^3.0" + "twig/twig": "^1.34 || ^2.12 || ^3.0", + "vimeo/psalm": "^5.15" }, "suggest": { "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", @@ -905,7 +907,7 @@ ], "support": { "issues": "https://github.com/doctrine/DoctrineBundle/issues", - "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.2" + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.13.1" }, "funding": [ { @@ -921,7 +923,7 @@ "type": "tidelift" } ], - "time": "2025-01-15T11:12:38+00:00" + "time": "2024-11-08T23:27:54+00:00" }, { "name": "doctrine/doctrine-migrations-bundle", @@ -4867,16 +4869,16 @@ }, { "name": "spatie/temporary-directory", - "version": "2.3.0", + "version": "2.2.1", "source": { "type": "git", "url": "https://github.com/spatie/temporary-directory.git", - "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b" + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/580eddfe9a0a41a902cac6eeb8f066b42e65a32b", - "reference": "580eddfe9a0a41a902cac6eeb8f066b42e65a32b", + "url": "https://api.github.com/repos/spatie/temporary-directory/zipball/76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", + "reference": "76949fa18f8e1a7f663fd2eaa1d00e0bcea0752a", "shasum": "" }, "require": { @@ -4912,7 +4914,7 @@ ], "support": { "issues": "https://github.com/spatie/temporary-directory/issues", - "source": "https://github.com/spatie/temporary-directory/tree/2.3.0" + "source": "https://github.com/spatie/temporary-directory/tree/2.2.1" }, "funding": [ { @@ -4924,7 +4926,7 @@ "type": "github" } ], - "time": "2025-01-13T13:04:43+00:00" + "time": "2023-12-25T11:46:58+00:00" }, { "name": "symfony/apache-pack", @@ -5023,31 +5025,32 @@ }, { "name": "symfony/cache", - "version": "v6.4.16", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "70d60e9a3603108563010f8592dff15a6f15dfae" + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/70d60e9a3603108563010f8592dff15a6f15dfae", - "reference": "70d60e9a3603108563010f8592dff15a6f15dfae", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^2.5|^3", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.3.6|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { - "doctrine/dbal": "<2.13.1", - "symfony/dependency-injection": "<5.4", - "symfony/http-kernel": "<5.4", - "symfony/var-dumper": "<5.4" + "doctrine/dbal": "<3.6", + "symfony/dependency-injection": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/var-dumper": "<6.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -5056,15 +5059,16 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/filesystem": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/clock": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/filesystem": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5099,7 +5103,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v6.4.16" + "source": "https://github.com/symfony/cache/tree/v7.2.1" }, "funding": [ { @@ -5115,7 +5119,7 @@ "type": "tidelift" } ], - "time": "2024-11-20T10:10:54+00:00" + "time": "2024-12-07T08:08:50+00:00" }, { "name": "symfony/cache-contracts", @@ -5195,20 +5199,20 @@ }, { "name": "symfony/clock", - "version": "v6.4.13", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/b2bf55c4dd115003309eafa87ee7df9ed3dde81b", - "reference": "b2bf55c4dd115003309eafa87ee7df9ed3dde81b", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/clock": "^1.0", "symfony/polyfill-php83": "^1.28" }, @@ -5249,7 +5253,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v6.4.13" + "source": "https://github.com/symfony/clock/tree/v7.2.0" }, "funding": [ { @@ -5265,38 +5269,38 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/config", - "version": "v6.4.14", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" + "reference": "bcd3c4adf0144dee5011bb35454728c38adec055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", - "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", + "url": "https://api.github.com/repos/symfony/config/zipball/bcd3c4adf0144dee5011bb35454728c38adec055", + "reference": "bcd3c4adf0144dee5011bb35454728c38adec055", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^7.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<5.4", + "symfony/finder": "<6.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/finder": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/finder": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5324,7 +5328,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.14" + "source": "https://github.com/symfony/config/tree/v7.2.0" }, "funding": [ { @@ -5340,7 +5344,7 @@ "type": "tidelift" } ], - "time": "2024-11-04T11:33:53+00:00" + "time": "2024-11-04T11:36:24+00:00" }, { "name": "symfony/console", @@ -5503,40 +5507,39 @@ }, { "name": "symfony/dependency-injection", - "version": "v6.4.16", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8" + "reference": "a475747af1a1c98272a5471abc35f3da81197c5d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/7a379d8871f6a36f01559c14e11141cc02eb8dc8", - "reference": "7a379d8871f6a36f01559c14e11141cc02eb8dc8", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a475747af1a1c98272a5471abc35f3da81197c5d", + "reference": "a475747af1a1c98272a5471abc35f3da81197c5d", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.2.10|^7.0" + "symfony/service-contracts": "^3.5", + "symfony/var-exporter": "^6.4|^7.0" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<6.1", - "symfony/finder": "<5.4", - "symfony/proxy-manager-bridge": "<6.3", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/finder": "<6.4", + "symfony/yaml": "<6.4" }, "provide": { "psr/container-implementation": "1.1|2.0", "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.1|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5564,7 +5567,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.16" + "source": "https://github.com/symfony/dependency-injection/tree/v7.2.0" }, "funding": [ { @@ -5580,7 +5583,7 @@ "type": "tidelift" } ], - "time": "2024-11-25T14:52:46+00:00" + "time": "2024-11-25T15:45:00+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5973,22 +5976,22 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.17", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/37ad2380e8c1a8cf62a1200a5c10080b679b446c", - "reference": "37ad2380e8c1a8cf62a1200a5c10080b679b446c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", @@ -5997,7 +6000,7 @@ "require-dev": { "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^5.4|^6.0|^7.0" + "symfony/serializer": "^6.4|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -6028,7 +6031,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.17" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -6044,28 +6047,28 @@ "type": "tidelift" } ], - "time": "2024-12-06T13:30:51+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.13", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", - "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -6074,13 +6077,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6108,7 +6111,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" }, "funding": [ { @@ -6124,7 +6127,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -6883,36 +6886,37 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.16", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57" + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/431771b7a6f662f1575b3cfc8fd7617aa9864d57", - "reference": "431771b7a6f662f1575b3cfc8fd7617aa9864d57", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/62d1a43796ca3fea3f83a8470dfe63a4af3bc588", + "reference": "62d1a43796ca3fea3f83a8470dfe63a4af3bc588", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, "conflict": { + "doctrine/dbal": "<3.6", "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { - "doctrine/dbal": "^2.13.1|^3|^4", + "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^6.4.12|^7.1.5", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", - "symfony/mime": "^5.4|^6.0|^7.0", - "symfony/rate-limiter": "^5.4|^6.0|^7.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6940,7 +6944,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.16" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.2" }, "funding": [ { @@ -6956,7 +6960,7 @@ "type": "tidelift" } ], - "time": "2024-11-13T18:58:10+00:00" + "time": "2024-12-30T19:00:17+00:00" }, { "name": "symfony/http-kernel", @@ -7649,20 +7653,20 @@ }, { "name": "symfony/options-resolver", - "version": "v6.4.16", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/368128ad168f20e22c32159b9f761e456cec0c78", - "reference": "368128ad168f20e22c32159b9f761e456cec0c78", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -7696,7 +7700,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v6.4.16" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -7712,31 +7716,31 @@ "type": "tidelift" } ], - "time": "2024-11-20T10:57:02+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/password-hasher", - "version": "v6.4.13", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/password-hasher.git", - "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47" + "reference": "d8bd3d66d074c0acba1214a0d42f5941a8e1e94d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/password-hasher/zipball/e97a1b31f60b8bdfc1fdedab4398538da9441d47", - "reference": "e97a1b31f60b8bdfc1fdedab4398538da9441d47", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/d8bd3d66d074c0acba1214a0d42f5941a8e1e94d", + "reference": "d8bd3d66d074c0acba1214a0d42f5941a8e1e94d", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "conflict": { - "symfony/security-core": "<5.4" + "symfony/security-core": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/security-core": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -7768,7 +7772,7 @@ "password" ], "support": { - "source": "https://github.com/symfony/password-hasher/tree/v6.4.13" + "source": "https://github.com/symfony/password-hasher/tree/v7.2.0" }, "funding": [ { @@ -7784,7 +7788,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/polyfill-intl-grapheme", @@ -8574,36 +8578,34 @@ }, { "name": "symfony/routing", - "version": "v6.4.16", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220" + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/91e02e606b4b705c2f4fb42f7e7708b7923a3220", - "reference": "91e02e606b4b705c2f4fb42f7e7708b7923a3220", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "doctrine/annotations": "<1.12", - "symfony/config": "<6.2", - "symfony/dependency-injection": "<5.4", - "symfony/yaml": "<5.4" + "symfony/config": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/yaml": "<6.4" }, "require-dev": { - "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.2|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/yaml": "^5.4|^6.0|^7.0" + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/yaml": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8637,7 +8639,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.16" + "source": "https://github.com/symfony/routing/tree/v7.2.0" }, "funding": [ { @@ -8653,7 +8655,7 @@ "type": "tidelift" } ], - "time": "2024-11-13T15:31:34+00:00" + "time": "2024-11-25T11:08:51+00:00" }, { "name": "symfony/runtime", @@ -8736,16 +8738,16 @@ }, { "name": "symfony/security-bundle", - "version": "v7.1.6", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/security-bundle.git", - "reference": "7df1d3d431be03fbeb1b162eebca424005b48cdd" + "reference": "e7b04b503a4eb49307b9997ac9370f403c2f5198" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-bundle/zipball/7df1d3d431be03fbeb1b162eebca424005b48cdd", - "reference": "7df1d3d431be03fbeb1b162eebca424005b48cdd", + "url": "https://api.github.com/repos/symfony/security-bundle/zipball/e7b04b503a4eb49307b9997ac9370f403c2f5198", + "reference": "e7b04b503a4eb49307b9997ac9370f403c2f5198", "shasum": "" }, "require": { @@ -8759,9 +8761,9 @@ "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/password-hasher": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", + "symfony/security-core": "^7.2", "symfony/security-csrf": "^6.4|^7.0", - "symfony/security-http": "^7.1", + "symfony/security-http": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -8793,7 +8795,7 @@ "symfony/twig-bundle": "^6.4|^7.0", "symfony/validator": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0", - "twig/twig": "^3.0.4", + "twig/twig": "^3.12", "web-token/jwt-library": "^3.3.2|^4.0" }, "type": "symfony-bundle", @@ -8822,7 +8824,7 @@ "description": "Provides a tight integration of the Security component into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-bundle/tree/v7.1.6" + "source": "https://github.com/symfony/security-bundle/tree/v7.2.2" }, "funding": [ { @@ -8838,48 +8840,49 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-12-30T18:55:54+00:00" }, { "name": "symfony/security-core", - "version": "v6.4.16", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/security-core.git", - "reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0" + "reference": "fdbf318b939a86f89b0c071f60b9d551261d3cc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-core/zipball/19cdb7de86e556202ab16e0cffd1a97348231bc0", - "reference": "19cdb7de86e556202ab16e0cffd1a97348231bc0", + "url": "https://api.github.com/repos/symfony/security-core/zipball/fdbf318b939a86f89b0c071f60b9d551261d3cc1", + "reference": "fdbf318b939a86f89b0c071f60b9d551261d3cc1", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/event-dispatcher-contracts": "^2.5|^3", - "symfony/password-hasher": "^5.4|^6.0|^7.0", + "symfony/password-hasher": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "symfony/event-dispatcher": "<5.4", - "symfony/http-foundation": "<5.4", - "symfony/ldap": "<5.4", - "symfony/security-guard": "<5.4", - "symfony/translation": "<5.4.35|>=6.0,<6.3.12|>=6.4,<6.4.3|>=7.0,<7.0.3", - "symfony/validator": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/translation": "<6.4.3|>=7.0,<7.0.3", + "symfony/validator": "<6.4" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", "psr/container": "^1.1|^2.0", "psr/log": "^1|^2|^3", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", - "symfony/ldap": "^5.4|^6.0|^7.0", - "symfony/string": "^5.4|^6.0|^7.0", - "symfony/translation": "^5.4.35|~6.3.12|^6.4.3|^7.0.3", + "symfony/cache": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4.3|^7.0.3", "symfony/validator": "^6.4|^7.0" }, "type": "library", @@ -8908,7 +8911,7 @@ "description": "Symfony Security Component - Core Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-core/tree/v6.4.16" + "source": "https://github.com/symfony/security-core/tree/v7.2.0" }, "funding": [ { @@ -8924,31 +8927,33 @@ "type": "tidelift" } ], - "time": "2024-11-27T09:48:51+00:00" + "time": "2024-11-27T09:50:52+00:00" }, { "name": "symfony/security-csrf", - "version": "v6.4.13", + "version": "v7.2.2", "source": { "type": "git", "url": "https://github.com/symfony/security-csrf.git", - "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3" + "reference": "a2031e57dc02002163770a5cc02fafdd70decf1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-csrf/zipball/c34421b7d34efbaef5d611ab2e646a0ec464ffe3", - "reference": "c34421b7d34efbaef5d611ab2e646a0ec464ffe3", + "url": "https://api.github.com/repos/symfony/security-csrf/zipball/a2031e57dc02002163770a5cc02fafdd70decf1d", + "reference": "a2031e57dc02002163770a5cc02fafdd70decf1d", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/security-core": "^5.4|^6.0|^7.0" + "php": ">=8.2", + "symfony/security-core": "^6.4|^7.0" }, "conflict": { - "symfony/http-foundation": "<5.4" + "symfony/http-foundation": "<6.4" }, "require-dev": { - "symfony/http-foundation": "^5.4|^6.0|^7.0" + "psr/log": "^1|^2|^3", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -8976,7 +8981,7 @@ "description": "Symfony Security Component - CSRF Library", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-csrf/tree/v6.4.13" + "source": "https://github.com/symfony/security-csrf/tree/v7.2.2" }, "funding": [ { @@ -8992,20 +8997,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-12-20T09:56:48+00:00" }, { "name": "symfony/security-http", - "version": "v7.1.10", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/security-http.git", - "reference": "4e0bc002cb69181d623c7f84beb1b8d885efa43d" + "reference": "125844598d9cef4fe72a9f6c4a78ac7c59c3f532" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/security-http/zipball/4e0bc002cb69181d623c7f84beb1b8d885efa43d", - "reference": "4e0bc002cb69181d623c7f84beb1b8d885efa43d", + "url": "https://api.github.com/repos/symfony/security-http/zipball/125844598d9cef4fe72a9f6c4a78ac7c59c3f532", + "reference": "125844598d9cef4fe72a9f6c4a78ac7c59c3f532", "shasum": "" }, "require": { @@ -9015,7 +9020,7 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/polyfill-mbstring": "~1.0", "symfony/property-access": "^6.4|^7.0", - "symfony/security-core": "^6.4|^7.0", + "symfony/security-core": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -9064,7 +9069,7 @@ "description": "Symfony Security Component - HTTP Integration", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/security-http/tree/v7.1.10" + "source": "https://github.com/symfony/security-http/tree/v7.2.1" }, "funding": [ { @@ -9080,7 +9085,7 @@ "type": "tidelift" } ], - "time": "2024-12-04T10:33:06+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/serializer", @@ -10288,34 +10293,32 @@ }, { "name": "symfony/var-dumper", - "version": "v6.4.15", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", - "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^6.3|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/uid": "^5.4|^6.0|^7.0", - "twig/twig": "^2.13|^3.0.4" + "symfony/console": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/uid": "^6.4|^7.0", + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -10353,7 +10356,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" }, "funding": [ { @@ -10369,30 +10372,29 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:28:48+00:00" + "time": "2024-11-08T15:48:14+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.13", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51" + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", - "reference": "0f605f72a363f8743001038a176eeb2a11223b51", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3" + "php": ">=8.2" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -10430,7 +10432,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" }, "funding": [ { @@ -10446,7 +10448,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:18:03+00:00" + "time": "2024-10-18T07:58:17+00:00" }, { "name": "symfony/web-link", @@ -11026,31 +11028,35 @@ "time": "2024-07-15T13:18:35+00:00" }, { - "name": "clue/ndjson-react", - "version": "v1.3.0", + "name": "composer/semver", + "version": "3.4.3", "source": { "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { - "php": ">=5.3", - "react/stream": "^1.2" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { - "Clue\\React\\NDJson\\": "src/" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -11059,285 +11065,72 @@ ], "authors": [ { - "name": "Christian Lück", - "email": "christian@clue.engineering" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", + "description": "Semver library that offers utilities, version constraint parsing and validation.", "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" + "semantic", + "semver", + "validation", + "versioning" ], "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" }, "funding": [ { - "url": "https://clue.engineering/support", + "url": "https://packagist.com", "type": "custom" }, { - "url": "https://github.com/clue", + "url": "https://github.com/composer", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" } ], - "time": "2022-12-23T10:58:28+00:00" + "time": "2024-09-19T14:15:21+00:00" }, { - "name": "composer/pcre", - "version": "3.3.2", + "name": "doctrine/data-fixtures", + "version": "2.0.1", "source": { "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e" + "url": "https://github.com/doctrine/data-fixtures.git", + "reference": "2ae45139f148c9272c49a521d82cc50491355a99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e", - "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e", + "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/2ae45139f148c9272c49a521d82cc50491355a99", + "reference": "2ae45139f148c9272c49a521d82cc50491355a99", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "doctrine/persistence": "^3.1", + "php": "^8.1", + "psr/log": "^1.1 || ^2 || ^3" }, "conflict": { - "phpstan/phpstan": "<1.11.10" - }, - "require-dev": { - "phpstan/phpstan": "^1.12 || ^2", - "phpstan/phpstan-strict-rules": "^1 || ^2", - "phpunit/phpunit": "^8 || ^9" - }, - "type": "library", - "extra": { - "phpstan": { - "includes": [ - "extension.neon" - ] - }, - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.3.2" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-11-12T16:29:46+00:00" - }, - { - "name": "composer/semver", - "version": "3.4.3", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.3" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-09-19T14:15:21+00:00" - }, - { - "name": "composer/xdebug-handler", - "version": "3.0.5", - "source": { - "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", - "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", - "shasum": "" - }, - "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" - }, - "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" - } - ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2024-05-06T16:37:16+00:00" - }, - { - "name": "doctrine/data-fixtures", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/data-fixtures.git", - "reference": "2ae45139f148c9272c49a521d82cc50491355a99" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/data-fixtures/zipball/2ae45139f148c9272c49a521d82cc50491355a99", - "reference": "2ae45139f148c9272c49a521d82cc50491355a99", - "shasum": "" - }, - "require": { - "doctrine/persistence": "^3.1", - "php": "^8.1", - "psr/log": "^1.1 || ^2 || ^3" - }, - "conflict": { - "doctrine/dbal": "<3.5 || >=5", - "doctrine/orm": "<2.14 || >=4", - "doctrine/phpcr-odm": "<1.3.0" + "doctrine/dbal": "<3.5 || >=5", + "doctrine/orm": "<2.14 || >=4", + "doctrine/phpcr-odm": "<1.3.0" }, "require-dev": { "doctrine/coding-standard": "^12", @@ -11485,300 +11278,89 @@ "time": "2024-12-05T18:35:55+00:00" }, { - "name": "evenement/evenement", - "version": "v3.0.2", + "name": "myclabs/deep-copy", + "version": "1.12.1", "source": { "type": "git", - "url": "https://github.com/igorw/evenement.git", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", - "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { - "php": ">=7.0" + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { - "phpunit/phpunit": "^9 || ^6" + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], "psr-4": { - "Evenement\\": "src/" + "DeepCopy\\": "src/DeepCopy/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "authors": [ - { - "name": "Igor Wiedler", - "email": "igor@wiedler.ch" - } - ], - "description": "Événement is a very simple event dispatching library for PHP", + "description": "Create deep copies (clones) of your objects", "keywords": [ - "event-dispatcher", - "event-emitter" + "clone", + "copy", + "duplicate", + "object", + "object graph" ], "support": { - "issues": "https://github.com/igorw/evenement/issues", - "source": "https://github.com/igorw/evenement/tree/v3.0.2" + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, - "time": "2023-08-08T05:53:35+00:00" + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2024-11-08T17:47:46+00:00" }, { - "name": "fidry/cpu-core-counter", - "version": "1.2.0", + "name": "nikolaposa/version", + "version": "4.2.0", "source": { "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "8520451a140d3f46ac33042715115e290cf5785f" + "url": "https://github.com/nikolaposa/version.git", + "reference": "003fefa14f47cd44917546285e39d196af062a95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/8520451a140d3f46ac33042715115e290cf5785f", - "reference": "8520451a140d3f46ac33042715115e290cf5785f", + "url": "https://api.github.com/repos/nikolaposa/version/zipball/003fefa14f47cd44917546285e39d196af062a95", + "reference": "003fefa14f47cd44917546285e39d196af062a95", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "beberlei/assert": "^3.2", + "php": "^8.1" }, "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^1.9.2", - "phpstan/phpstan-deprecation-rules": "^1.0.0", - "phpstan/phpstan-phpunit": "^1.2.2", - "phpstan/phpstan-strict-rules": "^1.4.4", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.2.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2024-08-06T10:04:20+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.68.0", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c", - "reference": "73f78d8b2b34a0dd65fedb434a602ee4c2c8ad4c", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.0", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.3", - "ext-filter": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.2", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.5", - "react/event-loop": "^1.0", - "react/promise": "^2.0 || ^3.0", - "react/socket": "^1.0", - "react/stream": "^1.0", - "sebastian/diff": "^4.0 || ^5.1 || ^6.0", - "symfony/console": "^5.4 || ^6.4 || ^7.0", - "symfony/event-dispatcher": "^5.4 || ^6.4 || ^7.0", - "symfony/filesystem": "^5.4 || ^6.4 || ^7.0", - "symfony/finder": "^5.4 || ^6.4 || ^7.0", - "symfony/options-resolver": "^5.4 || ^6.4 || ^7.0", - "symfony/polyfill-mbstring": "^1.31", - "symfony/polyfill-php80": "^1.31", - "symfony/polyfill-php81": "^1.31", - "symfony/process": "^5.4 || ^6.4 || ^7.2", - "symfony/stopwatch": "^5.4 || ^6.4 || ^7.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.4", - "infection/infection": "^0.29.8", - "justinrainbow/json-schema": "^5.3 || ^6.0", - "keradus/cli-executor": "^2.1", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.7", - "php-cs-fixer/accessible-object": "^1.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", - "phpunit/phpunit": "^9.6.22 || ^10.5.40 || ^11.5.2", - "symfony/var-dumper": "^5.4.48 || ^6.4.15 || ^7.2.0", - "symfony/yaml": "^5.4.45 || ^6.4.13 || ^7.2.0" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/Fixer/Internal/*" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.68.0" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2025-01-13T17:01:01+00:00" - }, - { - "name": "myclabs/deep-copy", - "version": "1.12.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "conflict": { - "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2024-11-08T17:47:46+00:00" - }, - { - "name": "nikolaposa/version", - "version": "4.2.0", - "source": { - "type": "git", - "url": "https://github.com/nikolaposa/version.git", - "reference": "003fefa14f47cd44917546285e39d196af062a95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikolaposa/version/zipball/003fefa14f47cd44917546285e39d196af062a95", - "reference": "003fefa14f47cd44917546285e39d196af062a95", - "shasum": "" - }, - "require": { - "beberlei/assert": "^3.2", - "php": "^8.1" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.44", - "phpstan/phpstan": "^1.10", - "phpstan/phpstan-beberlei-assert": "^1.1", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^10.5" + "friendsofphp/php-cs-fixer": "^3.44", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-beberlei-assert": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpunit/phpunit": "^10.5" }, "type": "library", "extra": { @@ -11934,6 +11516,58 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-cs-fixer/shim", + "version": "v3.68.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/shim.git", + "reference": "23acc692a99304559d4c94e9f299158ecd0ed7d1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/shim/zipball/23acc692a99304559d4c94e9f299158ecd0ed7d1", + "reference": "23acc692a99304559d4c94e9f299158ecd0ed7d1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "replace": { + "friendsofphp/php-cs-fixer": "self.version" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer", + "php-cs-fixer.phar" + ], + "type": "application", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/PHP-CS-Fixer/shim/issues", + "source": "https://github.com/PHP-CS-Fixer/shim/tree/v3.68.0" + }, + "time": "2025-01-13T17:01:38+00:00" + }, { "name": "phpstan/extension-installer", "version": "1.4.3", @@ -12702,532 +12336,6 @@ ], "time": "2024-12-05T13:48:26+00:00" }, - { - "name": "react/cache", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" - }, - { - "name": "react/child-process", - "version": "v0.6.6", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/1721e2b93d89b745664353b9cfc8f155ba8a6159", - "reference": "1721e2b93d89b745664353b9cfc8f155ba8a6159", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.6" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-01-01T16:37:48+00:00" - }, - { - "name": "react/dns", - "version": "v1.13.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.13.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-13T14:18:03+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.5.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2023-11-13T13:48:05+00:00" - }, - { - "name": "react/promise", - "version": "v3.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", - "reference": "8a164643313c71354582dc850b42b33fa12a4b63", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.10.39 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-05-24T10:39:05+00:00" - }, - { - "name": "react/socket", - "version": "v1.16.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.16.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-07-26T10:38:09+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, { "name": "rector/rector", "version": "1.2.10", @@ -13293,12 +12401,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec" + "reference": "a717959d5f0bf7c9a881efdbb7ec0da4454a14ac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec", - "reference": "e7a38fcc13e4ddfe9a28d5c7bf50aa9a9da758ec", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/a717959d5f0bf7c9a881efdbb7ec0da4454a14ac", + "reference": "a717959d5f0bf7c9a881efdbb7ec0da4454a14ac", "shasum": "" }, "conflict": { @@ -13534,7 +12642,6 @@ "gilacms/gila": "<=1.15.4", "gleez/cms": "<=1.3|==2", "globalpayments/php-sdk": "<2", - "goalgorilla/open_social": "<12.3.8|>=12.4,<12.4.5|>=13.0.0.0-alpha1,<13.0.0.0-alpha11", "gogentooss/samlbase": "<1.2.7", "google/protobuf": "<3.15", "gos/web-socket-bundle": "<1.10.4|>=2,<2.6.1|>=3,<3.3", @@ -13582,7 +12689,6 @@ "intelliants/subrion": "<4.2.2", "inter-mediator/inter-mediator": "==5.5", "ipl/web": "<0.10.1", - "islandora/crayfish": "<4.1", "islandora/islandora": ">=2,<2.4.1", "ivankristianto/phpwhois": "<=4.3", "jackalope/jackalope-doctrine-dbal": "<1.7.4", @@ -13665,7 +12771,6 @@ "mediawiki/abuse-filter": "<1.39.9|>=1.40,<1.41.3|>=1.42,<1.42.2", "mediawiki/cargo": "<3.6.1", "mediawiki/core": "<1.39.5|==1.40", - "mediawiki/data-transfer": ">=1.39,<1.39.11|>=1.41,<1.41.3|>=1.42,<1.42.2", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "melisplatform/melis-asset-manager": "<5.0.1", @@ -13861,7 +12966,7 @@ "silverstripe/cms": "<4.11.3", "silverstripe/comments": ">=1.3,<3.1.1", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": "<5.3.8", + "silverstripe/framework": "<5.2.16", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", "silverstripe/recipe-cms": ">=4.5,<4.5.3", @@ -13993,20 +13098,13 @@ "twig/twig": "<3.11.2|>=3.12,<3.14.1", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1", - "typo3/cms-belog": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-beuser": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-core": "<=8.7.56|>=9,<=9.5.48|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-dashboard": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", "typo3/cms-extbase": "<6.2.24|>=7,<7.6.8|==8.1.1", - "typo3/cms-extensionmanager": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", "typo3/cms-fluid": "<4.3.4|>=4.4,<4.4.1", - "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", + "typo3/cms-form": ">=8,<=8.7.39|>=9,<=9.5.24|>=10,<=10.4.13|>=11,<=11.1", "typo3/cms-frontend": "<4.3.9|>=4.4,<4.4.5", - "typo3/cms-indexed-search": ">=10,<=10.4.47|>=11,<=11.5.41|>=12,<=12.4.24|>=13,<=13.4.2", - "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8|==13.4.2", - "typo3/cms-lowlevel": ">=11,<=11.5.41", + "typo3/cms-install": "<4.1.14|>=4.2,<4.2.16|>=4.3,<4.3.9|>=4.4,<4.4.5|>=12.2,<12.4.8", "typo3/cms-rte-ckeditor": ">=9.5,<9.5.42|>=10,<10.4.39|>=11,<11.5.30", - "typo3/cms-scheduler": ">=11,<=11.5.41", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.12|>=3.1,<3.1.10|>=3.2,<3.2.13|>=3.3,<3.3.13|>=4,<4.0.6", "typo3/html-sanitizer": ">=1,<=1.5.2|>=2,<=2.1.3", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4|>=2.3,<2.3.99|>=3,<3.0.20|>=3.1,<3.1.18|>=3.2,<3.2.14|>=3.3,<3.3.23|>=4,<4.0.17|>=4.1,<4.1.16|>=4.2,<4.2.12|>=4.3,<4.3.3", @@ -14154,7 +13252,7 @@ "type": "tidelift" } ], - "time": "2025-01-15T23:05:13+00:00" + "time": "2025-01-08T21:04:52+00:00" }, { "name": "sebastian/cli-parser", @@ -15331,22 +14429,23 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.62.1", + "version": "v1.62.0", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590" + "reference": "0624f13b1e0ff86df6f6646c711d806d9af12629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/468ff2708200c95ebc0d85d3174b6c6711b8a590", - "reference": "468ff2708200c95ebc0d85d3174b6c6711b8a590", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/0624f13b1e0ff86df6f6646c711d806d9af12629", + "reference": "0624f13b1e0ff86df6f6646c711d806d9af12629", "shasum": "" }, "require": { "doctrine/inflector": "^2.0", "nikic/php-parser": "^4.18|^5.0", "php": ">=8.1", + "php-cs-fixer/shim": "^v3.64", "symfony/config": "^6.4|^7.0", "symfony/console": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", @@ -15403,7 +14502,7 @@ ], "support": { "issues": "https://github.com/symfony/maker-bundle/issues", - "source": "https://github.com/symfony/maker-bundle/tree/v1.62.1" + "source": "https://github.com/symfony/maker-bundle/tree/v1.62.0" }, "funding": [ { @@ -15419,7 +14518,7 @@ "type": "tidelift" } ], - "time": "2025-01-15T00:21:40+00:00" + "time": "2024-12-10T23:51:12+00:00" }, { "name": "symfony/phpunit-bridge", @@ -15709,6 +14808,6 @@ "ext-imagick": "*", "ext-zip": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-dev": [], + "plugin-api-version": "2.3.0" } diff --git a/package-lock.json b/package-lock.json index af534762..3dbceeda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "biblioteca", + "name": "html", "lockfileVersion": 2, "requires": true, "packages": { @@ -13,7 +13,7 @@ "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@hotwired/stimulus": "^3.0", - "@rushstack/eslint-patch": "^1.8.0", + "@rushstack/eslint-patch": "^1.10.5", "@symfony/stimulus-bridge": "^3.2", "@symfony/ux-autocomplete": "file:vendor/symfony/ux-autocomplete/assets", "@symfony/ux-live-component": "file:vendor/symfony/ux-live-component/assets", @@ -22,9 +22,8 @@ "@vue/eslint-config-prettier": "^10.1.0", "autoprefixer": "^10.4.20", "bootstrap": "^5.3.3", - "bootstrap-icons": "^1.11.3", - "core-js": "^3.39.0", - "eslint": "^9.17", + "core-js": "^3.40.0", + "eslint": "^9.18", "eslint-plugin-vue": "^9.32.0", "prettier": "^3.4.2", "regenerator-runtime": "^0.14.1", @@ -1953,11 +1952,10 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, - "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -2034,11 +2032,10 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -2054,12 +2051,12 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, - "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -2951,9 +2948,9 @@ "peer": true }, "node_modules/@rushstack/eslint-patch": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", - "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", + "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", "dev": true }, "node_modules/@sinclair/typebox": { @@ -2964,9 +2961,9 @@ "license": "MIT" }, "node_modules/@symfony/stimulus-bridge": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.2.tgz", - "integrity": "sha512-kIaUEGPXW7g14zsHkIvQWw8cmfCdXSqsEQx18fuHPBb+R0h8nYPyY+e9uVtTuHlE2wHwAjrJoc6YBBK4a7CpKA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.3.tgz", + "integrity": "sha512-36rQTihQ2MGOn8EmdOYCr3DQfP3WS1CNcUUXKTPY5ghtFOeb7OVuhbc32AjRowE2/vaVDOUCPOTv3VLf5VtXBA==", "dev": true, "dependencies": { "@hotwired/stimulus-webpack-helpers": "^1.0.1", @@ -4293,7 +4290,6 @@ "version": "1.11.3", "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", - "dev": true, "funding": [ { "type": "github", @@ -4590,9 +4586,9 @@ "dev": true }, "node_modules/core-js": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", - "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", "dev": true, "hasInstallScript": true, "funding": { @@ -5323,19 +5319,18 @@ } }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -9915,36 +9910,25 @@ }, "vendor/symfony/ux-autocomplete/assets": { "name": "@symfony/ux-autocomplete", - "version": "1.0.0", + "version": "0.0.1", "dev": true, - "license": "MIT", - "devDependencies": { + "dependencies": { "@hotwired/stimulus": "^3.0.0", "tom-select": "^2.2.2", "vitest-fetch-mock": "^0.2.2" - }, - "peerDependencies": { - "@hotwired/stimulus": "^3.0.0", - "tom-select": "^2.2.2" } }, "vendor/symfony/ux-live-component/assets": { "name": "@symfony/ux-live-component", - "version": "1.0.0", + "version": "0.0.1", "dev": true, - "license": "MIT", "dependencies": { - "idiomorph": "^0.3.0" - }, - "devDependencies": { "@hotwired/stimulus": "^3.0.0", "@testing-library/dom": "^7.31.0", "@testing-library/user-event": "^13.1.9", "@types/node-fetch": "^2.6.2", + "idiomorph": "^0.3.0", "node-fetch": "^2.6.1" - }, - "peerDependencies": { - "@hotwired/stimulus": "^3.0.0" } } }, @@ -11171,9 +11155,9 @@ } }, "@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" @@ -11222,9 +11206,9 @@ } }, "@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", + "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", "dev": true }, "@eslint/object-schema": { @@ -11234,11 +11218,12 @@ "dev": true }, "@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "requires": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, @@ -11724,9 +11709,9 @@ "peer": true }, "@rushstack/eslint-patch": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", - "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", + "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", "dev": true }, "@sinclair/typebox": { @@ -11736,9 +11721,9 @@ "dev": true }, "@symfony/stimulus-bridge": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.2.tgz", - "integrity": "sha512-kIaUEGPXW7g14zsHkIvQWw8cmfCdXSqsEQx18fuHPBb+R0h8nYPyY+e9uVtTuHlE2wHwAjrJoc6YBBK4a7CpKA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.3.tgz", + "integrity": "sha512-36rQTihQ2MGOn8EmdOYCr3DQfP3WS1CNcUUXKTPY5ghtFOeb7OVuhbc32AjRowE2/vaVDOUCPOTv3VLf5VtXBA==", "dev": true, "requires": { "@hotwired/stimulus-webpack-helpers": "^1.0.1", @@ -12678,8 +12663,7 @@ "bootstrap-icons": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz", - "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==", - "dev": true + "integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==" }, "brace-expansion": { "version": "1.1.11", @@ -12880,9 +12864,9 @@ "dev": true }, "core-js": { - "version": "3.39.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz", - "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==", + "version": "3.40.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz", + "integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==", "dev": true }, "core-js-compat": { @@ -13367,18 +13351,18 @@ "dev": true }, "eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", + "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.18.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", diff --git a/package.json b/package.json index c8bfd2fb..27b44745 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@babel/core": "^7.26.0", "@babel/preset-env": "^7.26.0", "@hotwired/stimulus": "^3.0", - "@rushstack/eslint-patch": "^1.8.0", + "@rushstack/eslint-patch": "^1.10.5", "@symfony/stimulus-bridge": "^3.2", "@symfony/ux-autocomplete": "file:vendor/symfony/ux-autocomplete/assets", "@symfony/ux-live-component": "file:vendor/symfony/ux-live-component/assets", @@ -16,9 +16,8 @@ "@vue/eslint-config-prettier": "^10.1.0", "autoprefixer": "^10.4.20", "bootstrap": "^5.3.3", - "bootstrap-icons": "^1.11.3", - "core-js": "^3.39.0", - "eslint": "^9.17", + "core-js": "^3.40.0", + "eslint": "^9.18", "eslint-plugin-vue": "^9.32.0", "prettier": "^3.4.2", "regenerator-runtime": "^0.14.1", diff --git a/src/Controller/AuthorController.php b/src/Controller/AuthorController.php deleted file mode 100644 index 6e1ea935..00000000 --- a/src/Controller/AuthorController.php +++ /dev/null @@ -1,41 +0,0 @@ -findByAuthor($name); - - $series = []; - $otherBooks = []; - foreach ($books as $book) { - if ($book->getSerie() !== null) { - $series[$book->getSerie()] = $book->getSerie(); - } else { - $otherBooks[] = $book; - } - } - - $firstBooks = []; - foreach ($series as $serie) { - $firstUnreadBook = $bookRepository->getFirstUnreadBook($serie); - $firstBooks[$serie] = $firstUnreadBook; - } - - return $this->render('author/detail.html.twig', [ - 'author' => $name, - 'firstBooks' => $firstBooks, - 'otherBooks' => $otherBooks, - ]); - } -} diff --git a/src/Controller/SerieController.php b/src/Controller/SerieController.php deleted file mode 100644 index 36e1f256..00000000 --- a/src/Controller/SerieController.php +++ /dev/null @@ -1,48 +0,0 @@ -findBySerie($name); - - if ($books === []) { - throw $this->createNotFoundException('No books found for this serie'); - } - - $user = $this->getUser(); - - if (!$user instanceof User) { - throw $this->createAccessDeniedException('Invalid user'); - } - - $authors = []; - - $firstUnreadBook = $bookRepository->getFirstUnreadBook($name); - - foreach ($books as $book) { - foreach ($book->getAuthors() as $author) { - $authors[$author] = $author; - } - } - - - return $this->render('serie/detail.html.twig', [ - 'serie' => $name, - 'books' => $books, - 'authors' => $authors, - 'firstUnreadBook' => $firstUnreadBook - ]); - } -} diff --git a/src/Twig/Components/AddBookToShelf.php b/src/Twig/Components/AddBookToShelf.php new file mode 100644 index 00000000..987aae6a --- /dev/null +++ b/src/Twig/Components/AddBookToShelf.php @@ -0,0 +1,80 @@ +getRepository(Shelf::class); + + $this->shelves = $shelfRepository->findBy(['user' => $security->getUser()]); + $this->shelves = array_filter($this->shelves, static fn ($item) => $item->getQueryString() === null); + } + + #[LiveAction] + public function add(EntityManagerInterface $entityManager, #[LiveArg] int $shelf): void + { + $shelfRepository = $entityManager->getRepository(Shelf::class); + + $shelf = $shelfRepository->find($shelf); + + if (null === $shelf) { + throw new \RuntimeException('Shelf not found'); + } + + $this->book->addShelf($shelf); + + $entityManager->flush(); + + $this->flashMessage = 'Added to shelf'; + } + + #[LiveAction] + public function remove(EntityManagerInterface $entityManager, #[LiveArg] int $shelf): void + { + $shelfRepository = $entityManager->getRepository(Shelf::class); + + $shelf = $shelfRepository->find($shelf); + + if (null === $shelf) { + throw new \RuntimeException('Shelf not found'); + } + + $this->book->removeShelf($shelf); + + $entityManager->flush(); + + $this->flashMessage = 'Removed from shelf'; + } +} diff --git a/src/Twig/FilteredBookUrl.php b/src/Twig/FilteredBookUrl.php index 0d3389bc..e94166a8 100644 --- a/src/Twig/FilteredBookUrl.php +++ b/src/Twig/FilteredBookUrl.php @@ -29,23 +29,6 @@ public function getFunctions(): array public function filteredBookUrl(array $params): string { $params = $this->filteredBookUrlGenerator->getParametersArray($params); - - $key = ''; - $value=''; - if (array_key_exists('filterQuery', $params)) { - if (preg_match('/^(serie|authors):=`(.+)`[ ]+$/', $params['filterQuery'], $matches)) { - $key = $matches[1]; - $value = $matches[2]; - } - } - $route = match ($key) { - 'authors' => 'app_author_detail', - 'serie' => 'app_serie_detail', - default => 'app_allbooks', - }; - if ($route !== 'app_allbooks') { - return $this->router->generate($route, ['name' => $value]); - } return $this->router->generate('app_allbooks', $params); } diff --git a/templates/author/detail.html.twig b/templates/author/detail.html.twig deleted file mode 100644 index d496ef04..00000000 --- a/templates/author/detail.html.twig +++ /dev/null @@ -1,45 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}{{ author }}{% endblock %} - -{% block body %} - -
- -
-

{{ author }}

-
-
- -
-
-

{{ 'Series'|trans }}

-
- {% for serie, book in firstBooks %} -
- - - {% include 'book/_teaser.html.twig' with {book: book, withDetail:false} %} -

{{ serie }}

-
- {% endfor %} -
-
-
- -
-
-

{{ 'Other books'|trans }}

-
- {% for book in otherBooks %} -
- {% include 'book/_teaser.html.twig' with {book: book} %} -
- {% endfor %} -
-
-
- - - -{% endblock %} diff --git a/templates/book/_teaser.html.twig b/templates/book/_teaser.html.twig index bf39be8d..92ffcd50 100644 --- a/templates/book/_teaser.html.twig +++ b/templates/book/_teaser.html.twig @@ -1,7 +1,4 @@ {# @var book App\Entity\Book #} -{% if withDetail is not defined %} -{% set withDetail = true %} -{% endif %} {% set read = false %} {% for interaction in book.bookInteractions|default([]) %} {% if interaction.user.id == app.user.id and interaction.finished %} @@ -25,7 +22,7 @@ {% else %} {% set icon='bi-file-binary-fill' %} {% endif %} -
+ \ No newline at end of file diff --git a/templates/book/index.html.twig b/templates/book/index.html.twig index 471ac352..0d4a271e 100644 --- a/templates/book/index.html.twig +++ b/templates/book/index.html.twig @@ -4,21 +4,6 @@ {% block body %}
-
{% if book.imageFilename is not null %} {{ 'book.picture-for-book'|trans }} {{ book.title }} @@ -27,9 +12,18 @@ {% endif %} {{ include('book/_interaction.html.twig') }} - {% if not book.verified %} - {{ component('UploadBookPicture',{'book':book}) }} - {% endif %} + + + {{ component('AddBookToShelf',{'book':book, 'user':app.user, 'shelves': shelves}) }}
@@ -110,6 +104,9 @@
  • {{ 'book.extract-cover-from-file'|trans }}
  • +
  • + {{ component('UploadBookPicture',{'book':book}) }} +
  • diff --git a/templates/components/AddBookToShelf.html.twig b/templates/components/AddBookToShelf.html.twig new file mode 100644 index 00000000..21e3f9a5 --- /dev/null +++ b/templates/components/AddBookToShelf.html.twig @@ -0,0 +1,37 @@ +
    + {% if shelves is not empty %} +
    {{ "shelf.shelves"|trans }}
    +
    + {% for shelf in shelves %} + + {% if shelf in book.shelves %} + + {% else %} + + {% endif %} + {% endfor %} +
    + {% endif %} + {% if flashMessage %} +
    + {{ flashMessage }} +
    + {% endif %} + +
    diff --git a/templates/components/InlineEditInteraction.html.twig b/templates/components/InlineEditInteraction.html.twig index 4aee2682..98481284 100644 --- a/templates/components/InlineEditInteraction.html.twig +++ b/templates/components/InlineEditInteraction.html.twig @@ -1,86 +1,87 @@
    - -
    - - + {% if interaction is not null %} + {% if interaction.finished %} + {% set finished=true %} + {% endif %} + {% endif %} -
    + {% set hidden=false %} + {% if interaction is not null %} + {% if interaction.hidden %} + {% set hidden=true %} + {% endif %} + {% endif %} + + + {% set favorite = false %} + {% if interaction is not null %} + {% if interaction.favorite %} + {% set favorite = true %} + {% endif %} + {% endif %} + + + + + + + {% component BootstrapModal with {id: modalId} %} + {% block modal_header %} +
    {{ book.title }}
    + + {% endblock %} + {% block modal_body %} + + {{ form(form,{ + attr: { + 'novalidate': true, + 'class': '', + 'data-action': 'live#action', + 'data-live-action-param': 'prevent|saveInteraction', + } + }) }} + {% endblock %} + + {% endcomponent %}
    \ No newline at end of file diff --git a/templates/serie/detail.html.twig b/templates/serie/detail.html.twig deleted file mode 100644 index dd7050f8..00000000 --- a/templates/serie/detail.html.twig +++ /dev/null @@ -1,49 +0,0 @@ -{% extends 'base.html.twig' %} - -{% block title %}{{ serie }}{% endblock %} - -{% block body %} - -
    -
    - - {{ firstUnreadBook.title }} - -
    -
    -

    {{ serie }}

    -

    {% for author in authors %} - {{ author }}{% endfor %}

    -
    -
    -
    -
    -

    {{ 'books.in-serie'|trans }}

    -
    -
    -
    - {% for book in books %} - - - - - - {% endfor %} -
    - {% include 'book/_teaser.html.twig' with {book: book, withDetail: false} %} - -

    {{ book.title }}

    -
    {{ book.serie }} #{{ book.serieIndex }}
    -
    - {{ component('InlineEditInteraction', {book:book, user: app.user}) }} -
    -
    -
    -
    - - - - -{% endblock %} From 0623cdf58d25a9d01d1f2e5cb5812cf63443c5cd Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Mon, 20 Jan 2025 06:23:04 +0100 Subject: [PATCH 05/11] Refactor Reading status and reading list into enums --- .gitignore | 1 + config/packages/framework.yaml | 1 - src/Form/InlineInteractionType.php | 42 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 src/Form/InlineInteractionType.php diff --git a/.gitignore b/.gitignore index 8f4769f5..a15fd278 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ + ###> symfony/framework-bundle ### /.env.local /.env.local.php diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 95d3e294..32fb174e 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -5,7 +5,6 @@ framework: secret: '%env(APP_SECRET)%' trusted_proxies: '%env(TRUSTED_PROXIES)%' #csrf_protection: true - ide: 'phpstorm://open?file=%%f&line=%%l&/var/www/html/>/Users/sergiomendolia/IdeaProjects/biblioteca/' http_method_override: false handle_all_throwables: true http_cache: true diff --git a/src/Form/InlineInteractionType.php b/src/Form/InlineInteractionType.php new file mode 100644 index 00000000..53cd5d6d --- /dev/null +++ b/src/Form/InlineInteractionType.php @@ -0,0 +1,42 @@ +add('finished', CheckboxType::class, [ + 'required' => false, + ]) + ->add('favorite', CheckboxType::class, [ + 'required' => false, + ]) + ->add('finishedDate', null, [ + 'widget' => 'single_text', + 'html5' => true, + 'required' => false, + ]) + ->add('submit', SubmitType::class) + ; + } + + #[\Override] + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => BookInteraction::class, + 'csrf_protection' => false, + 'label_translation_prefix' => 'interaction.form.', + ]); + } +} From dcf95d4987973816d1367a3649ab93a0f53d3be8 Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Mon, 27 Jan 2025 06:46:39 +0100 Subject: [PATCH 06/11] Refactor into enums --- config/packages/biblioverse_typesense.yaml | 10 ++-------- src/Controller/BookController.php | 4 ++-- src/Entity/Book.php | 8 ++++---- src/Entity/BookInteraction.php | 3 +-- src/Enum/AgeCategory.php | 13 +++++++------ src/Enum/ReadStatus.php | 7 +++---- src/Enum/ReadingList.php | 7 +++---- src/Repository/BookInteractionRepository.php | 2 +- src/Repository/BookRepository.php | 10 +++++----- src/Service/BookProgressionService.php | 2 +- src/Service/Search/SearchHelper.php | 2 +- src/Twig/Components/InlineEditBook.php | 7 +++---- src/Twig/Components/InlineEditInteraction.php | 9 +++++---- 13 files changed, 38 insertions(+), 46 deletions(-) diff --git a/config/packages/biblioverse_typesense.yaml b/config/packages/biblioverse_typesense.yaml index b9e7e482..499f159f 100644 --- a/config/packages/biblioverse_typesense.yaml +++ b/config/packages/biblioverse_typesense.yaml @@ -56,7 +56,7 @@ biblioverse_typesense: type: string facet: true optional: true - entity_attribute: ageCategory + entity_attribute: ageCategoryLabel user.read: name: read optional: true @@ -76,13 +76,7 @@ biblioverse_typesense: default_sorting_field: sortable_id symbols_to_index: ['+', '#', '@', '_'] token_separators: [' ', '-', "'"] -# finders: -# books_autocomplete: -# finder_parameters: -# query_by: title,serie,extension,authors,tags,summary -# limit: 100 -# num_typos: 2 when@test: biblioverse_typesense: - auto_update: false \ No newline at end of file + auto_update: false diff --git a/src/Controller/BookController.php b/src/Controller/BookController.php index 945aa9d2..8202eff2 100644 --- a/src/Controller/BookController.php +++ b/src/Controller/BookController.php @@ -212,7 +212,7 @@ public function read( if ($interaction->getReadStatus() !== ReadStatus::Finished && $page === $book->getPageNumber()) { $interaction->setReadStatus(ReadStatus::Finished); - //TODO Add next unread in serie to reading list? + // TODO Add next unread in serie to reading list? $interaction->setFinishedDate(new \DateTime()); $this->addFlash('success', 'Book finished! Congratulations!'); @@ -399,7 +399,7 @@ public function relocate(Request $request, Book $book, BookFileSystemManagerInte $entityManager->flush(); $this->addFlash('success', 'Book relocated.'); } catch (\Exception $e) { - $this->addFlash('danger', 'Error during relocation: ' . $e->getMessage()); + $this->addFlash('danger', 'Error during relocation: '.$e->getMessage()); } return $this->redirect($request->headers->get('referer') ?? '/'); diff --git a/src/Entity/Book.php b/src/Entity/Book.php index 5dc61495..819619ad 100644 --- a/src/Entity/Book.php +++ b/src/Entity/Book.php @@ -521,7 +521,7 @@ public function setAgeCategory(?AgeCategory $ageCategory): static public function getAgeCategoryLabel(): ?string { - return $this->ageCategory?->label()??'enum.agecategories.notset'; + return $this->ageCategory?->label() ?? 'enum.agecategories.notset'; } public function getUuid(): string @@ -610,13 +610,13 @@ public function getUsers(): object continue; } $userId = $user->getId(); - if ($interaction->getReadStatus()===ReadStatus::Finished) { + if ($interaction->getReadStatus() === ReadStatus::Finished) { $return['read'][] = $userId; } - if ($interaction->getReadingList()===ReadingList::ToRead) { + if ($interaction->getReadingList() === ReadingList::ToRead) { $return['favorite'][] = $userId; } - if ($interaction->getReadingList()===ReadingList::Ignored) { + if ($interaction->getReadingList() === ReadingList::Ignored) { $return['hidden'][] = $userId; } } diff --git a/src/Entity/BookInteraction.php b/src/Entity/BookInteraction.php index 918b5682..7449678c 100644 --- a/src/Entity/BookInteraction.php +++ b/src/Entity/BookInteraction.php @@ -2,8 +2,8 @@ namespace App\Entity; -use App\Enum\ReadStatus; use App\Enum\ReadingList; +use App\Enum\ReadStatus; use App\Repository\BookInteractionRepository; use Doctrine\DBAL\Types\Types; use Doctrine\ORM\Mapping as ORM; @@ -34,7 +34,6 @@ class BookInteraction #[ORM\Column(nullable: true)] private ?int $readPages = null; - /** * @deprecated Use RedingList enum instead */ diff --git a/src/Enum/AgeCategory.php b/src/Enum/AgeCategory.php index 3526a525..90be1000 100644 --- a/src/Enum/AgeCategory.php +++ b/src/Enum/AgeCategory.php @@ -17,15 +17,16 @@ public function label(): string public static function getLabel(?self $value): string { - if (null === $value) { + if (!$value instanceof AgeCategory) { return 'enum.agecategories.notset'; } + return match ($value) { - self::Adult=>'enum.agecategories.adults', - self::Everyone=>'enum.agecategories.everyone', - self::SixteenPlus=>'enum.agecategories.sixteenplus', - self::TenPlus=>'enum.agecategories.tenplus', - self::ThirteenPlus=>'enum.agecategories.thirteenplus', + self::Adult => 'enum.agecategories.adults', + self::Everyone => 'enum.agecategories.everyone', + self::SixteenPlus => 'enum.agecategories.sixteenplus', + self::TenPlus => 'enum.agecategories.tenplus', + self::ThirteenPlus => 'enum.agecategories.thirteenplus', }; } } diff --git a/src/Enum/ReadStatus.php b/src/Enum/ReadStatus.php index b18d0692..32bf922b 100644 --- a/src/Enum/ReadStatus.php +++ b/src/Enum/ReadStatus.php @@ -4,7 +4,6 @@ enum ReadStatus: string { - case NotStarted = 'rs-not-started'; case Started = 'rs-started'; case Finished = 'rs-finished'; @@ -17,9 +16,9 @@ public function label(): string public static function getLabel(self $value): string { return match ($value) { - self::NotStarted=>'enum.readstatus.not-started', - self::Started=>'enum.readstatus.started', - self::Finished=>'enum.readstatus.finished', + self::NotStarted => 'enum.readstatus.not-started', + self::Started => 'enum.readstatus.started', + self::Finished => 'enum.readstatus.finished', }; } } diff --git a/src/Enum/ReadingList.php b/src/Enum/ReadingList.php index 7058caa9..9910ad4e 100644 --- a/src/Enum/ReadingList.php +++ b/src/Enum/ReadingList.php @@ -4,7 +4,6 @@ enum ReadingList: string { - case ToRead = 'rl-to-read'; case Ignored = 'rl-ignored'; case NotDefined = 'rl-undefined'; @@ -17,9 +16,9 @@ public function label(): string public static function getLabel(self $value): string { return match ($value) { - self::ToRead=>'enum.readinglist.toread', - self::Ignored=>'enum.readinglist.ignored', - self::NotDefined=>'enum.readinglist.notdefined', + self::ToRead => 'enum.readinglist.toread', + self::Ignored => 'enum.readinglist.ignored', + self::NotDefined => 'enum.readinglist.notdefined', }; } } diff --git a/src/Repository/BookInteractionRepository.php b/src/Repository/BookInteractionRepository.php index 46e494f9..b5ae21bd 100644 --- a/src/Repository/BookInteractionRepository.php +++ b/src/Repository/BookInteractionRepository.php @@ -56,7 +56,7 @@ public function getFavourite(?int $max = null, bool $hideFinished = true): array $qb->setParameter('to_read', ReadingList::ToRead); if ($hideFinished) { $qb->andWhere('b.readingList != :finished'); - $qb->setParameter('finished',ReadStatus::Finished); + $qb->setParameter('finished', ReadStatus::Finished); } $qb->andWhere('b.readingList != :hidden') diff --git a/src/Repository/BookRepository.php b/src/Repository/BookRepository.php index 61976fde..50e31e95 100644 --- a/src/Repository/BookRepository.php +++ b/src/Repository/BookRepository.php @@ -266,7 +266,6 @@ public function findBySerie(string $serie): array $qb->orderBy('book.serieIndex', 'ASC'); - $user = $this->security->getUser(); if ($user instanceof User) { $qb->andWhere('COALESCE(book.ageCategory,1) <= COALESCE(:ageCategory,10)'); @@ -277,6 +276,7 @@ public function findBySerie(string $serie): array if (!is_array($result)) { return []; } + return $result; } @@ -320,7 +320,7 @@ public function getAllSeries(): Query public function getIncompleteSeries(): Query { - $qb= $this->createQueryBuilder('serie') + $qb = $this->createQueryBuilder('serie') ->select('serie.serie as item') ->addSelect('COUNT(serie.id) as bookCount') ->addSelect('MAX(serie.serieIndex) as lastBookIndex') @@ -341,7 +341,7 @@ public function getStartedSeries(): Query ->addSelect('MAX(serie.serieIndex) as lastBookIndex') ->addSelect('COUNT(bookInteraction.finished) as booksFinished') ->where('serie.serie IS NOT NULL'); - $qb = $this->joinInteractions($qb,'serie'); + $qb = $this->joinInteractions($qb, 'serie'); return $qb->addGroupBy('serie.serie') ->having('COUNT(bookInteraction.readStatus)>0 AND COUNT(bookInteraction.readStatus)where('publisher.publisher IS NOT NULL'); $qb = $this->joinInteractions($qb, 'publisher'); - return $qb->addGroupBy('publisher.publisher')->getQuery(); + return $qb->addGroupBy('publisher.publisher')->getQuery(); } - private function joinInteractions(QueryBuilder $qb, string $alias='book'): QueryBuilder + private function joinInteractions(QueryBuilder $qb, string $alias = 'book'): QueryBuilder { return $qb->leftJoin($alias.'.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.readStatus = :status_finished or bookInteraction.readingList=:list_ignored) and bookInteraction.user= :user') diff --git a/src/Service/BookProgressionService.php b/src/Service/BookProgressionService.php index cc6db88a..06e239d2 100644 --- a/src/Service/BookProgressionService.php +++ b/src/Service/BookProgressionService.php @@ -67,7 +67,7 @@ public function setProgression(Book $book, User $user, ?float $progress): self $this->em->persist($interaction); $book->addBookInteraction($interaction); } - $interaction->setReadPages((int)$readPages); + $interaction->setReadPages((int) $readPages); if ($progress >= 1.0) { $interaction->setReadStatus(ReadStatus::Finished); } else { diff --git a/src/Service/Search/SearchHelper.php b/src/Service/Search/SearchHelper.php index 146cc8b0..96ad4f66 100644 --- a/src/Service/Search/SearchHelper.php +++ b/src/Service/Search/SearchHelper.php @@ -53,7 +53,7 @@ public function prepareQuery(string $q, ?string $filterBy = null, ?string $sortB numTypos: 2, page: $page, perPage: $perPage, - facetBy: 'authors,serie,tags', + facetBy: 'authors,serie,tags,age', facetStrategy: 'exhaustive', ); diff --git a/src/Twig/Components/InlineEditBook.php b/src/Twig/Components/InlineEditBook.php index 7109558c..c5cf93b0 100644 --- a/src/Twig/Components/InlineEditBook.php +++ b/src/Twig/Components/InlineEditBook.php @@ -3,7 +3,6 @@ namespace App\Twig\Components; use App\Entity\Book; -use App\Entity\Shelf; use App\Enum\AgeCategory; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; @@ -29,7 +28,7 @@ class InlineEditBook extends AbstractController public Book $book; #[LiveProp(writable: true)] - public ?AgeCategory $ageCategory=null; + public ?AgeCategory $ageCategory = null; #[LiveProp()] public bool $isEditing = false; @@ -121,9 +120,9 @@ public function save(Request $request, EntityManagerInterface $entityManager): v } if (array_key_exists('updated', $data) && is_array($data['updated']) && array_key_exists('ageCategory', $data['updated'])) { - if($data['updated']['ageCategory'] !== '') { + if ($data['updated']['ageCategory'] !== '') { $this->book->setAgeCategory(AgeCategory::tryFrom($data['updated']['ageCategory'])); - }else{ + } else { $this->book->setAgeCategory(null); } } diff --git a/src/Twig/Components/InlineEditInteraction.php b/src/Twig/Components/InlineEditInteraction.php index f785deae..b37f766a 100644 --- a/src/Twig/Components/InlineEditInteraction.php +++ b/src/Twig/Components/InlineEditInteraction.php @@ -11,12 +11,10 @@ use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\FormFactoryInterface; -use Symfony\Component\Form\FormInterface; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\ComponentToolsTrait; -use Symfony\UX\LiveComponent\ComponentWithFormTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\ValidatableComponentTrait; use Symfony\UX\TwigComponent\Attribute\PostMount; @@ -27,6 +25,10 @@ class InlineEditInteraction extends AbstractController use DefaultActionTrait; use ValidatableComponentTrait; use ComponentToolsTrait; + /** + * @var list|Shelf[] + */ + public $shelves; #[LiveProp(writable: true)] public ?BookInteraction $interaction = null; @@ -42,7 +44,6 @@ class InlineEditInteraction extends AbstractController public function __construct(private EntityManagerInterface $entityManager, private FormFactoryInterface $formFactory) { - } #[PostMount] @@ -53,7 +54,7 @@ public function postMount(): void $shelfRepository = $this->entityManager->getRepository(Shelf::class); $this->shelves = $shelfRepository->findBy(['user' => $this->user]); - $this->shelves = array_filter($this->shelves, static fn($item) => $item->getQueryString() === null); + $this->shelves = array_filter($this->shelves, static fn ($item) => $item->getQueryString() === null); } private function getInteraction(): BookInteraction From 99a1789338e3ea43df85aa202d0dc1f8596da08c Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Mon, 27 Jan 2025 07:02:15 +0100 Subject: [PATCH 07/11] Fix fixtures --- src/DataFixtures/BookFixture.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/DataFixtures/BookFixture.php b/src/DataFixtures/BookFixture.php index a8528bff..0044852e 100644 --- a/src/DataFixtures/BookFixture.php +++ b/src/DataFixtures/BookFixture.php @@ -3,6 +3,7 @@ namespace App\DataFixtures; use App\Entity\Book; +use App\Enum\AgeCategory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Common\DataFixtures\DependentFixtureInterface; use Doctrine\Persistence\ObjectManager; @@ -90,7 +91,7 @@ public function load(ObjectManager $manager): void } if (array_key_exists('ageCategory', $bookData)) { - $book->setAgeCategory($bookData['ageCategory']); + $book->setAgeCategory(AgeCategory::tryFrom($bookData['ageCategory'])); } if (array_key_exists('tags', $bookData)) { From 41af16c542121092f13857139e236364504a30b3 Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Wed, 29 Jan 2025 07:14:09 +0100 Subject: [PATCH 08/11] Refactor into enums --- .../AutocompleteGroupController.php | 9 +- src/Controller/UserController.php | 1 - src/DataFixtures/UserFixture.php | 3 +- src/Entity/User.php | 7 +- src/Form/UserType.php | 4 +- src/Security/Voter/BookVoter.php | 3 +- src/Twig/Components/InlineEditBook.php | 9 +- src/Twig/Components/InlineEditInteraction.php | 56 ++++---- src/Twig/Components/InlineEditMultiple.php | 3 +- .../InlineEditInteraction.html.twig | 121 ++++++------------ templates/user/index.html.twig | 2 +- 11 files changed, 97 insertions(+), 121 deletions(-) diff --git a/src/Controller/AutocompleteGroupController.php b/src/Controller/AutocompleteGroupController.php index c1c76346..2fd47e99 100644 --- a/src/Controller/AutocompleteGroupController.php +++ b/src/Controller/AutocompleteGroupController.php @@ -2,12 +2,13 @@ namespace App\Controller; -use App\Entity\User; +use App\Enum\AgeCategory; use App\Repository\BookRepository; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; +use Symfony\Contracts\Translation\TranslatorInterface; /** * @phpstan-type GroupType array{ item:string, slug:string, bookCount:int, booksFinished:int, lastBookIndex:int } @@ -15,7 +16,7 @@ class AutocompleteGroupController extends AbstractController { #[Route('/autocomplete/group/{type}', name: 'app_autocomplete_group')] - public function index(Request $request, BookRepository $bookRepository, string $type): Response + public function index(Request $request, BookRepository $bookRepository, string $type, TranslatorInterface $translator): Response { $query = $request->get('query'); if (!is_string($query)) { @@ -25,8 +26,8 @@ public function index(Request $request, BookRepository $bookRepository, string $ $json = ['results' => []]; if ($type === 'ageCategory') { - foreach (User::AGE_CATEGORIES as $ageCategory => $ageCategoryId) { - $json['results'][] = ['value' => $ageCategoryId, 'text' => $ageCategory]; + foreach (AgeCategory::cases() as $ageCategory) { + $json['results'][] = ['value' => $ageCategory->value, 'text' => $translator->trans(AgeCategory::getLabel($ageCategory))]; } return new JsonResponse($json); diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php index 51c44df4..17b4fc61 100644 --- a/src/Controller/UserController.php +++ b/src/Controller/UserController.php @@ -27,7 +27,6 @@ public function index(UserRepository $userRepository): Response { return $this->render('user/index.html.twig', [ 'users' => $userRepository->findAll(), - 'age_categories' => array_flip(User::AGE_CATEGORIES), ]); } diff --git a/src/DataFixtures/UserFixture.php b/src/DataFixtures/UserFixture.php index bea54fea..cdd4ffff 100644 --- a/src/DataFixtures/UserFixture.php +++ b/src/DataFixtures/UserFixture.php @@ -3,6 +3,7 @@ namespace App\DataFixtures; use App\Entity\User; +use App\Enum\AgeCategory; use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Persistence\ObjectManager; use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; @@ -42,7 +43,7 @@ public function load(ObjectManager $manager): void $user->setUsername(self::CHILD_USERNAME); $user->setBirthday(new \DateTimeImmutable('1990-01-01')); $user->setLanguage('en'); - $user->setMaxAgeCategory(1); + $user->setMaxAgeCategory(AgeCategory::TenPlus); $user->setPassword($this->passwordHasher->hashPassword($user, self::CHILD_PASSWORD)); $user->setRoles(['ROLE_USER']); diff --git a/src/Entity/User.php b/src/Entity/User.php index ec2670da..6484c2d6 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -2,6 +2,7 @@ namespace App\Entity; +use App\Enum\AgeCategory; use App\Repository\UserRepository; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; @@ -70,7 +71,7 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface private ?\DateTimeInterface $birthday = null; #[ORM\Column(nullable: true)] - private ?int $maxAgeCategory = null; + private ?AgeCategory $maxAgeCategory = null; #[ORM\Column(length: 255, nullable: true)] /** @@ -355,12 +356,12 @@ public function setBirthday(?\DateTimeInterface $birthday): static return $this; } - public function getMaxAgeCategory(): ?int + public function getMaxAgeCategory(): ?AgeCategory { return $this->maxAgeCategory; } - public function setMaxAgeCategory(?int $maxAgeCategory): static + public function setMaxAgeCategory(?AgeCategory $maxAgeCategory): static { $this->maxAgeCategory = $maxAgeCategory; diff --git a/src/Form/UserType.php b/src/Form/UserType.php index ac1674d7..f3c298f4 100644 --- a/src/Form/UserType.php +++ b/src/Form/UserType.php @@ -3,8 +3,10 @@ namespace App\Form; use App\Entity\User; +use App\Enum\AgeCategory; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\Extension\Core\Type\PasswordType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; @@ -26,7 +28,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'label' => 'user.form.roles', 'translation_domain' => false, ]) - ->add('maxAgeCategory', ChoiceType::class, ['choices' => User::AGE_CATEGORIES, 'required' => false]) + ->add('maxAgeCategory', EnumType::class, ['class' => AgeCategory::class, 'required' => false]) ->add('language', ChoiceType::class, [ 'choices' => [ 'user.form.language.english' => 'en', diff --git a/src/Security/Voter/BookVoter.php b/src/Security/Voter/BookVoter.php index fcbecea4..11bac3ff 100644 --- a/src/Security/Voter/BookVoter.php +++ b/src/Security/Voter/BookVoter.php @@ -4,6 +4,7 @@ use App\Entity\Book; use App\Entity\User; +use App\Enum\AgeCategory; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; @@ -34,7 +35,7 @@ protected function voteOnAttribute(string $attribute, mixed $subject, TokenInter return match ($attribute) { self::EDIT => in_array('ROLE_ADMIN', $user->getRoles(), true), - self::VIEW => $user->getMaxAgeCategory() === null || $subject->getAgeCategory() <= $user->getMaxAgeCategory(), + self::VIEW => !$user->getMaxAgeCategory() instanceof AgeCategory || ($subject->getAgeCategory()->value ?? 0) <= ($user->getMaxAgeCategory()->value ?? 999), default => false, }; } diff --git a/src/Twig/Components/InlineEditBook.php b/src/Twig/Components/InlineEditBook.php index c5cf93b0..958c4ab9 100644 --- a/src/Twig/Components/InlineEditBook.php +++ b/src/Twig/Components/InlineEditBook.php @@ -63,7 +63,7 @@ public function activateEditing(): void } #[LiveAction] - public function usesuggestion(#[LiveArg] string $field, #[LiveArg] string $suggestion, EntityManagerInterface $entityManager): void + public function usesuggestion(#[LiveArg] string $field, #[LiveArg] string $suggestion): void { $this->isEditing = true; $to_call = 'set'.ucfirst($field); @@ -90,7 +90,7 @@ public function usesuggestion(#[LiveArg] string $field, #[LiveArg] string $sugge $this->book->$to_call($value); } } - $entityManager->flush(); + $this->entityManager->flush(); $this->dispatchBrowserEvent('manager:flush'); $this->isEditing = false; @@ -102,7 +102,7 @@ public function usesuggestion(#[LiveArg] string $field, #[LiveArg] string $sugge */ #[LiveAction] #[LiveListener('submit')] - public function save(Request $request, EntityManagerInterface $entityManager): void + public function save(Request $request): void { $all = $request->request->all(); if (!array_key_exists('data', $all)) { @@ -121,13 +121,14 @@ public function save(Request $request, EntityManagerInterface $entityManager): v if (array_key_exists('updated', $data) && is_array($data['updated']) && array_key_exists('ageCategory', $data['updated'])) { if ($data['updated']['ageCategory'] !== '') { + // @phpstan-ignore-next-line $this->book->setAgeCategory(AgeCategory::tryFrom($data['updated']['ageCategory'])); } else { $this->book->setAgeCategory(null); } } - $entityManager->flush(); + $this->entityManager->flush(); $this->dispatchBrowserEvent('manager:flush'); $this->isEditing = false; diff --git a/src/Twig/Components/InlineEditInteraction.php b/src/Twig/Components/InlineEditInteraction.php index b37f766a..1732c6ea 100644 --- a/src/Twig/Components/InlineEditInteraction.php +++ b/src/Twig/Components/InlineEditInteraction.php @@ -10,9 +10,9 @@ use App\Enum\ReadStatus; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\Form\FormFactoryInterface; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; +use Symfony\UX\LiveComponent\Attribute\LiveArg; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\ComponentToolsTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; @@ -33,16 +33,17 @@ class InlineEditInteraction extends AbstractController #[LiveProp(writable: true)] public ?BookInteraction $interaction = null; + #[LiveProp(writable: true, format: 'Y-m-d')] + public ?\DateTime $finished = null; + #[LiveProp()] public User $user; #[LiveProp()] public Book $book; public ?string $flashMessage = null; - public ?string $flashMessageFav = null; - public ?string $flashMessageHidden = null; - public function __construct(private EntityManagerInterface $entityManager, private FormFactoryInterface $formFactory) + public function __construct(private EntityManagerInterface $entityManager) { } @@ -51,6 +52,9 @@ public function postMount(): void { $this->interaction = $this->getInteraction(); + // @phpstan-ignore-next-line + $this->finished = $this->interaction->getFinishedDate(); + $shelfRepository = $this->entityManager->getRepository(Shelf::class); $this->shelves = $shelfRepository->findBy(['user' => $this->user]); @@ -75,52 +79,56 @@ private function getInteraction(): BookInteraction } #[LiveAction] - public function toggleReadStatus(): void + public function saveDate(): void { $interaction = $this->getInteraction(); - $interaction->setReadStatus(ReadStatus::toggle($interaction->getReadStatus())); - $this->book->setUpdated(new \DateTime('now')); - $this->entityManager->persist($this->book); + $interaction->setFinishedDate($this->finished); + $this->entityManager->persist($interaction); + $this->entityManager->flush(); - $this->interaction = $interaction; $this->flashMessage = 'Read status updated'; } #[LiveAction] - public function saveInteraction(): void + public function toggleReadStatus(#[LiveArg] string $value): void { - $this->submitForm(); - - $interaction = $this->getForm()->getData(); + $interaction = $this->getInteraction(); - if (!$interaction instanceof BookInteraction) { - throw new \RuntimeException('Invalid data'); - } + $readStatus = ReadStatus::tryFrom($value); + match ($readStatus) { + null => $interaction->setReadStatus(ReadStatus::NotStarted), + default => $interaction->setReadStatus($readStatus), + }; - $this->entityManager->persist($interaction); $this->book->setUpdated(new \DateTime('now')); $this->entityManager->persist($this->book); + $this->entityManager->persist($interaction); $this->entityManager->flush(); - $this->flashMessageFav = 'Saved'; - $this->dispatchBrowserEvent('manager:flush'); + $this->interaction = $interaction; + + $this->flashMessage = 'Read status updated'; } #[LiveAction] - public function toggleReadingList(EntityManagerInterface $entityManager): void + public function toggleReadingList(#[LiveArg] string $value): void { $interaction = $this->getInteraction(); - $interaction->setReadingList(ReadingList::toggle($interaction->getReadingList())); + $readingList = ReadingList::tryFrom($value); + match ($readingList) { + null => $interaction->setReadingList(ReadingList::NotDefined), + default => $interaction->setReadingList($readingList), + }; - $entityManager->persist($interaction); + $this->entityManager->persist($interaction); $this->book->setUpdated(new \DateTime('now')); $this->entityManager->persist($this->book); - $entityManager->flush(); + $this->entityManager->flush(); $this->interaction = $interaction; - $this->flashMessageFav = 'Reading list updated'; + $this->flashMessage = 'Reading list updated'; } } diff --git a/src/Twig/Components/InlineEditMultiple.php b/src/Twig/Components/InlineEditMultiple.php index 8e886bc7..aaf4caa3 100644 --- a/src/Twig/Components/InlineEditMultiple.php +++ b/src/Twig/Components/InlineEditMultiple.php @@ -3,6 +3,7 @@ namespace App\Twig\Components; use App\Entity\Book; +use App\Enum\AgeCategory; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; @@ -63,7 +64,7 @@ public function save(EntityManagerInterface $entityManager): void break; case 'ageCategory': $value = reset($this->fieldValue); - $book->setAgeCategory((int) $value); + $book->setAgeCategory(AgeCategory::tryFrom((int) $value)); break; default: diff --git a/templates/components/InlineEditInteraction.html.twig b/templates/components/InlineEditInteraction.html.twig index 98481284..28096f1e 100644 --- a/templates/components/InlineEditInteraction.html.twig +++ b/templates/components/InlineEditInteraction.html.twig @@ -1,87 +1,48 @@
    - {% set finished=false %} - {% set modalId=unique_id('interaction-', false) %} - {% if interaction is not null %} - {% if interaction.finished %} - {% set finished=true %} - {% endif %} +
    + {% for case in enum('App\\Enum\\ReadStatus').cases() %} + + {% endfor %} +
    + + {% if interaction.finishedDate is not null or interaction.readStatus == enum('App\\Enum\\ReadStatus').Finished %} +
    + +
    {% endif %} - {% set hidden=false %} - {% if interaction is not null %} - {% if interaction.hidden %} - {% set hidden=true %} - {% endif %} +
    + {% for case in enum('App\\Enum\\ReadingList').cases() %} + + {% endfor %} +
    + {% if flashMessage %} + {{ flashMessage }} {% endif %} +
    - - - - {% set favorite = false %} - {% if interaction is not null %} - {% if interaction.favorite %} - {% set favorite = true %} - {% endif %} - {% endif %} - - - - - - - {% component BootstrapModal with {id: modalId} %} - {% block modal_header %} -
    {{ book.title }}
    - - {% endblock %} - {% block modal_body %} - - {{ form(form,{ - attr: { - 'novalidate': true, - 'class': '', - 'data-action': 'live#action', - 'data-live-action-param': 'prevent|saveInteraction', - } - }) }} - {% endblock %} - - {% endcomponent %} -
    \ No newline at end of file + diff --git a/templates/user/index.html.twig b/templates/user/index.html.twig index 19567bd1..fe165d13 100644 --- a/templates/user/index.html.twig +++ b/templates/user/index.html.twig @@ -23,7 +23,7 @@ {% if user.maxAgeCategory is not null %} - {{ user.maxAgeCategory }} - {{ age_categories[user.maxAgeCategory] }} + {{ enum("App\\Enum\\AgeCategory").getLabel(user.maxAgeCategory)|trans }} {% endif %} From 7dc2f3bfb06743621c920eaccb120f6872e2d2bf Mon Sep 17 00:00:00 2001 From: Sergio Mendolia Date: Sun, 2 Feb 2025 09:47:59 +0100 Subject: [PATCH 09/11] Refactor book interactions and reading logic. Made `book_id` and `user_id` non-nullable, enforcing unique constraints in `book_interaction`. Introduced enums for `ReadStatus` and `ReadingList`, removing deprecated fields. Updated templates, controllers, and services to support refactored logic. Improved database queries for efficiency. --- migrations/Version20250202073734.php | 32 +++++++ phpstan.neon | 2 +- src/Controller/BookController.php | 2 +- src/Controller/OPDS/OpdsController.php | 3 - src/Entity/Book.php | 9 +- src/Entity/BookInteraction.php | 29 +++--- src/Entity/User.php | 5 +- src/Enum/ReadStatus.php | 9 ++ src/Enum/ReadingList.php | 11 ++- src/Repository/BookRepository.php | 28 +++--- src/Service/BookInteractionService.php | 8 +- src/Twig/Components/InlineEditBook.php | 4 +- src/Twig/Components/InlineEditInteraction.php | 77 +++++++--------- templates/author/detail.html.twig | 4 +- templates/book/_interaction.html.twig | 14 +-- templates/book/book-row.html.twig | 2 +- templates/book/index.html.twig | 15 ++-- .../InlineEditInteraction.html.twig | 88 +++++++------------ templates/serie/detail.html.twig | 4 +- .../Api/V1/Library/StateControllerTest.php | 7 +- tests/Service/BookProgressionServiceTest.php | 2 +- translations/messages+intl-icu.en.yaml | 7 ++ translations/messages+intl-icu.fr.yaml | 7 ++ 23 files changed, 194 insertions(+), 175 deletions(-) create mode 100644 migrations/Version20250202073734.php diff --git a/migrations/Version20250202073734.php b/migrations/Version20250202073734.php new file mode 100644 index 00000000..665373e7 --- /dev/null +++ b/migrations/Version20250202073734.php @@ -0,0 +1,32 @@ +addSql('DELETE FROM book_interaction WHERE book_id is null or user_id is null'); + $this->addSql('ALTER TABLE book_interaction CHANGE user_id user_id INT NOT NULL, CHANGE book_id book_id INT NOT NULL'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE book_interaction CHANGE user_id user_id INT DEFAULT NULL, CHANGE book_id book_id INT DEFAULT NULL'); + } +} diff --git a/phpstan.neon b/phpstan.neon index 999c26d5..41e998fc 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -10,4 +10,4 @@ parameters: # - '#Asserted type (.*) for (.*) with type (.*) does not narrow down the type.#' errorFormat: symplify typeAliases: - ReadingStateCriteria: "array{'book':int, 'readPages': int|null, 'finished': boolean}" + ReadingStateCriteria: "array{'book':int, 'readPages': int|null, 'readStatus': App\\Enum\\ReadStatus}" diff --git a/src/Controller/BookController.php b/src/Controller/BookController.php index af268c9f..1350cccd 100644 --- a/src/Controller/BookController.php +++ b/src/Controller/BookController.php @@ -211,7 +211,7 @@ public function read( // TODO Add next unread in serie to reading list? - $interaction->setFinishedDate(new \DateTime()); + $interaction->setFinishedDate(new \DateTimeImmutable()); $this->addFlash('success', 'Book finished! Congratulations!'); $manager->flush(); diff --git a/src/Controller/OPDS/OpdsController.php b/src/Controller/OPDS/OpdsController.php index b95174c9..45ba91b1 100644 --- a/src/Controller/OPDS/OpdsController.php +++ b/src/Controller/OPDS/OpdsController.php @@ -151,9 +151,6 @@ public function readinglist(BookInteractionRepository $bookInteractionRepository $feeds = []; foreach ($books as $bookInteraction) { - if ($bookInteraction->getBook() === null) { - continue; - } $feeds[] = $this->opds->convertBookToOpdsEntry($bookInteraction->getBook()); } $opds->feeds($feeds); diff --git a/src/Entity/Book.php b/src/Entity/Book.php index 192a3316..b92dff6b 100644 --- a/src/Entity/Book.php +++ b/src/Entity/Book.php @@ -435,10 +435,7 @@ public function addBookInteraction(BookInteraction $bookInteraction): static public function removeBookInteraction(BookInteraction $bookInteraction): static { - // set the owning side to null (unless already changed) - if ($this->bookInteractions->removeElement($bookInteraction) && $bookInteraction->getBook() === $this) { - $bookInteraction->setBook(null); - } + $this->bookInteractions->removeElement($bookInteraction); return $this; } @@ -606,9 +603,7 @@ public function getUsers(): object foreach ($this->getBookInteractions() as $interaction) { $user = $interaction->getUser(); - if ($user === null) { - continue; - } + $userId = $user->getId(); if ($interaction->getReadStatus() === ReadStatus::Finished) { $return['read'][] = $userId; diff --git a/src/Entity/BookInteraction.php b/src/Entity/BookInteraction.php index 7449678c..54f7a21f 100644 --- a/src/Entity/BookInteraction.php +++ b/src/Entity/BookInteraction.php @@ -10,6 +10,9 @@ use Gedmo\Mapping\Annotation as Gedmo; #[ORM\Entity(repositoryClass: BookInteractionRepository::class)] +#[ORM\Table(name: 'book_interaction', uniqueConstraints: [ + new ORM\UniqueConstraint(name: 'unique_user_book', columns: ['user_id', 'book_id']), +])] class BookInteraction { #[ORM\Id] @@ -18,12 +21,12 @@ class BookInteraction private ?int $id = null; #[ORM\ManyToOne(inversedBy: 'bookInteractions')] - #[ORM\JoinColumn(nullable: true)] - private ?User $user = null; + #[ORM\JoinColumn(nullable: false)] + private User $user; #[ORM\ManyToOne(inversedBy: 'bookInteractions')] - #[ORM\JoinColumn(nullable: true)] - private ?Book $book = null; + #[ORM\JoinColumn(nullable: false)] + private Book $book; /** * @deprecated Use ReadStatus enum instead @@ -40,8 +43,8 @@ class BookInteraction #[ORM\Column(nullable: false)] private bool $favorite = false; - #[ORM\Column(type: Types::DATE_MUTABLE, nullable: true)] - private ?\DateTimeInterface $finishedDate = null; + #[ORM\Column(type: Types::DATE_IMMUTABLE, nullable: true)] + private ?\DateTimeImmutable $finishedDate = null; #[ORM\Column(type: Types::DATETIME_IMMUTABLE, options: ['default' => '2024-01-12 00:00:00'])] #[Gedmo\Timestampable(on: 'create', )] @@ -71,24 +74,24 @@ public function getId(): ?int return $this->id; } - public function getUser(): ?User + public function getUser(): User { return $this->user; } - public function setUser(?User $user): static + public function setUser(User $user): static { $this->user = $user; return $this; } - public function getBook(): ?Book + public function getBook(): Book { return $this->book; } - public function setBook(?Book $book): static + public function setBook(Book $book): static { $this->book = $book; @@ -131,12 +134,12 @@ public function setFavorite(bool $favorite): static return $this; } - public function getFinishedDate(): ?\DateTimeInterface + public function getFinishedDate(): ?\DateTimeImmutable { return $this->finishedDate; } - public function setFinishedDate(?\DateTimeInterface $finishedDate): static + public function setFinishedDate(?\DateTimeImmutable $finishedDate): static { $this->finishedDate = $finishedDate; @@ -155,7 +158,7 @@ public function getUpdated(): ?\DateTimeImmutable public function getReadPages(): ?int { - if ($this->readStatus !== ReadStatus::Started) { + if ($this->readStatus !== ReadStatus::Started && $this->readStatus !== ReadStatus::Finished) { return null; } diff --git a/src/Entity/User.php b/src/Entity/User.php index ceb548cf..6f360abf 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -233,10 +233,7 @@ public function addBookInteraction(BookInteraction $bookInteraction): static public function removeBookInteraction(BookInteraction $bookInteraction): static { - // set the owning side to null (unless already changed) - if ($this->bookInteractions->removeElement($bookInteraction) && $bookInteraction->getUser() === $this) { - $bookInteraction->setUser(null); - } + $this->bookInteractions->removeElement($bookInteraction); return $this; } diff --git a/src/Enum/ReadStatus.php b/src/Enum/ReadStatus.php index 32bf922b..8840dd27 100644 --- a/src/Enum/ReadStatus.php +++ b/src/Enum/ReadStatus.php @@ -21,4 +21,13 @@ public static function getLabel(self $value): string self::Finished => 'enum.readstatus.finished', }; } + + public static function getIcon(self $value): string + { + return match ($value) { + self::NotStarted => 'question-circle', + self::Started => 'hourglass-split', + self::Finished => 'check-circle-fill', + }; + } } diff --git a/src/Enum/ReadingList.php b/src/Enum/ReadingList.php index 9910ad4e..28b39b23 100644 --- a/src/Enum/ReadingList.php +++ b/src/Enum/ReadingList.php @@ -4,9 +4,9 @@ enum ReadingList: string { + case NotDefined = 'rl-undefined'; case ToRead = 'rl-to-read'; case Ignored = 'rl-ignored'; - case NotDefined = 'rl-undefined'; public function label(): string { @@ -21,4 +21,13 @@ public static function getLabel(self $value): string self::NotDefined => 'enum.readinglist.notdefined', }; } + + public static function getIcon(self $value): string + { + return match ($value) { + self::ToRead => 'bookmark-heart-fill', + self::Ignored => 'bookmark-x-fill', + self::NotDefined => 'bookmark', + }; + } } diff --git a/src/Repository/BookRepository.php b/src/Repository/BookRepository.php index ab97a2a8..3047cb18 100644 --- a/src/Repository/BookRepository.php +++ b/src/Repository/BookRepository.php @@ -318,16 +318,21 @@ public function getFirstUnreadBook(string $serie): Book return $firstUnreadBook; } + + /** + * @return GroupType[] + */ + public function getAllSeries(): array { - $qb = $this->createQueryBuilder('serie') + $result = $this->createQueryBuilder('serie') ->select('serie.serie as item') ->addSelect('COUNT(serie.id) as bookCount') ->addSelect('MAX(serie.serieIndex) as lastBookIndex') ->addSelect('COUNT(bookInteraction.finished) as booksFinished') - ->where('serie.serie IS NOT NULL'); - $qb = $this->joinInteractions($qb, 'serie'); - - return $qb->addGroupBy('serie.serie')->getQuery(); + ->where('serie.serie IS NOT NULL') + ->leftJoin('serie.bookInteractions', 'bookInteraction', 'WITH', '(bookInteraction.finished = true or bookInteraction.hidden=true) and bookInteraction.user= :user') + ->setParameter('user', $this->security->getUser()) + ->addGroupBy('serie.serie')->getQuery(); // @phpstan-ignore-next-line return $this->convertResults($result->getResult()); @@ -375,7 +380,10 @@ public function getAllPublishers(): array ->where('publisher.publisher IS NOT NULL'); $qb = $this->joinInteractions($qb, 'publisher'); - return $qb->addGroupBy('publisher.publisher')->getQuery(); + $results = $qb->addGroupBy('publisher.publisher')->getQuery(); + + // @phpstan-ignore-next-line + return $this->convertResults($results->getResult()); } private function joinInteractions(QueryBuilder $qb, string $alias = 'book'): QueryBuilder @@ -385,9 +393,6 @@ private function joinInteractions(QueryBuilder $qb, string $alias = 'book'): Que ->setParameter('status_finished', ReadStatus::Finished) ->setParameter('list_ignored', ReadingList::Ignored) ->setParameter('user', $this->security->getUser()); - - // @phpstan-ignore-next-line - return $this->convertResults($results->getResult()); } /** @@ -403,6 +408,7 @@ public function getAllAuthors(): array $qb->addGroupBy('author.authors'); + // @phpstan-ignore-next-line return $this->convertResults($qb->getQuery()->getResult()); } @@ -419,6 +425,7 @@ public function getAllTags(): array $qb->addGroupBy('tag.tags'); + // @phpstan-ignore-next-line return $this->convertResults($qb->getQuery()->getResult()); } @@ -512,9 +519,6 @@ private function getChangedBooksQueryBuilder(KoboDevice $koboDevice, SyncToken $ $readingList = $this->getEntityManager()->getRepository(BookInteraction::class)->getFavourite(); foreach ($readingList as $bookInteraction) { $book = $bookInteraction->getBook(); - if ($book === null) { - continue; - } $books[$book->getId()] = $book; } } diff --git a/src/Service/BookInteractionService.php b/src/Service/BookInteractionService.php index 8858072f..8ce2c9d3 100644 --- a/src/Service/BookInteractionService.php +++ b/src/Service/BookInteractionService.php @@ -4,6 +4,8 @@ use App\Entity\Book; use App\Entity\User; +use App\Enum\ReadingList; +use App\Enum\ReadStatus; class BookInteractionService { @@ -19,13 +21,13 @@ public function getStats($books, User $user): array foreach ($books as $book) { $interaction = $book->getLastInteraction($user); if ($interaction !== null) { - if ($interaction->isFinished()) { + if ($interaction->getReadStatus() === ReadStatus::Finished) { $readBooks++; - } elseif ($interaction->getReadPages() > 0) { + } elseif ($interaction->getReadStatus() === ReadStatus::Started) { $inProgressBooks++; } - if ($interaction->isHidden()) { + if ($interaction->getReadingList() === ReadingList::Ignored) { $hiddenBooks++; } } diff --git a/src/Twig/Components/InlineEditBook.php b/src/Twig/Components/InlineEditBook.php index 92aea575..07616655 100644 --- a/src/Twig/Components/InlineEditBook.php +++ b/src/Twig/Components/InlineEditBook.php @@ -53,8 +53,8 @@ class InlineEditBook extends AbstractController public ?string $flashMessage = null; public function __construct(private readonly EntityManagerInterface $entityManager) - { $this->locales = $this->getTwoLettersLocales(); - + { + $this->locales = $this->getTwoLettersLocales(); } #[PostMount] diff --git a/src/Twig/Components/InlineEditInteraction.php b/src/Twig/Components/InlineEditInteraction.php index 232db07a..cca73313 100644 --- a/src/Twig/Components/InlineEditInteraction.php +++ b/src/Twig/Components/InlineEditInteraction.php @@ -11,12 +11,15 @@ use App\Form\InlineInteractionType; use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Bundle\SecurityBundle\Security; +use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\Form\FormInterface; use Symfony\UX\LiveComponent\Attribute\AsLiveComponent; use Symfony\UX\LiveComponent\Attribute\LiveAction; use Symfony\UX\LiveComponent\Attribute\LiveArg; use Symfony\UX\LiveComponent\Attribute\LiveProp; use Symfony\UX\LiveComponent\ComponentToolsTrait; +use Symfony\UX\LiveComponent\ComponentWithFormTrait; use Symfony\UX\LiveComponent\DefaultActionTrait; use Symfony\UX\LiveComponent\ValidatableComponentTrait; use Symfony\UX\TwigComponent\Attribute\PostMount; @@ -26,11 +29,8 @@ class InlineEditInteraction extends AbstractController { use DefaultActionTrait; use ValidatableComponentTrait; + use ComponentWithFormTrait; use ComponentToolsTrait; - /** - * @var list|Shelf[] - */ - public $shelves; #[LiveProp(writable: true)] public ?BookInteraction $interaction = null; @@ -38,35 +38,38 @@ class InlineEditInteraction extends AbstractController #[LiveProp(writable: true, format: 'Y-m-d')] public ?\DateTime $finished = null; - #[LiveProp()] - public User $user; #[LiveProp()] public Book $book; - /** - * @var array - */ - #[LiveProp()] + public ?string $flashMessage = null; public ?array $shelves = null; - public ?string $flashMessage = null; + public function __construct(private readonly EntityManagerInterface $entityManager, private readonly Security $security, private readonly FormFactoryInterface $formFactory) + { + } - public function __construct(private EntityManagerInterface $entityManager) + private function getCurrentUser(): User { + $user = $this->security->getUser(); + if (!$user instanceof User) { + throw new \RuntimeException('User not found'); + } + + return $user; + } + + private function getUserShelves(): array + { + $shelves = $this->getCurrentUser()->getShelves()->toArray(); + + return array_filter($shelves, static fn ($item) => $item->getQueryString() === null); } #[PostMount] public function postMount(): void { $this->interaction = $this->getOrCreateInteraction(); - - // @phpstan-ignore-next-line - $this->finished = $this->interaction->getFinishedDate(); - - $shelfRepository = $this->entityManager->getRepository(Shelf::class); - - $this->shelves = $shelfRepository->findBy(['user' => $this->user]); - $this->shelves = array_filter($this->shelves, static fn ($item) => $item->getQueryString() === null); + $this->shelves = $this->getUserShelves(); } #[\Override] @@ -77,43 +80,24 @@ protected function instantiateForm(): FormInterface private function getOrCreateInteraction(): BookInteraction { - $interaction = $this->interaction; + $interaction = $this->entityManager->getRepository(BookInteraction::class)->findOneBy(['book' => $this->book, 'user' => $this->getCurrentUser()]); if (!$interaction instanceof BookInteraction) { - // as the interaction value should be passed from the parent component - // if it is not set we consider no interaction exists yet $interaction = new BookInteraction(); - $interaction->setUser($this->user); + $interaction->setUser($this->getCurrentUser()); $interaction->setBook($this->book); $interaction->setReadPages(0); $interaction->setFinishedDate(null); } - return $interaction; - } - - #[LiveAction] - public function saveDate(): void - { - $interaction = $this->getOrCreateInteraction(); - - $interaction->setFinished(!$interaction->isFinished()); - $this->book->setUpdated(new \DateTimeImmutable('now')); - $this->entityManager->persist($this->book); - $interaction->setFinishedDate($this->finished); - - $this->entityManager->persist($interaction); - - $this->entityManager->flush(); + $this->interaction = $interaction; - $this->flashMessage = $interaction->isFinished() - ? 'inlineeditinteraction.flash.read' - : 'inlineeditinteraction.flash.unread'; + return $interaction; } #[LiveAction] public function toggleReadStatus(#[LiveArg] string $value): void { - $interaction = $this->getInteraction(); + $interaction = $this->getOrCreateInteraction(); $readStatus = ReadStatus::tryFrom($value); match ($readStatus) { @@ -121,6 +105,10 @@ public function toggleReadStatus(#[LiveArg] string $value): void default => $interaction->setReadStatus($readStatus), }; + if ($readStatus === ReadStatus::Finished) { + $interaction->setFinishedDate(new \DateTimeImmutable()); + } + $this->book->setUpdated(new \DateTimeImmutable('now')); $this->entityManager->persist($this->book); $this->entityManager->persist($interaction); @@ -142,6 +130,7 @@ public function toggleReadingList(#[LiveArg] string $value): void }; $this->entityManager->persist($interaction); + $this->book->setUpdated(new \DateTimeImmutable('now')); $this->entityManager->persist($this->book); $this->entityManager->flush(); diff --git a/templates/author/detail.html.twig b/templates/author/detail.html.twig index 06c4476e..a1aa55d7 100644 --- a/templates/author/detail.html.twig +++ b/templates/author/detail.html.twig @@ -29,10 +29,10 @@
    - {{ 'books.reading.progress'|trans({"%read%": readBooks, "%total%": books|length }) }} + {{ 'books.reading.progress'|trans({"%read%": readBooks+hiddenBooks, "%total%": books|length }) }}
    -
    +
    diff --git a/templates/book/_interaction.html.twig b/templates/book/_interaction.html.twig index 9e36d0a3..64a57cac 100644 --- a/templates/book/_interaction.html.twig +++ b/templates/book/_interaction.html.twig @@ -1,12 +1,4 @@ {# @var book App\Entity\Book #} -{% set existing=false %} -{% for interaction in book.bookInteractions %} - {% if is_granted('VIEW',interaction) %} - {% set existing=true %} - {{ component('InlineEditInteraction',{'user':app.user,'book':book, 'interaction': interaction, 'shelves': shelves, 'buttonWithText': true}) }} - {% endif %} -{% endfor %} - -{% if not existing %} - {{ component('InlineEditInteraction',{'user':app.user,'book':book, 'shelves': shelves, 'buttonWithText': true}) }} -{% endif %} \ No newline at end of file +{% if is_granted('VIEW',interaction) %} + {{ component('InlineEditInteraction',{'book':book, 'buttonWithText': true}) }} +{% endif %} diff --git a/templates/book/book-row.html.twig b/templates/book/book-row.html.twig index 384460e9..62406cac 100644 --- a/templates/book/book-row.html.twig +++ b/templates/book/book-row.html.twig @@ -21,6 +21,6 @@ {% endif %} - {{ component('InlineEditInteraction', {book:book, user: app.user, shelves: shelves}) }} + {{ component('InlineEditInteraction', {book:book}) }} diff --git a/templates/book/index.html.twig b/templates/book/index.html.twig index cfa9ef4b..e64af25b 100644 --- a/templates/book/index.html.twig +++ b/templates/book/index.html.twig @@ -35,20 +35,23 @@ {{ 'book.read-state'|trans }} - {% if interaction is not null and interaction.finished %} - {{ 'interaction.status.finished'|trans }} - {% elseif interaction is null or interaction.readPages is null or interaction.readPages==1 or book.pageNumber==0 or book.pageNumber is null %} - {{ 'interaction.status.not-started'|trans }} - {% else %} + {{ enum('App\\Enum\\ReadStatus').getLabel(interaction.readStatus|default( enum('App\\Enum\\ReadStatus').NotStarted))|trans }} + + {% if interaction is not null and interaction.readPages >0 %}
    - {{ 'interaction.status.progress'|trans({"%read%": interaction.readPages, "%total%": book.pageNumber }) }}
    {% endif %} + + {{ 'book.readinglist-state'|trans }} + + {{ enum('App\\Enum\\ReadingList').getLabel(interaction.readingList|default( enum('App\\Enum\\ReadingList').NotDefined))|trans }} + + {{ 'book.authors'|trans }} {{ component('InlineEditBook', {'book':book, 'field':'authors'}) }} diff --git a/templates/components/InlineEditInteraction.html.twig b/templates/components/InlineEditInteraction.html.twig index 1dcb10bc..6c51184c 100644 --- a/templates/components/InlineEditInteraction.html.twig +++ b/templates/components/InlineEditInteraction.html.twig @@ -4,21 +4,6 @@ {% set modalIdInteraction=unique_id('interaction-', false) %} {% set modalIdShelf=unique_id('shelf-', false) %} - {% set finished=false %} - {% set hidden=false %} - {% set favorite = false %} - {% if interaction is not null %} - {% if interaction.finished %} - {% set finished=true %} - {% endif %} - {% if interaction.hidden %} - {% set hidden=true %} - {% endif %} - {% if interaction.favorite %} - {% set favorite = true %} - {% endif %} - {% endif %} -