diff --git a/Domain/Exception/ArticleNotFoundException.php b/Domain/Exception/ArticleNotFoundException.php new file mode 100644 index 00000000..613b4388 --- /dev/null +++ b/Domain/Exception/ArticleNotFoundException.php @@ -0,0 +1,61 @@ + $filters + */ + private $filters; + + /** + * @param array $filters + */ + public function __construct(array $filters, int $code = 0, \Throwable $previous = null) + { + $this->model = ArticleInterface::class; + + $criteriaMessages = []; + foreach ($filters as $key => $value) { + if (\is_object($value)) { + $value = \get_debug_type($value); + } else { + $value = \json_encode($value); + } + + $criteriaMessages[] = \sprintf('"%s" %s', $key, $value); + } + + $message = \sprintf( + 'Model "%s" with %s not found', + $this->model, + \implode(' and ', $criteriaMessages) + ); + + parent::__construct($message, $code, $previous); + + $this->filters = $filters; + } + + public function getModel(): string + { + return $this->model; + } + + /** + * @return mixed[] + */ + public function getFilters(): array + { + return $this->filters; + } + +} diff --git a/Domain/Model/Article.php b/Domain/Model/Article.php index b79933ff..76da958a 100644 --- a/Domain/Model/Article.php +++ b/Domain/Model/Article.php @@ -27,17 +27,22 @@ class Article implements ArticleInterface /** * @var string */ - protected $id; + protected $uuid; public function __construct( - ?string $id = null + ?string $uuid = null ) { - $this->id = $id ?: Uuid::uuid4()->toString(); + $this->uuid = $uuid ?: Uuid::uuid4()->toString(); } public function getId(): string { - return $this->id; + return $this->uuid; + } + + public function getUuid(): string + { + return $this->uuid; } /** diff --git a/Domain/Model/ArticleInterface.php b/Domain/Model/ArticleInterface.php index bd4b0eb5..43105e40 100644 --- a/Domain/Model/ArticleInterface.php +++ b/Domain/Model/ArticleInterface.php @@ -16,11 +16,18 @@ /** * @experimental + * + * @method ArticleDimensionContentInterface createDimensionContent() see also */ interface ArticleInterface extends AuditableInterface, ContentRichEntityInterface { public const TEMPLATE_TYPE = 'article'; public const RESOURCE_KEY = 'article'; + /** + * @internal + */ public function getId(): string; + + public function getUuid(): string; } diff --git a/Domain/Repository/ArticleRepositoryInterface.php b/Domain/Repository/ArticleRepositoryInterface.php new file mode 100644 index 00000000..b2a8311b --- /dev/null +++ b/Domain/Repository/ArticleRepositoryInterface.php @@ -0,0 +1,103 @@ +, + * }|array $selects + * + * @throws ArticleNotFoundException + */ + public function getOneBy(array $filters, array $selects = []): ArticleInterface; + + /** + * @param array{ + * uuid?: string, + * uuids?: string[], + * locale?: string, + * stage?: string, + * } $filters + * + * @param array{ + * article_admin?: bool, + * article_website?: bool, + * with-article-content?: bool|array, + * }|array $selects + */ + public function findOneBy(array $filters, array $selects = []): ?ArticleInterface; + + /** + * @param array{ + * uuid?: string, + * uuids?: string[], + * locale?: string, + * stage?: string, + * categoryIds?: int[], + * categoryKeys?: string[], + * categoryOperator?: 'AND'|'OR', + * tagIds?: int[], + * tagNames?: string[], + * tagOperator?: 'AND'|'OR', + * templateKeys?: string[], + * page?: int, + * limit?: int, + * } $filters + * + * @param array{ + * id?: 'asc'|'desc', + * title?: 'asc'|'desc', + * } $sortBy + * + * @param array{ + * article_admin?: bool, + * article_website?: bool, + * with-article-content?: bool|array, + * }|array $selects + * + * @return iterable + */ + public function findBy(array $filters = [], array $sortBy = [], array $selects = []): iterable; + + /** + * @param array{ + * uuid?: string, + * uuids?: string[], + * locale?: string, + * stage?: string, + * categoryIds?: int[], + * categoryKeys?: string[], + * categoryOperator?: 'AND'|'OR', + * tagIds?: int[], + * tagNames?: string[], + * tagOperator?: 'AND'|'OR', + * templateKeys?: string[], + * } $filters + */ + public function countBy(array $filters = []): int; + + public function add(ArticleInterface $article): void; + + public function remove(ArticleInterface $article): void; +} diff --git a/Infrastructure/Doctrine/Repository/ArticleRepository.php b/Infrastructure/Doctrine/Repository/ArticleRepository.php new file mode 100644 index 00000000..dee28519 --- /dev/null +++ b/Infrastructure/Doctrine/Repository/ArticleRepository.php @@ -0,0 +1,269 @@ + [ + self::SELECT_ARTICLE_CONTENT => [ + DimensionContentQueryEnhancer::GROUP_SELECT_CONTENT_ADMIN => true, + ], + ], + self::GROUP_SELECT_ARTICLE_WEBSITE => [ + self::SELECT_ARTICLE_CONTENT => [ + DimensionContentQueryEnhancer::GROUP_SELECT_CONTENT_WEBSITE => true, + ], + ], + ]; + + /** + * @var EntityManagerInterface + */ + private $entityManager; + + /** + * @var EntityRepository + */ + protected $entityRepository; + + /** + * @var EntityRepository + */ + protected $entityDimensionContentRepository; + + /** + * @var DimensionContentQueryEnhancer + */ + protected $dimensionContentQueryEnhancer; + + /** + * @var class-string $articleClassName + */ + protected $articleClassName; + + /** + * @var class-string $articleDimensionContentClassName + */ + protected $articleDimensionContentClassName; + + public function __construct( + EntityManagerInterface $entityManager, + DimensionContentQueryEnhancer $dimensionContentQueryEnhancer + ) { + $this->entityRepository = $entityManager->getRepository(ArticleInterface::class); + $this->entityDimensionContentRepository = $entityManager->getRepository(ArticleDimensionContentInterface::class); + $this->entityManager = $entityManager; + $this->dimensionContentQueryEnhancer = $dimensionContentQueryEnhancer; + $this->articleClassName = $this->entityRepository->getClassName(); + $this->articleDimensionContentClassName = $this->entityDimensionContentRepository->getClassName(); + } + + public function createNew(?string $uuid = null): ArticleInterface + { + $className = $this->articleClassName; + + return new $className($uuid); + } + + public function getOneBy(array $filters, array $selects = []): ArticleInterface + { + $queryBuilder = $this->createQueryBuilder($filters, [], $selects); + + try { + /** @var ArticleInterface $article */ + $article = $queryBuilder->getQuery()->getSingleResult(); + } catch (NoResultException $e) { + throw new ArticleNotFoundException($filters, 0, $e); + } + + return $article; + } + + public function findOneBy(array $filters, array $selects = []): ?ArticleInterface + { + $queryBuilder = $this->createQueryBuilder($filters, [], $selects); + + try { + /** @var ArticleInterface $article */ + $article = $queryBuilder->getQuery()->getSingleResult(); + } catch (NoResultException $e) { + return null; + } + + return $article; + } + + public function countBy(array $filters = []): int + { + // The countBy method will ignore any page and limit parameters + // for better developer experience we will strip them away here + // instead of that the developer need to take that into account + // in there call of the countBy method. + unset($filters['page']); // @phpstan-ignore-line + unset($filters['limit']); + + $queryBuilder = $this->createQueryBuilder($filters); + + $queryBuilder->select('COUNT(DISTINCT article.uuid)'); + + return (int) $queryBuilder->getQuery()->getSingleScalarResult(); + } + + /** + * @return \Generator + */ + public function findBy(array $filters = [], array $sortBy = [], array $selects = []): \Generator + { + $queryBuilder = $this->createQueryBuilder($filters, $sortBy, $selects); + + /** @var iterable $articles */ + $articles = $queryBuilder->getQuery()->getResult(); + + foreach ($articles as $article) { + yield $article; + } + } + + public function add(ArticleInterface $article): void + { + $this->entityManager->persist($article); + } + + public function remove(ArticleInterface $article): void + { + $this->entityManager->remove($article); + } + + /** + * @param array{ + * uuid?: string, + * uuids?: string[], + * locale?: string|null, + * stage?: string|null, + * categoryIds?: int[], + * categoryKeys?: string[], + * categoryOperator?: 'AND'|'OR', + * tagIds?: int[], + * tagNames?: string[], + * tagOperator?: 'AND'|'OR', + * templateKeys?: string[], + * page?: int, + * limit?: int, + * } $filters + * @param array{ + * uuid?: 'asc'|'desc', + * title?: 'asc'|'desc', + * } $sortBy + * @param array{ + * article_admin?: bool, + * article_website?: bool, + * with-article-content?: bool|array, + * }|array $selects + */ + private function createQueryBuilder(array $filters, array $sortBy = [], array $selects = []): QueryBuilder + { + foreach ($selects as $selectGroup => $value) { + if (!$value) { + continue; + } + + if (isset(self::SELECTS[$selectGroup])) { + $selects = \array_replace_recursive($selects, self::SELECTS[$selectGroup]); + } + } + + $queryBuilder = $this->entityRepository->createQueryBuilder('article'); + + $uuid = $filters['uuid'] ?? null; + if ($uuid !== null) { + Assert::string($uuid); + $queryBuilder->andWhere('article.uuid = :uuid') + ->setParameter('uuid', $uuid); + } + + $uuids = $filters['uuids'] ?? null; + if ($uuids !== null) { + Assert::isArray($uuids); + $queryBuilder->andWhere('article.uuid IN(:uuids)') + ->setParameter('uuids', $uuids); + } + + $limit = $filters['limit'] ?? null; + if ($limit !== null) { + Assert::integer($limit); + $queryBuilder->setMaxResults($limit); + } + + $page = $filters['page'] ?? null; + if ($page !== null) { + Assert::notNull($limit); + Assert::integer($page); + $offset = (int) ($limit * ($page - 1)); + $queryBuilder->setFirstResult($offset); + } + + if (\array_key_exists('locale', $filters) // should also work with locale = null + && \array_key_exists('stage', $filters)) { + $this->dimensionContentQueryEnhancer->addFilters( + $queryBuilder, + 'example', + ArticleDimensionContentInterface::class, + $filters + ); + } + + // TODO add sortBys + + // selects + if ($selects[self::SELECT_ARTICLE_CONTENT] ?? null) { + /** @var array $contentSelects */ + $contentSelects = $selects[self::SELECT_ARTICLE_CONTENT] ?? []; + + $queryBuilder->leftJoin( + 'article.dimensionContents', + 'dimensionContent' + ); + + $this->dimensionContentQueryEnhancer->addSelects( + $queryBuilder, + ExampleDimensionContent::class, + $filters, + $contentSelects + ); + } + + return $queryBuilder; + } +} diff --git a/Resources/config/doctrine/Article/Article.orm.xml b/Resources/config/doctrine/Article/Article.orm.xml index 1a28b7de..c802cb18 100644 --- a/Resources/config/doctrine/Article/Article.orm.xml +++ b/Resources/config/doctrine/Article/Article.orm.xml @@ -7,7 +7,7 @@ table="ar_articles" repository-class="Sulu\Component\Persistence\Repository\ORM\EntityRepository" > - + diff --git a/Resources/config/doctrine/Article/ArticleDimensionContent.orm.xml b/Resources/config/doctrine/Article/ArticleDimensionContent.orm.xml index 6b30ffc7..5aa91644 100644 --- a/Resources/config/doctrine/Article/ArticleDimensionContent.orm.xml +++ b/Resources/config/doctrine/Article/ArticleDimensionContent.orm.xml @@ -12,7 +12,7 @@ - + diff --git a/Resources/config/experimental.xml b/Resources/config/experimental.xml index 7ac80f80..a9f63d47 100644 --- a/Resources/config/experimental.xml +++ b/Resources/config/experimental.xml @@ -11,6 +11,7 @@ --> + @@ -22,5 +23,19 @@ + + + + + + + + + + + + + + diff --git a/Resources/config/routing_experimental_admin_api.yaml b/Resources/config/routing_experimental_admin_api.yaml new file mode 100644 index 00000000..2050f172 --- /dev/null +++ b/Resources/config/routing_experimental_admin_api.yaml @@ -0,0 +1,4 @@ +sulu_article.admin_article_api: + type: rest + resource: sulu_article.admin_article_controller + name_prefix: sulu_article. diff --git a/Tests/Application/Kernel.php b/Tests/Application/Kernel.php index 1046ef58..1493f0ca 100644 --- a/Tests/Application/Kernel.php +++ b/Tests/Application/Kernel.php @@ -77,22 +77,26 @@ public function registerContainerConfiguration(LoaderInterface $loader) $loader->load(__DIR__ . '/config/config.yml'); $loader->load(__DIR__ . '/config/config_' . $this->config . '.yml'); - $type = 'default'; - if (getenv('ARTICLE_TEST_CASE')) { - $type = getenv('ARTICLE_TEST_CASE'); - } + if ($this->config === 'phpcr_storage') { + $type = 'default'; + if (getenv('ARTICLE_TEST_CASE')) { + $type = getenv('ARTICLE_TEST_CASE'); + } - $loader->load(__DIR__ . '/config/config_' . $type . '.yml'); + $loader->load(__DIR__ . '/config/config_' . $type . '.yml'); + } } public function process(ContainerBuilder $container) { - // Make some services which were inlined in optimization - $container->getDefinition('sulu_article.content.page_tree_data_provider') - ->setPublic(true); + if ($this->config === 'phpcr_storage') { + // Make some services which were inlined in optimization + $container->getDefinition('sulu_article.content.page_tree_data_provider') + ->setPublic(true); - $container->getDefinition('sulu_article.elastic_search.article_indexer') - ->setPublic(true); + $container->getDefinition('sulu_article.elastic_search.article_indexer') + ->setPublic(true); + } } protected function getKernelParameters() @@ -104,4 +108,14 @@ protected function getKernelParameters() return $parameters; } + + public function getCacheDir() + { + return parent::getCacheDir() . '/' . $this->config; + } + + public function getCommonCacheDir() + { + return parent::getCommonCacheDir() . '/' . $this->config; + } } diff --git a/Tests/Functional/Infrastructure/Doctrine/Repository/ArticleRepositoryTest.php b/Tests/Functional/Infrastructure/Doctrine/Repository/ArticleRepositoryTest.php new file mode 100644 index 00000000..d29cdd92 --- /dev/null +++ b/Tests/Functional/Infrastructure/Doctrine/Repository/ArticleRepositoryTest.php @@ -0,0 +1,315 @@ + 'test_experimental_storage']); + $this->articleRepository = static::getContainer()->get('sulu_article.article_repository'); + } + + public function testFindOneByNotExist(): void + { + $uuid = Uuid::uuid4()->toString(); + $this->assertNull($this->articleRepository->findOneBy(['uuid' => $uuid])); + } + + public function testGetOneByNotExist(): void + { + $this->expectException(ArticleNotFoundException::class); + + $uuid = Uuid::uuid4()->toString(); + $this->articleRepository->getOneBy(['uuid' => $uuid]); + } + + public function testFindByNotExist(): void + { + $uuid = Uuid::uuid4()->toString(); + $articles = iterator_to_array($this->articleRepository->findBy(['uuids' => [$uuid]])); + $this->assertCount(0, $articles); + } + + public function testAdd(): void + { + $uuid = Uuid::uuid4()->toString(); + $article = new Article($uuid); + + $this->articleRepository->add($article); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $article = $this->articleRepository->getOneBy(['uuid' => $uuid]); + $this->assertSame($uuid, $article->getUuid()); + } + + public function testRemove(): void + { + $uuid = Uuid::uuid4()->toString(); + $article = new Article($uuid); + + $this->articleRepository->add($article); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $article = $this->articleRepository->getOneBy(['uuid' => $uuid]); + $this->articleRepository->remove($article); + static::getEntityManager()->flush(); + + $this->assertNull($this->articleRepository->findOneBy(['uuid' => $uuid])); + } + + public function testCountBy(): void + { + static::purgeDatabase(); + + $this->articleRepository->add(new Article()); + $this->articleRepository->add(new Article()); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $this->assertSame(2, $this->articleRepository->countBy()); + } + + public function testFindByUuids(): void + { + static::purgeDatabase(); + + $uuid = Uuid::uuid4()->toString(); + $uuid2 = Uuid::uuid4()->toString(); + $uuid3 = Uuid::uuid4()->toString(); + $article = new Article($uuid); + $article2 = new Article($uuid2); + $article3 = new Article($uuid3); + + $this->articleRepository->add($article); + $this->articleRepository->add($article2); + $this->articleRepository->add($article3); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $articles = iterator_to_array($this->articleRepository->findBy(['uuids' => [$uuid, $uuid3]])); + + $this->assertCount(2, $articles); + } + + public function testFindByLimitAndPage(): void + { + static::purgeDatabase(); + + $this->articleRepository->add(new Article()); + $this->articleRepository->add(new Article()); + $this->articleRepository->add(new Article()); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $articles = \iterator_to_array($this->articleRepository->findBy(['limit' => 2, 'page' => 2])); + $this->assertCount(1, $articles); + } + + public function testFindByLocaleAndStage(): void + { + static::purgeDatabase(); + + $article = static::createArticle(); + $article2 = static::createArticle(); + $article3 = static::createArticle(); + static::createArticleContent($article, ['title' => 'Article A']); + static::createArticleContent($article, ['title' => 'Article A', 'stage' => 'live']); + static::createArticleContent($article2, ['title' => 'Article B']); + static::createArticleContent($article3, ['title' => 'Article C']); + static::createArticleContent($article3, ['title' => 'Article C', 'stage' => 'live']); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $articles = \iterator_to_array($this->articleRepository->findBy(['locale' => 'en', 'stage' => 'live'])); + $this->assertCount(2, $articles); + } + + public function testCategoryFilters(): void + { + static::purgeDatabase(); + + $categoryA = static::createCategory(['key' => 'a']); + $categoryB = static::createCategory(['key' => 'b']); + + $article = static::createArticle(); + $article2 = static::createArticle(); + $article3 = static::createArticle(); + static::createArticleContent($article, ['title' => 'Article A', 'excerptCategories' => [$categoryA]]); + static::createArticleContent($article2, ['title' => 'Article B']); + static::createArticleContent($article3, ['title' => 'Article C', 'excerptCategories' => [$categoryA, $categoryB]]); + static::getEntityManager()->flush(); + $categoryAId = $categoryA->getId(); + $categoryBId = $categoryB->getId(); + static::getEntityManager()->clear(); + + $this->assertCount(2, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryKeys' => ['a', 'b'], + ]))); + + $this->assertSame(2, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryKeys' => ['a', 'b'], + ])); + + $this->assertCount(1, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryKeys' => ['a', 'b'], + 'categoryOperator' => 'AND', + ]))); + + $this->assertSame(1, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryKeys' => ['a', 'b'], + 'categoryOperator' => 'AND', + ])); + + $this->assertCount(2, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryIds' => [$categoryAId, $categoryBId], + ]))); + + $this->assertSame(2, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryIds' => [$categoryAId, $categoryBId], + ])); + + $this->assertCount(1, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryIds' => [$categoryAId, $categoryBId], + 'categoryOperator' => 'AND', + ]))); + + $this->assertSame(1, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'categoryIds' => [$categoryAId, $categoryBId], + 'categoryOperator' => 'AND', + ])); + } + + public function testTagFilters(): void + { + static::purgeDatabase(); + + $tagA = static::createTag(['name' => 'a']); + $tagB = static::createTag(['name' => 'b']); + + $article = static::createArticle(); + $article2 = static::createArticle(); + $article3 = static::createArticle(); + static::createArticleContent($article, ['title' => 'Article A', 'excerptTags' => [$tagA]]); + static::createArticleContent($article2, ['title' => 'Article B']); + static::createArticleContent($article3, ['title' => 'Article C', 'excerptTags' => [$tagA, $tagB]]); + static::getEntityManager()->flush(); + $tagAId = $tagA->getId(); + $tagBId = $tagB->getId(); + static::getEntityManager()->clear(); + + $this->assertCount(2, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagNames' => ['a', 'b'], + ]))); + + $this->assertSame(2, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagNames' => ['a', 'b'], + ])); + + $this->assertCount(1, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagNames' => ['a', 'b'], + 'tagOperator' => 'AND', + ]))); + + $this->assertSame(1, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagNames' => ['a', 'b'], + 'tagOperator' => 'AND', + ])); + + $this->assertCount(2, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagIds' => [$tagAId, $tagBId], + ]))); + + $this->assertSame(2, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagIds' => [$tagAId, $tagBId], + ])); + + $this->assertCount(1, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagIds' => [$tagAId, $tagBId], + 'tagOperator' => 'AND', + ]))); + + $this->assertSame(1, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'tagIds' => [$tagAId, $tagBId], + 'tagOperator' => 'AND', + ])); + } + + public function testFilterTemplateKeys(): void + { + static::purgeDatabase(); + + $article = static::createArticle(); + $article2 = static::createArticle(); + $article3 = static::createArticle(); + static::createArticleContent($article, ['title' => 'Article A', 'templateKey' => 'a']); + static::createArticleContent($article2, ['title' => 'Article B', 'templateKey' => 'b']); + static::createArticleContent($article3, ['title' => 'Article C', 'templateKey' => 'c']); + static::getEntityManager()->flush(); + static::getEntityManager()->clear(); + + $this->assertCount(2, iterator_to_array($this->articleRepository->findBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'templateKeys' => ['a', 'c'], + ]))); + + $this->assertSame(2, $this->articleRepository->countBy([ + 'locale' => 'en', + 'stage' => 'draft', + 'templateKeys' => ['a', 'c'], + ])); + } +} diff --git a/Tests/Functional/Traits/CreateArticleTrait.php b/Tests/Functional/Traits/CreateArticleTrait.php new file mode 100644 index 00000000..af29939c --- /dev/null +++ b/Tests/Functional/Traits/CreateArticleTrait.php @@ -0,0 +1,67 @@ +get('sulu_article.article_repository'); + $article = $articleRepository->createNew($data['uuid'] ?? null); + + $articleRepository->add($article); + + return $article; + } + + /** + * @param array{ + * locale?: ?string, + * stage?: ?string, + * templateKey?: ?string, + * templateData?: mixed[], + * excerptCategories?: CategoryInterface[], + * excerptTags?: TagInterface[], + * } $data + */ + public function createArticleContent(ArticleInterface $article, array $data = []): void + { + $locale = $data['locale'] ?? 'en'; + $stage = $data['stage'] ?? DimensionContentInterface::STAGE_DRAFT; + + $unlocalizedDimensionContent = $article->createDimensionContent(); + $unlocalizedDimensionContent->setStage($stage); + $article->addDimensionContent($unlocalizedDimensionContent); + + $localizedDimensionContent = $article->createDimensionContent(); + $localizedDimensionContent->setLocale($locale); + $localizedDimensionContent->setStage($stage); + $localizedDimensionContent->setTitle($data['title'] ?? null); + + $templateKey = $data['templateKey'] ?? null; + if ($templateKey) { + $localizedDimensionContent->setTemplateKey($templateKey); + } + $localizedDimensionContent->setTemplateData($data['templateData'] ?? ['title' => '']); + $localizedDimensionContent->setExcerptCategories($data['excerptCategories'] ?? []); + $localizedDimensionContent->setExcerptTags($data['excerptTags'] ?? []); + + $article->addDimensionContent($localizedDimensionContent); + } + + protected abstract static function getEntityManager(): EntityManagerInterface; + + protected abstract static function getContainer(): ContainerInterface; +} diff --git a/Tests/Functional/Traits/CreateCategoryTrait.php b/Tests/Functional/Traits/CreateCategoryTrait.php new file mode 100644 index 00000000..e022a4a6 --- /dev/null +++ b/Tests/Functional/Traits/CreateCategoryTrait.php @@ -0,0 +1,51 @@ +get('sulu.repository.category'); + /** @var CategoryInterface $category */ + $category = $categoryRepository->createNew(); + $category->setKey($data['key'] ?? null); + $category->setDefaultLocale($data['default_locale'] ?? 'en'); + + static::getEntityManager()->persist($category); + + return $category; + } + + /** + * @param array{ + * title?: ?string, + * locale?: ?string, + * } $data + */ + public function createCategoryTranslation(CategoryInterface $category, array $data = []): CategoryTranslationInterface + { + $categoryTranslation = new CategoryTranslation(); + $categoryTranslation->setLocale($data['locale'] ?? 'en'); + $categoryTranslation->setTranslation($data['title'] ?? ''); + $category->addTranslation($categoryTranslation); + + return $categoryTranslation; + } + + protected abstract static function getEntityManager(): EntityManagerInterface; + + protected abstract static function getContainer(): ContainerInterface; +} diff --git a/Tests/Functional/Traits/CreateTagTrait.php b/Tests/Functional/Traits/CreateTagTrait.php new file mode 100644 index 00000000..ff5a24e2 --- /dev/null +++ b/Tests/Functional/Traits/CreateTagTrait.php @@ -0,0 +1,31 @@ +get('sulu.repository.tag'); + /** @var TagInterface $tag */ + $tag = $tagRepository->createNew(); + $tag->setName($data['name'] ?? ''); + + static::getEntityManager()->persist($tag); + + return $tag; + } + + protected abstract static function getEntityManager(): EntityManagerInterface; + + protected abstract static function getContainer(): ContainerInterface; +} diff --git a/UserInterface/Controller/Admin/ArticleController.php b/UserInterface/Controller/Admin/ArticleController.php new file mode 100644 index 00000000..8fcdbce6 --- /dev/null +++ b/UserInterface/Controller/Admin/ArticleController.php @@ -0,0 +1,71 @@ +articleRepository = $articleRepository; + $this->setViewHandler($viewhandler); + } + + public function cgetAction(Request $request): Response + { + return new Response(501); + } + + public function getAction(Request $request, string $uuid): Response + { + $locale = $request->query->get('locale', $request->getLocale()); + + $article = $this->articleRepository->getOneBy([ + 'uuid' => $uuid, + 'locale' => $locale, + 'stage' => DimensionContentInterface::STAGE_DRAFT, + ], [ + 'context' => 'article_admin', + ]); + + return $this->handleView( + $this->view($article, 200) + ->setContext((new Context())->setSerializeNull(null)->setGroups(['article_admin'])) + ); + } + + public function postAction(Request $request): Response + { + return new Response(501); + } + + public function putAction(Request $request, string $uuid): Response + { + return new Response(501); + } + + public function deleteAction(Request $request, string $uuid): Response + { + return new Response(501); + } +} diff --git a/composer.json b/composer.json index 05adb082..2cb3b773 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "phpstan/phpstan-symfony": "^0.12.3", "phpunit/phpunit": "^8.2", "sulu/automation-bundle": "^2.0@dev", - "sulu/content-bundle": "^0.6.1", + "sulu/content-bundle": "^0.x@dev", "symfony/browser-kit": "^4.3 || ^5.0", "symfony/dotenv": "^4.3 || ^5.0", "symfony/monolog-bundle": "^3.1", @@ -86,9 +86,9 @@ }, "scripts": { "bootstrap-test-environment": [ - "Tests/Application/bin/adminconsole doctrine:database:drop --if-exists --force --env test", - "Tests/Application/bin/adminconsole doctrine:database:create --env test", - "Tests/Application/bin/adminconsole doctrine:schema:update --force --env test", + "Tests/Application/bin/adminconsole doctrine:database:drop --if-exists --force --env test_experimental_storage", + "Tests/Application/bin/adminconsole doctrine:database:create --env test_experimental_storage", + "Tests/Application/bin/adminconsole doctrine:schema:update --force --env test_experimental_storage", "Tests/Application/bin/adminconsole ongr:es:index:create --manager=default --if-not-exists --env test", "Tests/Application/bin/adminconsole ongr:es:index:create --manager=live --if-not-exists --env test" ],