From 2543156e6af429fed31bcea249fa038c7d0422c3 Mon Sep 17 00:00:00 2001 From: Gordon Heydon Date: Thu, 7 May 2020 17:13:17 +1000 Subject: [PATCH] Create new subView to allow embedding a view inside and entity --- src/Plugin/Deriver/Fields/SubViewDeriver.php | 109 ++++++++++ src/Plugin/GraphQL/Fields/SubView.php | 201 +++++++++++++++++++ 2 files changed, 310 insertions(+) create mode 100644 src/Plugin/Deriver/Fields/SubViewDeriver.php create mode 100644 src/Plugin/GraphQL/Fields/SubView.php diff --git a/src/Plugin/Deriver/Fields/SubViewDeriver.php b/src/Plugin/Deriver/Fields/SubViewDeriver.php new file mode 100644 index 0000000..6a1b00c --- /dev/null +++ b/src/Plugin/Deriver/Fields/SubViewDeriver.php @@ -0,0 +1,109 @@ +get('entity_type.manager'), + $container->get('plugin.manager.graphql.interface'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function __construct(EntityTypeManagerInterface $entityTypeManager, PluginManagerInterface $interfacePluginManager, EntityTypeBundleInfoInterface $entityTypeBundleInfo) { + parent::__construct($entityTypeManager, $interfacePluginManager); + $this->entityTypeBundleInfo = $entityTypeBundleInfo; + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($basePluginDefinition) { + if ($this->entityTypeManager->hasDefinition('view')) { + $viewStorage = $this->entityTypeManager->getStorage('view'); + + foreach (Views::getApplicableViews('graphql_display') as [$viewId, $displayId]) { + /** @var \Drupal\views\ViewEntityInterface $view */ + $view = $viewStorage->load($viewId); + if (!$this->getRowResolveType($view, $displayId)) { + continue; + } + + /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ + $display = $this->getViewDisplay($view, $displayId); + $arg_options = $display->getOption('arguments'); + + if (count($arg_options) == 1) { + $arg_option = reset($arg_options); + + if (!empty($arg_option['validate']['type']) && strpos($arg_option['validate']['type'], ':') !== FALSE) { + [$type, $entityTypeId] = explode(':', $arg_option['validate']['type']); + if ($type == 'entity' && isset($entityTypeId)) { + $entityType = $this->entityTypeManager->getDefinition($entityTypeId); + $supportsBundles = $entityType->hasKey('bundle'); + $bundles = $supportsBundles && isset($arg_option['validate_options']['bundles']) ? array_values($arg_option['validate_options']['bundles']) : []; + $id = implode('-', [$viewId, $displayId, 'sub-view']); + $arguments = []; + $arguments += $this->getPagerArguments($display); + $arguments += $this->getSortArguments($display, $id); + $arguments += $this->getFilterArguments($display, $id); + + $parents = []; + if (empty($bundles)) { + $parents[] = StringHelper::camelCase($entityTypeId); + + if ($supportsBundles) { + $bundleInfo = array_keys($this->entityTypeBundleInfo->getBundleInfo($entityTypeId)); + foreach ($bundleInfo as $bundle) { + $parents[] = StringHelper::camelCase($entityTypeId, $bundle); + } + } + } + else { + foreach ($bundles as $bundle) { + $parents[] = StringHelper::camelCase($entityTypeId, $bundle); + } + } + + $this->derivatives[$id] = [ + 'id' => $id, + 'name' => $display->getGraphQLQueryName(), + 'type' => $display->getGraphQLResultName(), + 'parents' => $parents, + 'arguments' => $arguments, + 'view' => $viewId, + 'display' => $displayId, + 'paged' => $this->isPaged($display), + ] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition; + } + } + } + } + } + + return parent::getDerivativeDefinitions($basePluginDefinition); + } + +} diff --git a/src/Plugin/GraphQL/Fields/SubView.php b/src/Plugin/GraphQL/Fields/SubView.php new file mode 100644 index 0000000..9875aeb --- /dev/null +++ b/src/Plugin/GraphQL/Fields/SubView.php @@ -0,0 +1,201 @@ +entityTypeManager = $entityTypeManager; + parent::__construct($configuration, $pluginId, $pluginDefinition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) { + return new static( + $configuration, + $pluginId, + $pluginDefinition, + $container->get('entity_type.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) { + $storage = $this->entityTypeManager->getStorage('view'); + $definition = $this->getPluginDefinition(); + + /** @var \Drupal\views\Entity\View $view */ + if ($view = $storage->load($definition['view'])) { + $executable = $view->getExecutable(); + $executable->setDisplay($definition['display']); + /** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */ + $display = $executable->getDisplay($definition['display']); + + // a subview can only work on an entity, so return null it it is not. + + if (!$value instanceof EntityInterface) { + return; + } + + // Set the first argument to the id of the current entity. + $executable->setArguments([$value->id()]); + + $filters = $executable->getDisplay()->getOption('filters');; + $input = $this->extractExposedInput($value, $args, $filters); + $executable->setExposedInput($input); + + // This is a workaround for the Taxonomy Term filter which requires a full + // exposed form to be sent OR the display being an attachment to just + // accept input values. + $executable->is_attachment = TRUE; + $executable->exposed_raw_input = $input; + + if (!empty($definition['paged'])) { + // Set paging parameters. + $executable->setItemsPerPage($args['pageSize']); + $executable->setCurrentPage($args['page']); + } + + if (isset($args['offset']) && !empty($args['offset'])) { + $executable->setOffset($args['offset']); + } + + $result = $executable->render($definition['display']); + /** @var \Drupal\Core\Cache\CacheableMetadata $cache */ + if ($cache = $result['cache']) { + $cache->setCacheContexts( + array_filter($cache->getCacheContexts(), function ($context) { + // Don't emit the url cache contexts. + return $context !== 'url' && strpos($context, 'url.') !== 0; + }) + ); + } + yield $result; + } + } + + /** + * {@inheritdoc} + */ + protected function getCacheDependencies(array $result, $value, array $args, ResolveContext $context, ResolveInfo $info) { + return array_map(function ($item) { + return $item['cache']; + }, $result); + } + + /** + * Retrieves the contextual filter argument from the parent value or args. + * + * @param $value + * The resolved parent value. + * @param $args + * The arguments provided to the field. + * + * @return array + * An array of arguments containing the contextual filter value from the + * parent or provided args if any. + */ + protected function extractContextualFilters($value, $args) { + $definition = $this->getPluginDefinition(); + $arguments = []; + + foreach ($definition['arguments_info'] as $argumentId => $argumentInfo) { + if (isset($args['contextualFilter'][$argumentId])) { + $arguments[$argumentInfo['index']] = $args['contextualFilter'][$argumentId]; + } + elseif ( + $value instanceof EntityInterface && + $value->getEntityTypeId() === $argumentInfo['entity_type'] && + (empty($argumentInfo['bundles']) || + in_array($value->bundle(), $argumentInfo['bundles'], TRUE)) + ) { + $arguments[$argumentInfo['index']] = $value->id(); + } + else { + $arguments[$argumentInfo['index']] = NULL; + } + } + + return $arguments; + } + + /** + * Retrieves sort and filter arguments from the provided field args. + * + * @param $value + * The resolved parent value. + * @param $args + * The array of arguments provided to the field. + * @param $filters + * The available filters for the configured view. + * + * @return array + * The array of sort and filter arguments to execute the view with. + */ + protected function extractExposedInput($value, $args, $filters) { + // Prepare arguments for use as exposed form input. + $input = array_filter([ + // Sorting arguments. + 'sort_by' => isset($args['sortBy']) ? $args['sortBy'] : NULL, + 'sort_order' => isset($args['sortDirection']) ? $args['sortDirection'] : NULL, + ]); + + // If some filters are missing from the input, set them to an empty string + // explicitly. Otherwise views module generates "Undefined index" notice. + foreach ($filters as $filterKey => $filterRow) { + if (!isset($filterRow['expose']['identifier'])) { + continue; + } + + $inputKey = $filterRow['expose']['identifier']; + if (!isset($args['filter'][$inputKey])) { + $input[$inputKey] = $filterRow['value']; + } else { + $input[$inputKey] = $args['filter'][$inputKey]; + } + } + + return $input; + } + +}