From 4eb21c1005e5a8ac96be5a9c7c5d61126dca4de3 Mon Sep 17 00:00:00 2001 From: Julio Date: Tue, 26 Oct 2021 19:42:13 +0200 Subject: [PATCH] Add pages entities + tests --- .../Controller/Admin/SettingsController.php | 11 +- src/CoreBundle/DataFixtures/PageFixtures.php | 28 ++ .../DataProvider/Extension/PageExtension.php | 66 +++++ src/CoreBundle/Entity/Page.php | 255 ++++++++++++++++ src/CoreBundle/Entity/PageCategory.php | 148 ++++++++++ src/CoreBundle/Repository/PageRepository.php | 33 +++ tests/ChamiloTestTrait.php | 5 + .../CourseCategoryRepositoryTest.php | 18 +- .../Repository/PageRepositoryTest.php | 273 ++++++++++++++++++ 9 files changed, 816 insertions(+), 21 deletions(-) create mode 100644 src/CoreBundle/DataFixtures/PageFixtures.php create mode 100644 src/CoreBundle/DataProvider/Extension/PageExtension.php create mode 100644 src/CoreBundle/Entity/Page.php create mode 100644 src/CoreBundle/Entity/PageCategory.php create mode 100644 src/CoreBundle/Repository/PageRepository.php create mode 100644 tests/CoreBundle/Repository/PageRepositoryTest.php diff --git a/src/CoreBundle/Controller/Admin/SettingsController.php b/src/CoreBundle/Controller/Admin/SettingsController.php index 5a743fd3ce..446e2462e2 100644 --- a/src/CoreBundle/Controller/Admin/SettingsController.php +++ b/src/CoreBundle/Controller/Admin/SettingsController.php @@ -7,7 +7,6 @@ declare(strict_types=1); namespace Chamilo\CoreBundle\Controller\Admin; use Chamilo\CoreBundle\Controller\BaseController; -use Chamilo\CoreBundle\Entity\AccessUrl; use Chamilo\CoreBundle\Traits\ControllerTrait; use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted; use Symfony\Component\Form\Extension\Core\Type\SubmitType; @@ -106,9 +105,7 @@ class SettingsController extends BaseController public function updateSettingAction(Request $request, string $namespace): Response { $manager = $this->getSettingsManager(); - // @todo improve get the current url entity - $urlId = $request->getSession()->get('access_url_id'); - $url = $this->getDoctrine()->getRepository(AccessUrl::class)->find($urlId); + $url = $this->getAccessUrl(); $manager->setUrl($url); $schemaAlias = $manager->convertNameSpaceToService($namespace); $searchForm = $this->getSearchForm(); @@ -188,12 +185,10 @@ class SettingsController extends BaseController */ #[IsGranted('ROLE_ADMIN')] #[Route('/settings_sync', name: 'admin_settings')] - public function syncSettings(Request $request): Response + public function syncSettings(): Response { $manager = $this->getSettingsManager(); - // @todo improve get the current url entity - $urlId = $request->getSession()->get('access_url_id'); - $url = $this->getDoctrine()->getRepository(AccessUrl::class)->find($urlId); + $url = $this->getAccessUrl(); $manager->setUrl($url); $manager->installSchemas($url); diff --git a/src/CoreBundle/DataFixtures/PageFixtures.php b/src/CoreBundle/DataFixtures/PageFixtures.php new file mode 100644 index 0000000000..75261186b5 --- /dev/null +++ b/src/CoreBundle/DataFixtures/PageFixtures.php @@ -0,0 +1,28 @@ +getReference(AccessUserFixtures::ADMIN_USER_REFERENCE); + $category = (new PageCategory()) + ->setTitle('home') + ->setType('grid') + ->setCreator($admin) + ; + $manager->persist($category); + $manager->flush(); + } +} diff --git a/src/CoreBundle/DataProvider/Extension/PageExtension.php b/src/CoreBundle/DataProvider/Extension/PageExtension.php new file mode 100644 index 0000000000..605e7bd56f --- /dev/null +++ b/src/CoreBundle/DataProvider/Extension/PageExtension.php @@ -0,0 +1,66 @@ +security = $security; + $this->requestStack = $request; + } + + public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null): void + { + $this->addWhere($queryBuilder, $resourceClass); + } + + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []): void + { + //$this->addWhere($queryBuilder, $resourceClass); + } + + private function addWhere(QueryBuilder $qb, string $resourceClass): void + { + if (Page::class !== $resourceClass) { + return; + } + + $alias = $qb->getRootAliases()[0]; + + $request = $this->requestStack->getCurrentRequest(); + $urlId = $request->getSession()->get('access_url_id'); + + // Url filter by default. + $qb + ->andWhere("$alias.url = :url") + ->setParameter('url', $urlId) + ; + + $locale = $request->query->get('locale'); + + // By default, load the locale elements. + if (empty($locale)) { + $qb + ->andWhere("$alias.locale = :locale") + ->setParameter('locale', $request->getLocale()) + ; + } + } +} diff --git a/src/CoreBundle/Entity/Page.php b/src/CoreBundle/Entity/Page.php new file mode 100644 index 0000000000..8fec3e5d01 --- /dev/null +++ b/src/CoreBundle/Entity/Page.php @@ -0,0 +1,255 @@ + [ + 'security' => "is_granted('ROLE_USER')", + ], + 'post' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + ], + itemOperations: [ + 'get' => [ + 'security' => "is_granted('ROLE_USER')", + ], + 'put' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + 'delete' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + ], + denormalizationContext: [ + 'groups' => ['page:write'], + ], + normalizationContext: [ + 'groups' => ['page:read'], + ], +)] + +#[ApiFilter(SearchFilter::class, properties: [ + 'locale' => 'exact', + 'url' => 'exact', + 'category' => 'exact', + 'category.title' => 'partial', +])] +class Page +{ + use TimestampableTypedEntity; + + /** + * @ORM\Column(name="id", type="bigint") + * @ORM\Id + * @ORM\GeneratedValue() + */ + protected int $id; + + /** + * @ORM\Column(name="title", type="string", length=255) + */ + #[Assert\NotBlank] + #[Groups(['page:read', 'page:write'])] + protected string $title; + + /** + * @ORM\Column(name="content", type="text") + */ + #[Groups(['page:read', 'page:write'])] + #[Assert\NotBlank] + protected string $content; + + /** + * @Gedmo\Slug( + * fields={"title"}, + * updatable=true, + * unique=true, + * ) + * @ORM\Column(name="slug", type="string", length=255) + */ + protected string $slug; + + /** + * @ORM\Column(name="enabled", type="boolean", nullable=false) + */ + #[Groups(['page:read', 'page:write'])] + protected bool $enabled; + + /** + * @Gedmo\SortablePosition + * @ORM\Column(name="position", type="integer") + */ + #[Groups(['page:read', 'page:write'])] + protected int $position; + + /** + * @ORM\Column(name="locale", type="string", length=10) + */ + #[Groups(['page:read', 'page:write'])] + protected string $locale; + + /** + * @ORM\ManyToOne(targetEntity="AccessUrl", cascade={"persist"}) + * @ORM\JoinColumn(name="access_url_id", referencedColumnName="id") + */ + #[Assert\NotNull] + #[Groups(['page:read', 'page:write'])] + protected AccessUrl $url; + + /** + * @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\User") + * @ORM\JoinColumn(name="creator_id", referencedColumnName="id", nullable=true, onDelete="CASCADE") + */ + #[Assert\NotNull] + #[Groups(['page:read', 'page:write'])] + protected User $creator; + + /** + * @Gedmo\SortableGroup + * @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\PageCategory", inversedBy="pages") + * @ORM\JoinColumn(name="category_id", referencedColumnName="id", onDelete="SET NULL") + */ + #[Groups(['page:read', 'page:write'])] + protected ?PageCategory $category = null; + + public function __construct() + { + $this->enabled = false; + } + + public function getId(): int + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getContent(): string + { + return $this->content; + } + + public function setContent(string $content): self + { + $this->content = $content; + + return $this; + } + + public function getSlug(): string + { + return $this->slug; + } + + public function setSlug(string $slug): self + { + $this->slug = $slug; + + return $this; + } + + public function isEnabled(): bool + { + return $this->enabled; + } + + public function setEnabled(bool $enabled): self + { + $this->enabled = $enabled; + + return $this; + } + + public function getLocale(): string + { + return $this->locale; + } + + public function setLocale(string $locale): self + { + $this->locale = $locale; + + return $this; + } + + public function getUrl(): AccessUrl + { + return $this->url; + } + + public function setUrl(AccessUrl $url): self + { + $this->url = $url; + + return $this; + } + + public function getCreator(): User + { + return $this->creator; + } + + public function setCreator(User $creator): self + { + $this->creator = $creator; + + return $this; + } + + public function getPosition(): int + { + return $this->position; + } + + public function setPosition(int $position): self + { + $this->position = $position; + + return $this; + } + + public function getCategory(): ?PageCategory + { + return $this->category; + } + + public function setCategory(PageCategory $category): self + { + $this->category = $category; + + return $this; + } +} diff --git a/src/CoreBundle/Entity/PageCategory.php b/src/CoreBundle/Entity/PageCategory.php new file mode 100644 index 0000000000..f6344c9fe4 --- /dev/null +++ b/src/CoreBundle/Entity/PageCategory.php @@ -0,0 +1,148 @@ + [ + 'security' => "is_granted('ROLE_USER')", + ], + 'post' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + ], + itemOperations: [ + 'get' => [ + 'security' => "is_granted('ROLE_USER')", + ], + 'put' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + 'delete' => [ + 'security' => "is_granted('ROLE_ADMIN')", + ], + ], + denormalizationContext: [ + 'groups' => ['page_category:write'], + ], + normalizationContext: [ + 'groups' => ['page_category:read'], + ], +)] +class PageCategory +{ + use TimestampableTypedEntity; + + /** + * @ORM\Column(name="id", type="bigint") + * @ORM\Id + * @ORM\GeneratedValue() + */ + #[Groups(['page_category:read', 'page_category:write'])] + protected ?int $id = null; + + /** + * @ORM\Column(name="title", type="string", length=255) + */ + #[Assert\NotBlank] + #[Groups(['page_category:read', 'page_category:write'])] + protected string $title; + + /** + * @ORM\ManyToOne(targetEntity="Chamilo\CoreBundle\Entity\User") + * @ORM\JoinColumn(name="creator_id", referencedColumnName="id", nullable=true, onDelete="CASCADE") + */ + #[Assert\NotNull] + protected User $creator; + + /** + * @ORM\Column(name="type", type="string") + */ + #[Groups(['page_category:read', 'page_category:write', 'page:read'])] + #[Assert\NotBlank] + protected string $type; + + /** + * @var Collection|Page[] + * @ORM\OneToMany(targetEntity="Chamilo\CoreBundle\Entity\Page", mappedBy="category", cascade={"persist"}) + */ + protected Collection $pages; + + public function __construct() + { + $this->pages = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } + + public function getTitle(): string + { + return $this->title; + } + + public function setTitle(string $title): self + { + $this->title = $title; + + return $this; + } + + public function getCreator(): User + { + return $this->creator; + } + + public function setCreator(User $creator): self + { + $this->creator = $creator; + + return $this; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + public function getPages() + { + return $this->pages; + } + + public function setPages($pages): self + { + $this->pages = $pages; + + return $this; + } +} diff --git a/src/CoreBundle/Repository/PageRepository.php b/src/CoreBundle/Repository/PageRepository.php new file mode 100644 index 0000000000..ec3272c536 --- /dev/null +++ b/src/CoreBundle/Repository/PageRepository.php @@ -0,0 +1,33 @@ +getEntityManager()->persist($page); + $this->getEntityManager()->flush(); + } + + public function delete(Page $page = null): void + { + if (null !== $page) { + $this->getEntityManager()->remove($page); + $this->getEntityManager()->flush(); + } + } +} diff --git a/tests/ChamiloTestTrait.php b/tests/ChamiloTestTrait.php index 598f3f90ca..c66d61ed39 100644 --- a/tests/ChamiloTestTrait.php +++ b/tests/ChamiloTestTrait.php @@ -126,6 +126,11 @@ trait ChamiloTestTrait return $repo->findByUsername($username); } + public function getAdmin(): User + { + return $this->getUser('admin'); + } + public function getCourse($courseId): ?Course { $repo = static::getContainer()->get(CourseRepository::class); diff --git a/tests/CoreBundle/Repository/CourseCategoryRepositoryTest.php b/tests/CoreBundle/Repository/CourseCategoryRepositoryTest.php index ea76bb5765..a9267cc1c2 100644 --- a/tests/CoreBundle/Repository/CourseCategoryRepositoryTest.php +++ b/tests/CoreBundle/Repository/CourseCategoryRepositoryTest.php @@ -20,8 +20,6 @@ class CourseCategoryRepositoryTest extends AbstractApiTest public function testCreate(): void { - self::bootKernel(); - $em = $this->getEntityManager(); $repo = self::getContainer()->get(CourseCategoryRepository::class); $defaultCount = $repo->count([]); @@ -44,8 +42,6 @@ class CourseCategoryRepositoryTest extends AbstractApiTest public function testCreateWithParent(): void { - self::bootKernel(); - $em = $this->getEntityManager(); $repo = self::getContainer()->get(CourseCategoryRepository::class); $defaultCount = $repo->count([]); @@ -71,8 +67,6 @@ class CourseCategoryRepositoryTest extends AbstractApiTest public function testCreateWithAsset(): void { - self::bootKernel(); - $em = $this->getEntityManager(); /** @var CourseCategoryRepository $repoCourseCategory */ @@ -164,8 +158,6 @@ class CourseCategoryRepositoryTest extends AbstractApiTest public function testEditAndDeleteAsset(): void { - self::bootKernel(); - $em = $this->getEntityManager(); $repoCourseCategory = self::getContainer()->get(CourseCategoryRepository::class); @@ -209,13 +201,13 @@ class CourseCategoryRepositoryTest extends AbstractApiTest $categories = $repoCourseCategory->findAllInAccessUrl($urlId); - $this->assertSame(3, \count($categories)); + $this->assertCount(3, $categories); $categories = $repoCourseCategory->findAllInAccessUrl($urlId, false); - $this->assertSame(3, \count($categories)); + $this->assertCount(3, $categories); $categories = $repoCourseCategory->findAllInAccessUrl($urlId, false, 99); - $this->assertSame(0, \count($categories)); + $this->assertCount(0, $categories); } public function testGetCategoriesByCourseIdAndAccessUrlId(): void @@ -245,9 +237,9 @@ class CourseCategoryRepositoryTest extends AbstractApiTest $em->flush(); $categories = $repoCourseCategory->getCategoriesByCourseIdAndAccessUrlId($urlId, $course->getId()); - $this->assertSame(1, \count($categories)); + $this->assertCount(1, $categories); $categories = $repoCourseCategory->getCategoriesByCourseIdAndAccessUrlId($urlId, $course->getId(), true); - $this->assertSame(1, \count($categories)); + $this->assertCount(1, $categories); } } diff --git a/tests/CoreBundle/Repository/PageRepositoryTest.php b/tests/CoreBundle/Repository/PageRepositoryTest.php new file mode 100644 index 0000000000..843e709499 --- /dev/null +++ b/tests/CoreBundle/Repository/PageRepositoryTest.php @@ -0,0 +1,273 @@ +getEntityManager(); + $pageRepo = self::getContainer()->get(PageRepository::class); + + $user = $this->getAdmin(); + $url = $this->getAccessUrl(); + + $category = (new PageCategory()) + ->setCreator($user) + ->setTitle('category1') + ->setType('simple') + ->setCreatedAt(new DateTime()) + ->setUpdatedAt(new DateTime()) + ; + $this->assertHasNoEntityViolations($category); + $em->persist($category); + + $page = (new Page()) + ->setTitle('page1') + ->setContent('page1 content') + ->setCreator($user) + ->setUrl($url) + ->setPosition(0) + ->setCategory($category) + ->setSlug('english') + ->setLocale('en') + ->setEnabled(true) + ->setCreatedAt(new DateTime()) + ->setUpdatedAt(new DateTime()) + ; + $this->assertHasNoEntityViolations($page); + $em->persist($page); + $em->flush(); + + $this->assertSame(0, $page->getPosition()); + $this->assertSame(1, $pageRepo->count([])); + + $category2 = (new PageCategory()) + ->setCreator($user) + ->setTitle('category2') + ->setType('simple') + ->setCreatedAt(new DateTime()) + ->setUpdatedAt(new DateTime()) + ; + + $this->assertHasNoEntityViolations($category2); + $em->persist($category2); + + $pageFrench = (new Page()) + ->setTitle("l'ĂȘtĂȘ") + ->setContent('french content') + ->setCreator($user) + ->setUrl($url) + ->setCategory($category2) + ->setLocale('fr') + ->setEnabled(true) + ->setCreatedAt(new DateTime()) + ->setUpdatedAt(new DateTime()) + ; + $this->assertHasNoEntityViolations($pageFrench); + $em->persist($pageFrench); + $em->flush(); + + $this->assertSame(0, $pageFrench->getPosition()); + $this->assertSame('fr', $pageFrench->getLocale()); + $this->assertSame('lete', $pageFrench->getSlug()); + $this->assertSame(2, $pageRepo->count([])); + + return $page; + } + + public function testAddAnotherPage(): void + { + $page = $this->testCreate(); + $em = $this->getEntityManager(); + $pageRepo = self::getContainer()->get(PageRepository::class); + + /** @var Page $page */ + $page = $pageRepo->find($page->getId()); + + $url = $this->getAccessUrl(); + $user = $this->getAdmin(); + + $anotherPage = (new Page()) + ->setTitle('page2') + ->setContent('page2 content') + ->setUrl($url) + ->setCreator($user) + ->setLocale('en') + ->setEnabled(true) + ->setCategory($page->getCategory()) + ; + $this->assertHasNoEntityViolations($anotherPage); + $em->persist($anotherPage); + $em->flush(); + + $this->assertSame(3, $pageRepo->count([])); + $this->assertSame(1, $anotherPage->getPosition()); + $this->assertNotNull($anotherPage->getCategory()); + } + + public function testUpdate(): void + { + $page = $this->testCreate(); + $pageRepo = self::getContainer()->get(PageRepository::class); + + $this->assertSame(2, $pageRepo->count([])); + + $page->setLocale('fr'); + $pageRepo->update($page); + + $this->assertSame('fr', $page->getLocale()); + $this->assertSame(2, $pageRepo->count([])); + } + + public function testDelete(): void + { + $page = $this->testCreate(); + $pageRepo = self::getContainer()->get(PageRepository::class); + $pageRepo->delete($page); + $this->assertSame(1, $pageRepo->count([])); + } + + public function testGetPages(): void + { + $this->testAddAnotherPage(); + + $token = $this->getUserToken([]); + $this->createClientWithCredentials($token)->request('GET', '/api/pages'); + $this->assertResponseIsSuccessful(); + + $response = $this->createClientWithCredentials($token)->request( + 'GET', + '/api/pages', + [ + 'query' => [ + 'locale' => 'en', + ], + ] + ); + $this->assertResponseIsSuccessful(); + + // Asserts that the returned content type is JSON-LD (the default) + $this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8'); + + // Asserts that the returned JSON is a superset of this one + $this->assertJsonContains([ + '@context' => '/api/contexts/Page', + '@id' => '/api/pages', + '@type' => 'hydra:Collection', + 'hydra:totalItems' => 2, + ]); + + $this->assertCount(2, $response->toArray()['hydra:member']); + $this->assertMatchesResourceCollectionJsonSchema(Page::class); + + $response = $this->createClientWithCredentials($token)->request( + 'GET', + '/api/pages', + [ + 'query' => [ + 'locale' => 'en', + 'category.title' => 'category1', + ], + ] + ); + $this->assertCount(2, $response->toArray()['hydra:member']); + $this->assertJsonContains([ + '@context' => '/api/contexts/Page', + '@id' => '/api/pages', + '@type' => 'hydra:Collection', + 'hydra:member' => [ + [ + '@type' => 'Page', + 'title' => 'page1', + ], + [ + '@type' => 'Page', + 'title' => 'page2', + ], + ], + ]); + + $response = $this->createClientWithCredentials($token)->request( + 'GET', + '/api/pages', + [ + 'query' => [ + 'locale' => 'fr', + ], + ] + ); + $this->assertCount(1, $response->toArray()['hydra:member']); + } + + public function testAddPage(): void + { + $user = $this->getAdmin(); + $url = $this->getAccessUrl(); + + $url = $this->findIriBy(AccessUrl::class, ['id' => $url->getId()]); + $token = $this->getUserToken([]); + $this->createClientWithCredentials($token)->request( + 'POST', + '/api/pages', + [ + 'json' => [ + 'creator' => $user->getIri(), + 'url' => $url, + 'locale' => 'en', + 'title' => 'my post', + 'content' => 'hello', + ], + ] + ); + $this->assertResponseStatusCodeSame(201); + } + + public function testGetPage(): void + { + $page = $this->testCreate(); + $iri = $this->findIriBy(Page::class, ['id' => $page->getId()]); + + $token = $this->getUserToken([]); + $this->createClientWithCredentials($token)->request('GET', $iri); + $this->assertResponseIsSuccessful(); + $this->assertJsonContains([ + '@id' => $iri, + '@type' => 'Page', + 'title' => 'english', + 'content' => 'english content', + '@context' => '/api/contexts/Page', + ]); + } + + public function testDeletePage(): void + { + $page = $this->testCreate(); + $iri = $this->findIriBy(Page::class, ['id' => $page->getId()]); + + $token = $this->getUserToken([]); + $this->createClientWithCredentials($token)->request( + 'DELETE', + $iri + ); + + $this->assertResponseStatusCodeSame(204); + + $iri = $this->findIriBy(Page::class, ['id' => $page->getId()]); + $this->assertNull($iri); + } +}