diff --git a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Compiler/QueryTypePass.php b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Compiler/QueryTypePass.php index 7ec9954f476..44be3891064 100644 --- a/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Compiler/QueryTypePass.php +++ b/eZ/Bundle/EzPublishCoreBundle/DependencyInjection/Compiler/QueryTypePass.php @@ -5,6 +5,7 @@ namespace eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Compiler; use Exception; +use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; use ReflectionClass; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -16,23 +17,28 @@ */ class QueryTypePass implements CompilerPassInterface { + /** @var \Symfony\Component\DependencyInjection\Reference[] */ + private $queryTypeRefs = []; + public function process(ContainerBuilder $container) { if (!$container->hasDefinition('ezpublish.query_type.registry')) { return; } - $queryTypes = []; + $queryTypesRefs = []; + + // array of QueryType classes. Used to prevent double handling between services & convention definitions. + $queryTypesClasses = []; // tagged query types - $taggedServiceIds = $container->findTaggedServiceIds('ezpublish.query_type'); - foreach ($taggedServiceIds as $taggedServiceId => $tags) { + foreach ($container->findTaggedServiceIds('ezpublish.query_type') as $taggedServiceId => $tags) { $queryTypeDefinition = $container->getDefinition($taggedServiceId); - $queryTypeClass = $queryTypeDefinition->getClass(); + $queryTypeClassName = $queryTypeDefinition->getClass(); for ($i = 0, $count = count($tags); $i < $count; ++$i) { - // TODO: Check for duplicates - $queryTypes[$queryTypeClass::getName()] = new Reference($taggedServiceId); + $queryTypesRefs[] = new Reference($taggedServiceId); + $queryTypesClasses[$queryTypeClassName] = true; } } @@ -48,30 +54,48 @@ public function process(ContainerBuilder $container) continue; } - $queryTypeServices = []; + $conventionQueryTypeDefs = []; $bundleQueryTypeNamespace = substr($bundleClass, 0, strrpos($bundleClass, '\\') + 1) . 'QueryType'; foreach (glob($bundleQueryTypesDir . DIRECTORY_SEPARATOR . '*QueryType.php') as $queryTypeFilePath) { $queryTypeFileName = basename($queryTypeFilePath, '.php'); $queryTypeClassName = $bundleQueryTypeNamespace . '\\' . $queryTypeFileName; + if (isset($queryTypesClasses[$queryTypeClassName])) { + continue; + } if (!class_exists($queryTypeClassName)) { throw new Exception("Expected $queryTypeClassName to be defined in $queryTypeFilePath"); } - $queryTypeReflectionClass = new ReflectionClass($queryTypeClassName); - if (!$queryTypeReflectionClass->implementsInterface('eZ\Publish\Core\QueryType\QueryType')) { - throw new Exception("$queryTypeClassName needs to implement eZ\\Publish\\Core\\QueryType\\QueryType"); - } + $this->checkInterface($queryTypeClassName); $serviceId = 'ezpublish.query_type.convention.' . strtolower($bundleName) . '_' . strtolower($queryTypeFileName); - $queryTypeServices[$serviceId] = new Definition($queryTypeClassName); - - $queryTypes[$queryTypeClassName::getName()] = new Reference($serviceId); + $queryTypeDefinition = new Definition($queryTypeClassName); + $conventionQueryTypeDefs[$serviceId] = $queryTypeDefinition; + $queryTypesRefs[] = new Reference($serviceId); } - $container->addDefinitions($queryTypeServices); + $container->addDefinitions($conventionQueryTypeDefs); } } - $aggregatorDefinition = $container->getDefinition('ezpublish.query_type.registry'); - $aggregatorDefinition->addMethodCall('addQueryTypes', [$queryTypes]); + $registryDef = $container->getDefinition('ezpublish.query_type.registry'); + $registryDef->addMethodCall('addQueryTypes', [$queryTypesRefs]); + } + + /** + * Checks that $queryTypeClassName implements the QueryType interface. + * + * @param string $queryTypeClassName + * + * @throws InvalidArgumentException + */ + private function checkInterface($queryTypeClassName) + { + $queryTypeReflectionClass = new ReflectionClass($queryTypeClassName); + if (!$queryTypeReflectionClass->implementsInterface('eZ\Publish\Core\QueryType\QueryType')) { + throw new InvalidArgumentException( + "QueryTypeClass $queryTypeClassName", + 'needs to implement eZ\Publish\Core\QueryType\QueryType' + ); + } } } diff --git a/eZ/Bundle/EzPublishCoreBundle/Features/Content/query_controller.feature b/eZ/Bundle/EzPublishCoreBundle/Features/Content/query_controller.feature new file mode 100644 index 00000000000..0b1333cde1c --- /dev/null +++ b/eZ/Bundle/EzPublishCoreBundle/Features/Content/query_controller.feature @@ -0,0 +1,15 @@ +Feature: The ez_query controller can be used in content views to get query results into a view template + + Scenario: A content view that uses the query controller... + Given there is a blog content_view configuration + And it sets the controller to 'ez_query:contentAction' + And it sets the parameter "query" to a valid QueryType name + When a content matching the blog configuration is rendered + Then the view template has an 'items' variable + And the 'items' variable is an array with the results from the queryType + + Scenario: The template variable search results are assigned to can be customized + + Scenario: Parameters can be passed to the QueryType + + Scenario: Parameters from the view object can be passed to the QueryType diff --git a/eZ/Bundle/EzPublishCoreBundle/Features/Context/NavigationContext.php b/eZ/Bundle/EzPublishCoreBundle/Features/Context/NavigationContext.php new file mode 100644 index 00000000000..e753671f231 --- /dev/null +++ b/eZ/Bundle/EzPublishCoreBundle/Features/Context/NavigationContext.php @@ -0,0 +1,103 @@ +router = $router; + $this->urlAliasService = $urlAliasService; + } + + /** + * @Given /^there is a route "([^"]*)"$/ + */ + public function thereIsARoute($routeIdentifier) + { + /** @var \Symfony\Component\Routing\RouterInterface $router */ + $routeCollection = $this->router->getRouteCollection(); + Assertion::assertNotNull( + $route = $routeCollection->get($routeIdentifier), + "Failed asserting that there is a route named $routeIdentifier" + ); + + $this->currentRoute = $route; + } + + /** + * @Given /^that route has the default "([^"]*)" set to "([^"]*)"$/ + */ + public function routeHasTheDefaultSetTo($defaultName, $defaultValue) + { + Assertion::assertNotNull($this->currentRoute, 'No currentRoute was set'); + + Assertion::assertTrue( + $this->currentRoute->hasDefault($defaultName), + "Failed asserting that the route has the default attribute '$defaultName'" + ); + + if (is_string($defaultValue)) { + Assertion::assertEquals( + $defaultValue, + $this->currentRoute->getDefault($defaultName), + "Failed asserting that the route has the default attribute '$defaultName' set to '$defaultValue'" + ); + } elseif (is_array($defaultValue)) { + Assertion::assertArraySubset( + $defaultValue, + $this->currentRoute->getDefault($defaultName), + "Failed asserting that the route has the default attribute '$defaultName' with the given array items" + ); + } + } + + /** + * @Given /^that route has the default "([^"]*)" set to an array with the key "([^"]*)" set to "([^"]*)"$/ + */ + public function routeHasTheDefaultSetToArray($defaultName, $arrayKey, $arrayValue) + { + $this->routeHasTheDefaultSetTo($defaultName, [$arrayKey => $arrayValue]); + } + + /** + * @When /^I go to that route$/ + */ + public function iGoToThatRoute() + { + Assertion::assertTrue(isset($this->currentRoute), 'No current Route was set'); + $this->visitPath($this->currentRoute->getPath()); + } + + public function iVisitLocation(Location $location) + { + $urlAlias = $this->urlAliasService->reverseLookup($location); + $this->visitPath($urlAlias->path); + } +} diff --git a/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewConfigurationContext.php b/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewConfigurationContext.php new file mode 100644 index 00000000000..7d3a415d6d7 --- /dev/null +++ b/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewConfigurationContext.php @@ -0,0 +1,212 @@ +configResolver = $configResolver; + $this->locationService = $locationService; + } + + /** @BeforeScenario */ + public function gatherContexts(BeforeScenarioScope $scope) + { + $environment = $scope->getEnvironment(); + $this->navigationContext = $environment->getContext('eZ\Bundle\EzPublishCoreBundle\Features\Context\NavigationContext'); + } + + /** + * Looks up and asserts a view configuration existence. + * + * @param string $name The name of the view configuration block (blog, landing_page, ...) + * @param string $type The type of view configuration (content_view, block_view...) + * @param string $viewType full, line, ... + * + * @Given /^there is a ([a-z0-9_-]*) ([a-z0-9_]+) configuration$/ + * @Given /^there is a ([a-z0-9_-]*) ([a-z0-9_]+) configuration for the ([a-z0-9_]+) viewType$/ + * + * @return array The matching view configuration + */ + public function thereIsAViewConfiguration($name, $type, $viewType = 'full') + { + Assertion::assertTrue($this->configResolver->hasParameter($type)); + $configuration = $this->configResolver->getParameter($type); + + Assertion::assertArrayHasKey( + $viewType, + $configuration, + "Failed asserting that there are $type configurations for the '$viewType' viewType" + ); + + Assertion::assertArrayHasKey( + $name, + $configuration[$viewType], + "Failed asserting that there is a '$viewType' query_type_view configuration named '$name'" + ); + + $this->currentViewConfiguration = $configuration[$viewType][$name]; + + return $this->currentViewConfiguration; + } + + /** + * @Given /^there is a "([^"]*)" ([a-z0-9_]+) configuration for the "([^"]*)" viewType$/ + */ + public function thereIsAViewConfigurationForTheViewTypeWithTheTemplate($viewConfigName, $what, $viewType) + { + /** @var \eZ\Publish\Core\MVC\ConfigResolverInterface $configResolver */ + $queryTypeViewConfiguration = $this->configResolver->getParameter($what); + + Assertion::assertArrayHasKey( + $viewType, + $queryTypeViewConfiguration, + "Failed asserting that there are $what configurations for the viewType $viewType" + ); + + Assertion::assertArrayHasKey( + $viewConfigName, + $queryTypeViewConfiguration[$viewType], + "Failed asserting that there is a '$viewType' query_type_view configuration named '$viewConfigName'" + ); + + $this->currentViewConfiguration = $queryTypeViewConfiguration[$viewType][$viewConfigName]; + } + + /** + * @Given /^that configuration has "([^"]*)" set to "([^"]*)"$/ + */ + public function configurationHasThePropertySetToTheValue($propertyName, $propertyValue) + { + Assertion::assertNotNull($this->currentViewConfiguration, 'No currentViewConfiguration was set'); + + Assertion::assertArrayHasKey( + $propertyName, + $this->currentViewConfiguration, + "Failed asserting that the query_type_view configuration sets the $propertyName property" + ); + + Assertion::assertEquals( + $propertyValue, + $this->currentViewConfiguration[$propertyName], + "Failed asserting that the query_type_view configuration sets the $propertyName property to $propertyValue" + ); + } + + /** + * @Given /^that configuration has "([^"]*)" set to the boolean "([^"]*)"$/ + */ + public function configurationHasThePropertySetToTheBooleanValue($propertyName, $propertyValue) + { + if ($propertyValue === 'true') { + $propertyValue = true; + } elseif ($propertyValue === 'false') { + $propertyValue = false; + } else { + throw new InvalidArgumentException('propertyValue', "Unknown boolean value '$propertyValue''"); + } + + $this->configurationHasThePropertySetToTheValue($propertyName, $propertyValue); + } + + /** + * @Given /^that configuration matches on "([^"]*)" "([^"]*)"$/ + */ + public function configurationHasMatch($matcherName, $matcherValue) + { + Assertion::assertNotNull($this->currentViewConfiguration, 'No currentViewConfiguration was set'); + + $matchConfig = $this->currentViewConfiguration['match']; + + Assertion::assertArrayHasKey( + $matcherName, + $matchConfig, + "Failed asserting that the view has a matcher '$matcherName'" + ); + + if (is_string($matcherValue)) { + Assertion::assertEquals( + $matcherValue, + $matchConfig[$matcherName], + "Failed asserting that the view matches on '$matcherName: $matcherValue'" + ); + } elseif (is_array($matcherValue)) { + Assertion::assertArraySubset( + $matcherValue, + $matchConfig[$matcherName], + "Failed asserting that the view matches on '$matcherName' with the given array" + ); + } + } + + /** + * @Given /^it sets the controller to \'([^\']*)\'$/ + */ + public function itSetsTheControllerTo($controller) + { + Assertion::assertNotNull($this->currentViewConfiguration, 'No current view configuration was set'); + Assertion::assertArrayHasKey('controller', $this->currentViewConfiguration); + Assertion::assertEquals($controller, $this->currentViewConfiguration['controller']); + } + + /** + * @Given /^it sets the parameter "([^"]*)" to a valid QueryType name$/ + */ + public function itSetsParameterToAValidQueryTypeName($parameter) + { + $this->itSetsParameterTo($parameter, 'EzPlatformBehatBundle:LatestContent'); + } + + /** + * @Given /^it sets the parameter "([^"]*)" to "([^"]*)"$/ + */ + public function itSetsParameterTo($parameterName, $parameterValue) + { + Assertion::assertNotNull($this->currentViewConfiguration, 'No current view configuration was set'); + Assertion::assertArrayHasKey('params', $this->currentViewConfiguration); + Assertion::assertArrayHasKey($parameterName, $this->currentViewConfiguration['params']); + Assertion::assertEquals($parameterValue, $this->currentViewConfiguration['params'][$parameterName]); + } + + /** + * @When /^a content matching the blog configuration is rendered$/ + */ + public function aContentMatchingTheBlogConfigurationIsRendered() + { + $this->navigationContext->iVisitLocation( + $this->locationService->loadLocation(self::BLOG_LOCATION_ID) + ); + } +} diff --git a/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewTemplateContext.php b/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewTemplateContext.php new file mode 100644 index 00000000000..22747c2072e --- /dev/null +++ b/eZ/Bundle/EzPublishCoreBundle/Features/Context/ViewTemplateContext.php @@ -0,0 +1,45 @@ +assertSession()->elementExists('xpath', '//' . $this->buildVariableXpath($variableName)); + } + + /** + * @Given /^the \'([^\']*)\' variable is an array with the results from the queryType$/ + */ + public function theVariableIsAnArrayWithTheResultsFrom($variableName, $queryTypeName) + { + $xpath = '//' . $this->buildVariableXpath($variableName); + $elements = $this->getSession()->getPage()->findAll('xpath', $xpath); + /** @var NodeElement $element */ + $element = $elements[0]; + Assertion::assertEquals( + $element->find('xpath', '/following-sibling::abbr')->getText(), + 'array:1' + ); + } + + private function buildVariableXpath($variableName) + { + return "span[@class='sf-dump-key' and text() = '$variableName']"; + } +} diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml b/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml index a56ad489562..a531c98c390 100644 --- a/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml +++ b/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml @@ -12,6 +12,7 @@ parameters: ezpublish.controller.content.preview.class: eZ\Publish\Core\MVC\Symfony\Controller\Content\PreviewController ezpublish.controller.content.download.class: eZ\Publish\Core\MVC\Symfony\Controller\Content\DownloadController ezpublish.controller.content.download_redirection.class: eZ\Publish\Core\MVC\Symfony\Controller\Content\DownloadRedirectionController + ezpublish.controller.content.query.class: eZ\Publish\Core\MVC\Symfony\Controller\Content\QueryController ezpublish.controller.page.view.class: eZ\Bundle\EzPublishCoreBundle\Controller\PageController # Param converters @@ -29,6 +30,8 @@ parameters: ezpublish.exception_listener.class: eZ\Bundle\EzPublishCoreBundle\EventListener\ExceptionListener ezpublish.fields_groups.list.class: eZ\Publish\Core\Helper\FieldsGroups\ArrayTranslatorFieldsGroupsList + + ezpublish.query_type.registry.class: eZ\Publish\Core\QueryType\QueryTypeNameRegistry services: # Siteaccess is injected in the container at runtime @@ -172,7 +175,7 @@ services: - { name: kernel.event_subscriber } ezpublish.query_type.registry: - class: eZ\Publish\Core\QueryType\ArrayQueryTypeRegistry + class: %ezpublish.query_type.registry.class% ezpublish.fields_groups.list: class: %ezpublish.fields_groups.list.class% @@ -184,3 +187,11 @@ services: class: eZ\Publish\Core\Helper\FieldsGroups\RepositoryConfigFieldsGroupsListFactory arguments: - @ezpublish.api.repository_configuration_provider + ezpublish.controller.content.query: + class: %ezpublish.controller.content.query.class% + arguments: + - @ezpublish.query_type.registry + - @ezpublish.api.service.search + + ez_query: + alias: ezpublish.controller.content.query diff --git a/eZ/Bundle/EzPublishCoreBundle/Tests/DependencyInjection/Compiler/QueryTypePassTest.php b/eZ/Bundle/EzPublishCoreBundle/Tests/DependencyInjection/Compiler/QueryTypePassTest.php index 81674f38745..5f746ba38c3 100644 --- a/eZ/Bundle/EzPublishCoreBundle/Tests/DependencyInjection/Compiler/QueryTypePassTest.php +++ b/eZ/Bundle/EzPublishCoreBundle/Tests/DependencyInjection/Compiler/QueryTypePassTest.php @@ -31,29 +31,68 @@ protected function registerCompilerPass(ContainerBuilder $container) public function testRegisterTaggedQueryType() { - $def = new Definition(); - $def->addTag('ezpublish.query_type'); - $def->setClass('eZ\Bundle\EzPublishCoreBundle\Tests\DependencyInjection\Stub\QueryTypeBundle\QueryType\TestQueryType'); $serviceId = 'test.query_type'; - $this->setDefinition($serviceId, $def); + $this->defineQueryTypeService( + $serviceId, + 'eZ\Bundle\EzPublishCoreBundle\Tests\DependencyInjection\Stub\QueryTypeBundle\QueryType\TestQueryType' + ); $this->compile(); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( 'ezpublish.query_type.registry', 'addQueryTypes', - [['Test:Test' => new Reference($serviceId)]] + [[new Reference($serviceId)]] ); } public function testConventionQueryType() { - $this->setParameter('kernel.bundles', ['QueryTypeBundle' => 'eZ\Bundle\EzPublishCoreBundle\Tests\DependencyInjection\Stub\QueryTypeBundle\QueryTypeBundle']); + $this->defineQueryTypeBundle(); + + $this->compile(); + $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( + 'ezpublish.query_type.registry', + 'addQueryTypes', + [[new Reference('ezpublish.query_type.convention.querytypebundle_testquerytype')]] + ); + } + + /** + * Tests that a QueryType that is declared as a service and named by convention is registered correctly. + */ + public function testServicePlusConvention() + { + $this->defineQueryTypeService( + 'test.query_type', + 'eZ\Bundle\EzPublishCoreBundle\Tests\DependencyInjection\Stub\QueryTypeBundle\QueryType\TestQueryType' + ); + $this->defineQueryTypeBundle(); $this->compile(); $this->assertContainerBuilderHasServiceDefinitionWithMethodCall( 'ezpublish.query_type.registry', 'addQueryTypes', - [['Test:Test' => new Reference('ezpublish.query_type.convention.querytypebundle_testquerytype')]] + [[new Reference('test.query_type')]] + ); + $this->assertContainerBuilderNotHasService('ezpublish.query_type.convention.querytypebundle_testquerytype'); + } + + private function defineQueryTypeService($serviceId, $class) + { + $def = new Definition(); + $def->addTag('ezpublish.query_type'); + $def->setClass($class); + $this->setDefinition($serviceId, $def); + } + + /** + * Adds to the kernel the path to a stub bundle that contains a QueryType class named by convention. + */ + private function defineQueryTypeBundle() + { + $this->setParameter( + 'kernel.bundles', + ['QueryTypeBundle' => 'eZ\Bundle\EzPublishCoreBundle\Tests\DependencyInjection\Stub\QueryTypeBundle\QueryTypeBundle'] ); } } diff --git a/eZ/Bundle/EzPublishCoreBundle/behat_suites.yml b/eZ/Bundle/EzPublishCoreBundle/behat_suites.yml index 9c2dfa70bb9..bf9c7b42c95 100644 --- a/eZ/Bundle/EzPublishCoreBundle/behat_suites.yml +++ b/eZ/Bundle/EzPublishCoreBundle/behat_suites.yml @@ -12,7 +12,14 @@ core: - vendor/ezsystems/ezpublish-kernel/eZ/Bundle/EzPublishCoreBundle/Features/Content - vendor/ezsystems/ezpublish-kernel/eZ/Bundle/EzPublishCoreBundle/Features/Exception contexts: + - eZ\Bundle\EzPublishCoreBundle\Features\Context\NavigationContext: + router: @router + urlAliasService: @ezpublish.api.service.url_alias - eZ\Bundle\EzPublishCoreBundle\Features\Context\ContentPreviewContext - eZ\Bundle\EzPublishCoreBundle\Features\Context\ContentContext: repository: @ezpublish.api.repository - eZ\Bundle\EzPublishCoreBundle\Features\Context\ExceptionContext + - eZ\Bundle\EzPublishCoreBundle\Features\Context\ViewTemplateContext + - eZ\Bundle\EzPublishCoreBundle\Features\Context\ViewConfigurationContext: + configResolver: @ezpublish.config.resolver + locationService: @ezpublish.api.service.location diff --git a/eZ/Bundle/PlatformBehatBundle/DependencyInjection/EzPlatformBehatExtension.php b/eZ/Bundle/PlatformBehatBundle/DependencyInjection/EzPlatformBehatExtension.php index 70354599237..c78d0b77a2e 100644 --- a/eZ/Bundle/PlatformBehatBundle/DependencyInjection/EzPlatformBehatExtension.php +++ b/eZ/Bundle/PlatformBehatBundle/DependencyInjection/EzPlatformBehatExtension.php @@ -5,11 +5,14 @@ namespace EzSystems\PlatformBehatBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; +use Symfony\Component\Config\Resource\FileResource; +use Symfony\Component\Yaml\Yaml; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\DependencyInjection\Loader; +use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; -class EzPlatformBehatExtension extends Extension +class EzPlatformBehatExtension extends Extension implements PrependExtensionInterface { public function load(array $config, ContainerBuilder $container) { @@ -20,4 +23,12 @@ public function load(array $config, ContainerBuilder $container) $loader->load('services.yml'); } + + public function prepend(ContainerBuilder $container) + { + $configFile = __DIR__ . '/../Resources/config/ez_view.yml'; + $config = Yaml::parse(file_get_contents($configFile)); + $container->prependExtensionConfig('ezpublish', $config); + $container->addResource(new FileResource($configFile)); + } } diff --git a/eZ/Bundle/PlatformBehatBundle/QueryType/LatestContentQueryType.php b/eZ/Bundle/PlatformBehatBundle/QueryType/LatestContentQueryType.php new file mode 100644 index 00000000000..445d4351a92 --- /dev/null +++ b/eZ/Bundle/PlatformBehatBundle/QueryType/LatestContentQueryType.php @@ -0,0 +1,39 @@ + new Query\Criterion\LogicalAnd($criteria), + 'sortClauses' => [new Query\SortClause\DatePublished()], + 'limit' => isset($parameters['limit']) ? $parameters['limit'] : 10, + ]); + } + + public static function getName() + { + return 'EzPlatformBehatBundle:LatestContent'; + } + + /** + * Returns an array listing the parameters supported by the QueryType. + * @return array + */ + public function getSupportedParameters() + { + return ['type']; + } +} diff --git a/eZ/Bundle/PlatformBehatBundle/Resources/config/ez_view.yml b/eZ/Bundle/PlatformBehatBundle/Resources/config/ez_view.yml new file mode 100644 index 00000000000..a9509e99749 --- /dev/null +++ b/eZ/Bundle/PlatformBehatBundle/Resources/config/ez_view.yml @@ -0,0 +1,11 @@ +system: + default: + content_view: + full: + blog: + template: 'EzPlatformBehatBundle:full:dump.html.twig' + match: + Identifier\ContentType: 'blog' + controller: ez_query:contentAction + params: + query: 'EzPlatformBehatBundle:LatestContent' diff --git a/eZ/Bundle/PlatformBehatBundle/Resources/views/full/dump.html.twig b/eZ/Bundle/PlatformBehatBundle/Resources/views/full/dump.html.twig new file mode 100644 index 00000000000..d480c052b57 --- /dev/null +++ b/eZ/Bundle/PlatformBehatBundle/Resources/views/full/dump.html.twig @@ -0,0 +1,5 @@ + + +{{ dump() }} + + diff --git a/eZ/Publish/Core/MVC/Symfony/Controller/Content/QueryController.php b/eZ/Publish/Core/MVC/Symfony/Controller/Content/QueryController.php new file mode 100644 index 00000000000..2c50541e48f --- /dev/null +++ b/eZ/Publish/Core/MVC/Symfony/Controller/Content/QueryController.php @@ -0,0 +1,81 @@ +queryTypeRegistry = $queryTypeRegistry; + $this->searchService = $searchService; + } + + public function contentAction(ContentView $view) + { + $searchResults = $this->searchService->findContent( + $this->getQuery($view), + [] // @todo we most likely want to use the Viewed content language(s) + ); + $variableName = $view->hasParameter('variable') ? $view->getParameter('variable') : 'content_list'; + $view->addParameters([$variableName => $searchResults]); + + return $view; + } + + public function locationAction(ContentView $view) + { + return $view; + } + + public function contentInfoAction(ContentView $view) + { + return $view; + } + + /** + * Returns the query configured in the view parameters. + * + * @param \eZ\Publish\Core\MVC\Symfony\View\ContentView $view + * + * @return \eZ\Publish\API\Repository\Values\Content\Query + * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException If the query parameter isn't set + */ + private function getQuery(ContentView $view) + { + if (!$view->hasParameter('query')) { + throw new InvalidArgumentException('query', 'Missing required query parameter'); + } + $queryType = $this->queryTypeRegistry->getQueryType($view->getParameter('query')); + + $queryParameters = $view->hasParameter('queryParameters') ? $view->getParameter('queryParameters') : []; + $supportedQueryParameters = array_flip($queryType->getSupportedParameters()); + foreach ($queryParameters as $queryParameterName => $queryParameterValue) { + if (!isset($supportedQueryParameters[$queryParameterName])) { + throw new InvalidArgumentException("parameter $queryParameterName", 'unsupported query parameter'); + } + if (substr($queryParameterValue, 0, 2) == '@=') { + $language = new ExpressionLanguage(); + $queryParameters[$queryParameterName] = $language->evaluate( + substr($queryParameterValue, 2), + ['view' => $view, 'location' => $view->getLocation(), 'content' => $view->getContent()] + ); + } + } + + return $queryType->getQuery($queryParameters); + } +} diff --git a/eZ/Publish/Core/MVC/Symfony/View/Configurator/ViewProvider.php b/eZ/Publish/Core/MVC/Symfony/View/Configurator/ViewProvider.php index 95d45436f44..15f79b09442 100644 --- a/eZ/Publish/Core/MVC/Symfony/View/Configurator/ViewProvider.php +++ b/eZ/Publish/Core/MVC/Symfony/View/Configurator/ViewProvider.php @@ -42,6 +42,8 @@ public function configure(View $view) $view->setControllerReference($controllerReference); } + $view->addParameters($providerView->getParameters()); + return; } } diff --git a/eZ/Publish/Core/MVC/Symfony/View/Provider/Configured.php b/eZ/Publish/Core/MVC/Symfony/View/Provider/Configured.php index 8dd16073826..f6e09f3a863 100644 --- a/eZ/Publish/Core/MVC/Symfony/View/Provider/Configured.php +++ b/eZ/Publish/Core/MVC/Symfony/View/Provider/Configured.php @@ -60,6 +60,9 @@ protected function buildContentView(array $viewConfig) if (isset($viewConfig['controller'])) { $view->setControllerReference(new ControllerReference($viewConfig['controller'])); } + if (isset($viewConfig['params']) && is_array($viewConfig['params'])) { + $view->addParameters($viewConfig['params']); + } return $view; } diff --git a/eZ/Publish/Core/QueryType/LocationChildrenQueryType.php b/eZ/Publish/Core/QueryType/LocationChildrenQueryType.php new file mode 100644 index 00000000000..15af9ae26a0 --- /dev/null +++ b/eZ/Publish/Core/QueryType/LocationChildrenQueryType.php @@ -0,0 +1,75 @@ +languages = $languages; + $this->excludedContentTypes = $excludedContentTypes; + $this->sorter = $sorter; + } + + protected function configureOptions(OptionsResolver $optionsResolver) + { + $optionsResolver->setRequired('location'); + $optionsResolver->setDefault('types', []); + $optionsResolver->setAllowedValues('location', function ($value) { return $value instanceof Location; }); + $optionsResolver->setAllowedTypes('types', 'array'); + } + + protected function doGetQuery(array $parameters) + { + $query = new \eZ\Publish\API\Repository\Values\Content\LocationQuery(); + + $criteria = [ + new Criterion\Visibility(Criterion\Visibility::VISIBLE), + new Criterion\ParentLocationId($parameters['location']->id), + new Criterion\LanguageCode($this->languages), + ]; + + if (isset($parameters['types'])) { + $criteria[] = new Criterion\ContentTypeIdentifier($parameters['types']); + } + + if (!empty($this->excludedContentTypes)) { + $criteria[] = new Criterion\LogicalNot( + new Criterion\ContentTypeIdentifier($this->excludedContentTypes) + ); + } + + $query->filter = $criteria; + + $this->sorter->sortFromLocation($query, $parameters['location']); + + return $query; + } + + /** + * Returns the QueryType name. + * @return string + */ + public static function getName() + { + return 'LocationChildren'; + } +} diff --git a/eZ/Publish/Core/QueryType/ArrayQueryTypeRegistry.php b/eZ/Publish/Core/QueryType/QueryTypeNameRegistry.php similarity index 55% rename from eZ/Publish/Core/QueryType/ArrayQueryTypeRegistry.php rename to eZ/Publish/Core/QueryType/QueryTypeNameRegistry.php index 1205b34d707..969b1e96c99 100644 --- a/eZ/Publish/Core/QueryType/ArrayQueryTypeRegistry.php +++ b/eZ/Publish/Core/QueryType/QueryTypeNameRegistry.php @@ -8,27 +8,29 @@ use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; /** - * A QueryType registry based on an array. + * A QueryType registry that uses the QueryTypes names. */ -class ArrayQueryTypeRegistry +class QueryTypeNameRegistry implements QueryTypeRegistry { /** @var QueryType[] */ private $registry = []; - public function addQueryType($name, QueryType $queryType) + public function addQueryType(QueryType $queryType) { - $this->registry[$name] = $queryType; + $this->registry[$queryType::getName()] = $queryType; } public function addQueryTypes(array $queryTypes) { - $this->registry += $queryTypes; + foreach ($queryTypes as $queryType) { + $this->addQueryType($queryType); + } } public function getQueryType($name) { if (!isset($this->registry[$name])) { - throw new InvalidArgumentException('QueryType name', 'No QueryType found with that name'); + throw new InvalidArgumentException($name, 'No QueryType found with that name'); } return $this->registry[$name]; diff --git a/eZ/Publish/Core/QueryType/QueryTypeRegistry.php b/eZ/Publish/Core/QueryType/QueryTypeRegistry.php index a18ade1ac39..029abdb4dab 100644 --- a/eZ/Publish/Core/QueryType/QueryTypeRegistry.php +++ b/eZ/Publish/Core/QueryType/QueryTypeRegistry.php @@ -11,22 +11,22 @@ interface QueryTypeRegistry { /** - * Registers $queryType as $name. + * Registers $queryType. * * @param string $name * @param \eZ\Publish\Core\QueryType\QueryType $queryType */ - public function addQueryType($name, QueryType $queryType); + public function addQueryType(QueryType $queryType); /** * Registers QueryTypes from the $queryTypes array. * - * @param \eZ\Publish\Core\QueryType\QueryType[] $queryTypes An array of QueryTypes, with their name as the index + * @param \eZ\Publish\Core\QueryType\QueryType[] $queryTypes An array of QueryTypes. */ public function addQueryTypes(array $queryTypes); /** - * Get the QueryType $name. + * Get the QueryType named $name. * * @param string $name *