diff --git a/Content/ArticleDataProvider.php b/Content/ArticleDataProvider.php index 1664db222..d5d206adb 100644 --- a/Content/ArticleDataProvider.php +++ b/Content/ArticleDataProvider.php @@ -55,6 +55,11 @@ class ArticleDataProvider implements DataProviderInterface, DataProviderAliasInt */ private $referenceStore; + /** + * @var ArticleResourceItemFactory + */ + protected $articleResourceItemFactory; + /** * @var string */ @@ -70,6 +75,7 @@ class ArticleDataProvider implements DataProviderInterface, DataProviderAliasInt * @param DocumentManagerInterface $documentManager * @param LazyLoadingValueHolderFactory $proxyFactory * @param ReferenceStoreInterface $referenceStore + * @param ArticleResourceItemFactory $articleResourceItemFactory * @param string $articleDocumentClass * @param int $defaultLimit */ @@ -78,6 +84,7 @@ public function __construct( DocumentManagerInterface $documentManager, LazyLoadingValueHolderFactory $proxyFactory, ReferenceStoreInterface $referenceStore, + ArticleResourceItemFactory $articleResourceItemFactory, $articleDocumentClass, $defaultLimit ) { @@ -85,6 +92,7 @@ public function __construct( $this->documentManager = $documentManager; $this->proxyFactory = $proxyFactory; $this->referenceStore = $referenceStore; + $this->articleResourceItemFactory = $articleResourceItemFactory; $this->articleDocumentClass = $articleDocumentClass; $this->defaultLimit = $defaultLimit; } @@ -180,10 +188,7 @@ public function resolveResourceItems( $this->referenceStore->add($document->getUuid()); $uuids[] = $document->getUuid(); - $result[] = new ArticleResourceItem( - $document, - $this->getResource($document->getUuid(), $document->getLocale()) - ); + $result[] = $this->articleResourceItemFactory->createResourceItem($document); } return new DataProviderResult($result, $this->hasNextPage($queryResult, $limit, $page, $pageSize), $uuids); diff --git a/Content/ArticleResourceItem.php b/Content/ArticleResourceItem.php index 79f8660c9..e01f8cd0c 100644 --- a/Content/ArticleResourceItem.php +++ b/Content/ArticleResourceItem.php @@ -132,6 +132,16 @@ public function getPublished() return $this->article->getPublished(); } + /** + * Returns authored. + * + * @return \DateTime + */ + public function getAuthored() + { + return $this->article->getAuthored(); + } + /** * Returns excerpt. * diff --git a/Content/ArticleResourceItemFactory.php b/Content/ArticleResourceItemFactory.php new file mode 100644 index 000000000..8c8061a01 --- /dev/null +++ b/Content/ArticleResourceItemFactory.php @@ -0,0 +1,88 @@ +documentManager = $documentManager; + $this->proxyFactory = $proxyFactory; + } + + /** + * Creates and returns article source item with proxy document. + * + * @param ArticleViewDocumentInterface $articleViewDocument + * + * @return ArticleResourceItem + */ + public function createResourceItem(ArticleViewDocumentInterface $articleViewDocument) + { + return new ArticleResourceItem( + $articleViewDocument, + $this->getResource($articleViewDocument->getUuid(), $articleViewDocument->getLocale()) + ); + } + + /** + * Returns Proxy document for uuid. + * + * @param string $uuid + * @param string $locale + * + * @return object + */ + private function getResource($uuid, $locale) + { + return $this->proxyFactory->createProxy( + ArticleDocument::class, + function ( + &$wrappedObject, + LazyLoadingInterface $proxy, + $method, + array $parameters, + &$initializer + ) use ($uuid, $locale) { + $initializer = null; + $wrappedObject = $this->documentManager->find($uuid, $locale); + + return true; + } + ); + } +} diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index 2e8e5a32e..588e330c6 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -86,6 +86,17 @@ public function getConfigTreeBuilder() ->end() ->end() ->scalarNode('display_tab_all')->defaultTrue()->info("Display tab 'all' in list view")->end() + ->arrayNode('search_fields') + ->prototype('scalar')->end()->defaultValue([ + 'title', + 'excerpt.title', + 'excerpt.description', + 'excerpt.seo.title', + 'excerpt.seo.description', + 'excerpt.seo.keywords', + 'teaser_description', + ]) + ->end() ->end(); return $treeBuilder; diff --git a/DependencyInjection/SuluArticleExtension.php b/DependencyInjection/SuluArticleExtension.php index 5574e6be6..274f62c60 100644 --- a/DependencyInjection/SuluArticleExtension.php +++ b/DependencyInjection/SuluArticleExtension.php @@ -21,7 +21,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; use Symfony\Component\DependencyInjection\Loader; -use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\DependencyInjection\Extension; /** @@ -139,6 +138,7 @@ public function load(array $configs, ContainerBuilder $container) $container->setParameter('sulu_article.view_document.article.class', $config['documents']['article']['view']); $container->setParameter('sulu_article.display_tab_all', $config['display_tab_all']); $container->setParameter('sulu_article.smart_content.default_limit', $config['smart_content']['default_limit']); + $container->setParameter('sulu_article.search_fields', $config['search_fields']); $container->setParameter( 'sulu_article.content-type.article.template', diff --git a/Document/Repository/ArticleViewDocumentRepository.php b/Document/Repository/ArticleViewDocumentRepository.php new file mode 100644 index 000000000..b88ffc8ed --- /dev/null +++ b/Document/Repository/ArticleViewDocumentRepository.php @@ -0,0 +1,159 @@ +searchManager = $searchManager; + $this->articleDocumentClass = $articleDocumentClass; + $this->repository = $this->searchManager->getRepository($this->articleDocumentClass); + $this->searchFields = $searchFields; + } + + /** + * Finds recent articles for given parameters sorted by field `authored`. + * + * @param null|string $excludeUuid + * @param int $limit + * @param null|array $types + * @param null|string $locale + * + * @return DocumentIterator + */ + public function findRecent($excludeUuid = null, $limit = self::DEFAULT_LIMIT, array $types = null, $locale = null) + { + $search = $this->createSearch($limit, $types, $locale); + + if ($excludeUuid) { + $search->addQuery(new TermQuery('uuid', $excludeUuid), BoolQuery::MUST_NOT); + } + + $search->addSort(new FieldSort('authored', FieldSort::DESC)); + + return $this->repository->findDocuments($search); + } + + /** + * Finds similar articles for given `uuid` with given parameters. + * + * @param string $uuid + * @param int $limit + * @param null|array $types + * @param null|string $locale + * + * @return DocumentIterator + */ + public function findSimilar($uuid, $limit = self::DEFAULT_LIMIT, array $types = null, $locale = null) + { + $search = $this->createSearch($limit, $types, $locale); + + $search->addQuery( + new MoreLikeThisQuery( + null, + [ + 'fields' => $this->searchFields, + 'min_term_freq' => 1, + 'min_doc_freq' => 2, + 'ids' => [$this->getViewDocumentId($uuid, $locale)], + ] + ) + ); + + return $this->repository->findDocuments($search); + } + + /** + * Creates search with default queries (size, locale, types). + * + * @param int $limit + * @param null|array $types + * @param null|string $locale + * + * @return Search + */ + private function createSearch($limit, array $types = null, $locale = null) + { + $search = $this->repository->createSearch(); + + // set size + $search->setSize($limit); + + // filter by locale if provided + if ($locale) { + $search->addQuery(new TermQuery('locale', $locale), BoolQuery::FILTER); + } + + // filter by types if provided + if ($types) { + $typesQuery = new BoolQuery(); + foreach ($types as $type) { + $typesQuery->add(new TermQuery('type', $type), BoolQuery::SHOULD); + } + $search->addQuery($typesQuery); + } + + return $search; + } +} diff --git a/Exception/ArticleInRequestNotFoundException.php b/Exception/ArticleInRequestNotFoundException.php new file mode 100644 index 000000000..fe029a2fc --- /dev/null +++ b/Exception/ArticleInRequestNotFoundException.php @@ -0,0 +1,23 @@ + + %sulu_article.view_document.article.class% %sulu_article.smart_content.default_limit% @@ -230,6 +231,7 @@ + %sulu_article.view_document.article.class% %sulu_article.smart_content.default_limit% @@ -295,5 +297,32 @@ + + + + + + + + + + + %sulu_article.view_document.article.class% + %sulu_article.search_fields% + + + + + + + + + + + + diff --git a/Resources/doc/README.md b/Resources/doc/README.md index dd2b75dc3..071b2efb7 100644 --- a/Resources/doc/README.md +++ b/Resources/doc/README.md @@ -7,3 +7,4 @@ This documentation covers basic-topics to install and use this bundle. * [Installation](installation.md) * [Routing](routing.md) * [Content-Types](content-types.md) +* [Twig-Extensions](twig-extensions.md) diff --git a/Resources/doc/content-types.md b/Resources/doc/content-types.md index 7741a924f..b585ba271 100644 --- a/Resources/doc/content-types.md +++ b/Resources/doc/content-types.md @@ -49,7 +49,7 @@ the articles. ### Returns -The content-type returns a list of `ArticleViewDocumentInterface` instances. +The content-type returns a list of `ArticleResourceItem` instances. ## Teaser-Selection diff --git a/Resources/doc/installation.md b/Resources/doc/installation.md index b57b19c4f..dab96722d 100644 --- a/Resources/doc/installation.md +++ b/Resources/doc/installation.md @@ -118,10 +118,21 @@ php bin/console ongr:es:index:create --manager=live ```yml # app/config/config.yml +# Default configuration for extension with alias: "sulu_article" sulu_article: + smart_content: + default_limit: 100 + content_types: + article: + template: 'SuluArticleBundle:Template:content-types/article-selection.html.twig' + page_tree_route: + template: 'SuluArticleBundle:Template:content-types/page-tree-route.html.twig' + page_route_cascade: request # One of "request"; "task"; "off" documents: article: - view: Sulu\Bundle\ArticleBundle\Document\ArticleViewDocument + view: Sulu\Bundle\ArticleBundle\Document\ArticleViewDocument + article_page: + view: Sulu\Bundle\ArticleBundle\Document\ArticlePageViewObject types: # Prototype @@ -130,6 +141,16 @@ sulu_article: # Display tab 'all' in list view display_tab_all: true + search_fields: + + # Defaults: + - title + - excerpt.title + - excerpt.description + - excerpt.seo.title + - excerpt.seo.description + - excerpt.seo.keywords + - teaser_description ``` diff --git a/Resources/doc/twig-extensions.md b/Resources/doc/twig-extensions.md new file mode 100644 index 000000000..d8c7086a3 --- /dev/null +++ b/Resources/doc/twig-extensions.md @@ -0,0 +1,47 @@ +# Twig Extensions + +Twig Extensions + +* Load recent articles: `sulu_article_load_recent` +* Load similar articles: `sulu_article_load_similar` + +## `sulu_article_load_recent` + +Returns recent published articles, filter by given parameters. + +### Arguments + +- **limit**: *integer* - optional: set the limit - default: 5 +- **types**: *array* - optional: filter for article types - default: type of the requested article or null +- **locale**: *string* - optional: filter for locale - default: locale of the request + +### Returns + +The content-type returns a list of `ArticleResourceItem` instances. + +### Example + +```twig +{% set articles = sulu_article_load_recent(3, ['blog']) %} +``` + +## `sulu_article_load_similar` + +Returns similar articles compared to the requested one. +Note: Which fields are included in this request can be configured with `sulu_article.search_fields` config parameter. + +### Arguments + +- **limit**: *integer* - optional: set the limit - default: 5 +- **types**: *array* - optional: filter for article types - default: type of the requested article +- **locale**: *string* - optional: filter for locale - default: locale of the request + +### Returns + +The content-type returns a list of `ArticleResourceItem` instances. + +### Example + +```twig +{% set articles = sulu_article_load_similar(5, ['blog']) %} +``` diff --git a/Tests/Functional/Twig/ArticleViewDocumentTwigExtensionTest.php b/Tests/Functional/Twig/ArticleViewDocumentTwigExtensionTest.php new file mode 100644 index 000000000..5e8f162ae --- /dev/null +++ b/Tests/Functional/Twig/ArticleViewDocumentTwigExtensionTest.php @@ -0,0 +1,97 @@ +initPhpcr(); + + /** @var Manager $manager */ + $manager = $this->getContainer()->get('es.manager.default'); + $manager->dropAndCreateIndex(); + $manager = $this->getContainer()->get('es.manager.live'); + $manager->dropAndCreateIndex(); + } + + public function testFindMethods() + { + $items = [ + $this->createArticle('XXX 1'), + $this->createArticle('XXX 2'), + $this->createArticle('YYY 3'), + $this->createArticle('YYY 4'), + ]; + + $fakeRequest = Request::create('/', 'GET'); + $fakeRequest->setLocale('de'); + $fakeRequest->attributes->set('object', $this->getArticleDocument($items[0]['id'])); + + $requestStack = $this->getContainer()->get('request_stack'); + $requestStack->push($fakeRequest); + + // test 'loadRecent' => all others should be returned + $result = $this->getTwigExtension()->loadRecent(); + $this->assertCount(count($items) - 1, $result); + + // test 'loadSimilar' => only article with title 'XXX 2' should be returned + $result = $this->getTwigExtension()->loadSimilar(); + $this->assertCount(1, $result); + $this->assertEquals($items[1]['title'], $result[0]->getTitle()); + } + + private function createArticle($title = 'Test-Article', $template = 'default') + { + $client = $this->createAuthenticatedClient(); + $client->request( + 'POST', + '/api/articles?locale=' . self::LOCALE . '&action=publish', + ['title' => $title, 'template' => $template] + ); + + return json_decode($client->getResponse()->getContent(), true); + } + + /** + * @param string $uuid + * + * @return ArticleDocument + */ + private function getArticleDocument($uuid) + { + $documentManager = $this->getContainer()->get('sulu_document_manager.document_manager'); + + return $documentManager->find($uuid, self::LOCALE); + } + + /** + * @return ArticleViewDocumentTwigExtension + */ + private function getTwigExtension() + { + return $this->getContainer()->get('sulu_article.twig.view_document_repository'); + } +} diff --git a/Tests/Unit/Factory/ArticleResourceItemFactoryTest.php b/Tests/Unit/Factory/ArticleResourceItemFactoryTest.php new file mode 100644 index 000000000..4c38a6780 --- /dev/null +++ b/Tests/Unit/Factory/ArticleResourceItemFactoryTest.php @@ -0,0 +1,74 @@ +getArticleDocument(); + $articleViewDocument = $this->getArticleViewDocument($articleDocument); + $documentManager = $this->prophesize(DocumentManager::class); + $proxyFactory = $this->prophesize(LazyLoadingValueHolderFactory::class); + + $articleResourceItemFactory = new ArticleResourceItemFactory( + $documentManager->reveal(), + $proxyFactory->reveal() + ); + + $proxyFactory->createProxy(Argument::cetera()) + ->willReturn( + $this->prophesize(ArticleDocument::class)->willImplement(VirtualProxyInterface::class)->reveal() + ); + + $result = $articleResourceItemFactory->createResourceItem($articleViewDocument); + + $this->assertInstanceOf(VirtualProxyInterface::class, $result->getResource()); + $this->assertInstanceOf(ArticleDocument::class, $result->getResource()); + $this->assertInstanceOf(ArticleResourceItem::class, $result); + $this->assertEquals($result->getUuid(), $articleDocument->getUuid()); + } + + /** + * @return ArticleDocument + */ + private function getArticleDocument() + { + $articleDocument = new ArticleDocument(); + $articleDocument->setUuid('123-123-123'); + $articleDocument->setLocale('de'); + + return $articleDocument; + } + + /** + * @param ArticleDocument $articleDocument + * + * @return ArticleViewDocument + */ + private function getArticleViewDocument(ArticleDocument $articleDocument) + { + $articleViewDocument = new ArticleViewDocument($articleDocument->getUuid()); + $articleViewDocument->setLocale($articleDocument->getLocale()); + + return $articleViewDocument; + } +} diff --git a/Tests/Unit/Repository/ArticleViewDocumentRepositoryTest.php b/Tests/Unit/Repository/ArticleViewDocumentRepositoryTest.php new file mode 100644 index 000000000..d1dd2ed0f --- /dev/null +++ b/Tests/Unit/Repository/ArticleViewDocumentRepositoryTest.php @@ -0,0 +1,191 @@ +search = $this->prophesize(Search::class); + + $this->repository = $this->prophesize(Repository::class); + $this->repository->createSearch()->willReturn(new Search()); + + $this->searchManager = $this->prophesize(Manager::class); + $this->searchManager->getRepository(ArticleViewDocument::class)->willReturn($this->repository->reveal()); + + $this->articleViewDocumentRepository = new ArticleViewDocumentRepository( + $this->searchManager->reveal(), + ArticleViewDocument::class, + ['title', 'teaser_description'] + ); + } + + public function dataProvider() + { + return [ + [], + ['123-123-123', ['blog', 'article']], + ['321-321-321', ['blog'], 'en', 10], + ]; + } + + /** + * @dataProvider dataProvider + * + * @param string $uuid + * @param array $types + * @param string $locale + * @param int $limit + */ + public function testFindSimilar($uuid = '123-123-123', array $types = ['blog'], $locale = 'de', $limit = 12) + { + $termQuery = []; + foreach ($types as $type) { + $termQuery[] = [ + 'term' => ['type' => $type], + ]; + } + + $expectedSearch = [ + 'bool' => [ + 'filter' => [ + [ + 'term' => ['locale' => $locale], + ], + ], + 'must' => [ + [ + 'bool' => [ + 'should' => $termQuery, + ], + ], + [ + 'more_like_this' => [ + 'like' => null, + 'fields' => ['title', 'teaser_description'], + 'min_term_freq' => 1, + 'min_doc_freq' => 2, + 'ids' => [$uuid . '-' . $locale], + ], + ], + ], + ], + ]; + + $documentIterator = $this->prophesize(DocumentIterator::class); + + $this->repository->findDocuments(Argument::that(function(Search $search) use ($expectedSearch, $limit) { + $this->assertEquals($expectedSearch, $search->getQueries()->toArray()); + $this->assertEquals($limit, $search->getSize()); + $this->assertCount(0, $search->getSorts()); + + return true; + }))->willReturn($documentIterator->reveal()); + + $this->assertSame( + $documentIterator->reveal(), + $this->articleViewDocumentRepository->findSimilar($uuid, $limit, $types, $locale) + ); + } + + /** + * @dataProvider dataProvider + * + * @param string $excludeUuid + * @param array $types + * @param string $locale + * @param int $limit + */ + public function testFindRecent( + $excludeUuid = '123-123-123', + array $types = ['blog'], + $locale = 'de', + $limit = 12 + ) { + $termQuery = []; + foreach ($types as $type) { + $termQuery[] = [ + 'term' => ['type' => $type], + ]; + } + + $expectedSearch = [ + 'bool' => [ + 'filter' => [ + [ + 'term' => ['locale' => $locale], + ], + ], + 'must' => [ + [ + 'bool' => [ + 'should' => $termQuery, + ], + ], + ], + 'must_not' => [ + [ + 'term' => [ + 'uuid' => $excludeUuid, + ], + ], + ], + ], + ]; + + $documentIterator = $this->prophesize(DocumentIterator::class); + + $this->repository->findDocuments(Argument::that(function(Search $search) use ($expectedSearch, $limit) { + $this->assertEquals($expectedSearch, $search->getQueries()->toArray()); + $this->assertEquals($limit, $search->getSize()); + $this->assertCount(1, $search->getSorts()); + $this->assertEquals(new FieldSort('authored', FieldSort::DESC), current($search->getSorts())); + + return true; + }))->willReturn($documentIterator->reveal()); + + $this->assertSame( + $documentIterator->reveal(), + $this->articleViewDocumentRepository->findRecent($excludeUuid, $limit, $types, $locale) + ); + } +} diff --git a/Tests/Unit/Twig/ArticleViewDocumentTwigExtensionTest.php b/Tests/Unit/Twig/ArticleViewDocumentTwigExtensionTest.php new file mode 100644 index 000000000..8acb9ba10 --- /dev/null +++ b/Tests/Unit/Twig/ArticleViewDocumentTwigExtensionTest.php @@ -0,0 +1,225 @@ +getArticleDocuments(); + $articleViewDocuments = $this->getArticleViewDocuments($articleDocuments); + $articleResourceItems = $this->getArticleResourceItems($articleDocuments, $articleViewDocuments); + $documentIterator = $this->getDocumentIterator($articleViewDocuments); + $metadataFactory = $this->getMetadataFactory(); + + $articleViewDocumentRepository = $this->prophesize(ArticleViewDocumentRepository::class); + $articleViewDocumentRepository->findSimilar( + $articleDocuments[0]->getUuid(), + 5, + [ + $articleDocuments[0]->getStructureType(), + ], + $articleDocuments[0]->getLocale() + )->willReturn($documentIterator); + + $articleResourceItemFactory = $this->prophesize(ArticleResourceItemFactory::class); + $articleResourceItemFactory->createResourceItem($articleViewDocuments[1])->willReturn($articleResourceItems[1]); + $articleResourceItemFactory->createResourceItem($articleViewDocuments[2])->willReturn($articleResourceItems[2]); + + $request = $this->prophesize(Request::class); + $request->get('object')->willReturn($articleDocuments[0]); + $request->getLocale()->willReturn('de'); + + $requestStack = $this->prophesize(RequestStack::class); + $requestStack->getCurrentRequest()->wilLReturn($request); + + $referenceStore = $this->prophesize(ReferenceStore::class); + $referenceStore->add($articleDocuments[1]->getUuid())->shouldBeCalled(); + $referenceStore->add($articleDocuments[2]->getUuid())->shouldBeCalled(); + + $extension = new ArticleViewDocumentTwigExtension( + $articleViewDocumentRepository->reveal(), + $articleResourceItemFactory->reveal(), + $referenceStore->reveal(), + $metadataFactory->reveal(), + $requestStack->reveal() + ); + + $this->assertEquals( + [ + $articleResourceItems[1], + $articleResourceItems[2], + ], + $extension->loadSimilar() + ); + } + + public function testFindRecent() + { + $articleDocuments = $this->getArticleDocuments(); + $articleViewDocuments = $this->getArticleViewDocuments($articleDocuments); + $articleResourceItems = $this->getArticleResourceItems($articleDocuments, $articleViewDocuments); + $documentIterator = $this->getDocumentIterator($articleViewDocuments); + $metadataFactory = $this->getMetadataFactory(); + + $articleViewDocumentRepository = $this->prophesize(ArticleViewDocumentRepository::class); + $articleViewDocumentRepository->findRecent( + $articleDocuments[0]->getUuid(), + 5, + [ + $articleDocuments[0]->getStructureType(), + ], + $articleDocuments[0]->getLocale() + )->willReturn($documentIterator); + + $articleResourceItemFactory = $this->prophesize(ArticleResourceItemFactory::class); + $articleResourceItemFactory->createResourceItem($articleViewDocuments[1])->willReturn($articleResourceItems[1]); + $articleResourceItemFactory->createResourceItem($articleViewDocuments[2])->willReturn($articleResourceItems[2]); + + $request = $this->prophesize(Request::class); + $request->get('object')->willReturn($articleDocuments[0]); + $request->getLocale()->willReturn('de'); + + $requestStack = $this->prophesize(RequestStack::class); + $requestStack->getCurrentRequest()->wilLReturn($request); + + $referenceStore = $this->prophesize(ReferenceStore::class); + $referenceStore->add($articleDocuments[1]->getUuid())->shouldBeCalled(); + $referenceStore->add($articleDocuments[2]->getUuid())->shouldBeCalled(); + + $extension = new ArticleViewDocumentTwigExtension( + $articleViewDocumentRepository->reveal(), + $articleResourceItemFactory->reveal(), + $referenceStore->reveal(), + $metadataFactory->reveal(), + $requestStack->reveal() + ); + + $this->assertEquals( + [ + $articleResourceItems[1], + $articleResourceItems[2], + ], + $extension->loadRecent() + ); + } + + /** + * @return MetadataFactory + */ + private function getMetadataFactory() + { + $metadata = new StructureMetadata(); + $metadata->tags[] = ['name' => 'sulu_article.type', 'attributes' => ['type' => 'blog']]; + + $metadataFactory = $this->prophesize(StructureMetadataFactory::class); + $metadataFactory->getStructureMetadata('article', 'blog')->willReturn($metadata); + + return $metadataFactory; + } + + /** + * @return array + */ + private function getArticleDocuments() + { + $ids = ['123-123-123', '321-321-321', '111-111-111']; + + return array_map( + function ($id) { + $articleDocument = new ArticleDocument(); + $articleDocument->setUuid($id); + $articleDocument->setLocale('de'); + $articleDocument->setStructureType('blog'); + + return $articleDocument; + }, + array_values($ids) + ); + } + + /** + * @param array $articleDocuments + * + * @return array + */ + private function getArticleViewDocuments(array $articleDocuments) + { + return array_map( + function ($articleDocument) { + $articleViewDocument = new ArticleViewDocument($articleDocument->getUuid()); + $articleViewDocument->setLocale($articleDocument->getLocale()); + $articleViewDocument->setStructureType($articleDocument->getStructureType()); + + return $articleViewDocument; + }, + array_values($articleDocuments) + ); + } + + /** + * @param array $articleDocuments + * @param array $articleViewDocuments + * + * @return array + */ + private function getArticleResourceItems(array $articleDocuments, array $articleViewDocuments) + { + $articleResourceItems = []; + + foreach ($articleViewDocuments as $key => $value) { + $articleResourceItems[] = new ArticleResourceItem($articleViewDocuments[$key], $articleDocuments[$key]); + } + + return $articleResourceItems; + } + + /** + * @param array $articleViewDocuments + * + * @return DocumentIterator + */ + private function getDocumentIterator(array $articleViewDocuments) + { + $documentIteratorCount = 1; + + $documentIterator = $this->prophesize(DocumentIterator::class); + $documentIterator->rewind()->willReturn(0); + $documentIterator->next()->willReturn($documentIteratorCount); + $documentIterator->current()->will(function() use (&$documentIteratorCount, $articleViewDocuments) { + return $articleViewDocuments[$documentIteratorCount++]; + }); + $documentIterator->valid()->will(function() use (&$documentIteratorCount, $articleViewDocuments) { + if (array_key_exists($documentIteratorCount, $articleViewDocuments)) { + return true; + } + + return false; + }); + + return $documentIterator; + } +} diff --git a/Twig/ArticleViewDocumentTwigExtension.php b/Twig/ArticleViewDocumentTwigExtension.php new file mode 100644 index 000000000..206a16871 --- /dev/null +++ b/Twig/ArticleViewDocumentTwigExtension.php @@ -0,0 +1,212 @@ +articleViewDocumentRepository = $articleViewDocumentRepository; + $this->articleResourceItemFactory = $articleResourceItemFactory; + $this->referenceStore = $referenceStore; + $this->structureMetadataFactory = $structureMetadataFactory; + $this->requestStack = $requestStack; + } + + /** + * Returns an array of possible function in this extension. + * + * @return array + */ + public function getFunctions() + { + return [ + new \Twig_SimpleFunction('sulu_article_load_recent', [$this, 'loadRecent']), + new \Twig_SimpleFunction('sulu_article_load_similar', [$this, 'loadSimilar']), + ]; + } + + /** + * Loads recent articles with given parameters. + * + * @param int $limit + * @param null|array $types + * @param null|string $locale + * + * @return ArticleResourceItem[] + */ + public function loadRecent( + $limit = ArticleViewDocumentRepository::DEFAULT_LIMIT, + array $types = null, + $locale = null + ) { + $excludeUuid = null; + + /** @var Request $request */ + $request = $this->requestStack->getCurrentRequest(); + + $articleDocument = $request->get('object'); + if ($articleDocument instanceof ArticleDocument) { + $excludeUuid = $articleDocument->getUuid(); + + if (!$types) { + $types = [$this->getArticleType($articleDocument)]; + } + } + + if (!$locale) { + $locale = $request->getLocale(); + } + + $articleViewDocuments = $this->articleViewDocumentRepository->findRecent($excludeUuid, $limit, $types, $locale); + + return $this->getResourceItems($articleViewDocuments); + } + + /** + * Loads similar articles with given parameters. + * + * @param int $limit + * @param array|null $types + * @param null $locale + * + * @throws ArticleInRequestNotFoundException + * + * @return ArticleResourceItem[] + */ + public function loadSimilar( + $limit = ArticleViewDocumentRepository::DEFAULT_LIMIT, + array $types = null, + $locale = null + ) { + $uuid = null; + + $request = $this->requestStack->getCurrentRequest(); + + $articleDocument = $request->get('object'); + if ($articleDocument instanceof ArticleDocument) { + $uuid = $articleDocument->getUuid(); + + if (!$types) { + $types = [$this->getArticleType($articleDocument)]; + } + } + + if (!$uuid) { + throw new ArticleInRequestNotFoundException(); + } + + if (!$locale) { + $locale = $request->getLocale(); + } + + $articleViewDocuments = $this->articleViewDocumentRepository->findSimilar($uuid, $limit, $types, $locale); + + return $this->getResourceItems($articleViewDocuments); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'sulu_article.article_view_document'; + } + + /** + * @param DocumentIterator $articleViewDocuments + * + * @return ArticleResourceItem[] + */ + private function getResourceItems(DocumentIterator $articleViewDocuments) + { + $articleResourceItems = []; + + /** @var ArticleViewDocument $articleViewDocument */ + foreach ($articleViewDocuments as $articleViewDocument) { + $this->referenceStore->add($articleViewDocument->getUuid()); + $articleResourceItems[] = $this->articleResourceItemFactory->createResourceItem($articleViewDocument); + } + + return $articleResourceItems; + } + + /** + * @param ArticleDocument $articleDocument + * + * @return string + */ + private function getArticleType(ArticleDocument $articleDocument) + { + $structureMetadata = $this->structureMetadataFactory->getStructureMetadata( + 'article', + $articleDocument->getStructureType() + ); + + return $this->getType($structureMetadata); + } +}