Skip to content

Commit

Permalink
Create new subView to allow embedding a view inside and entity
Browse files Browse the repository at this point in the history
  • Loading branch information
gheydon committed May 8, 2020
1 parent a78f8fd commit 2543156
Show file tree
Hide file tree
Showing 2 changed files with 310 additions and 0 deletions.
109 changes: 109 additions & 0 deletions src/Plugin/Deriver/Fields/SubViewDeriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Drupal\graphql_views\Plugin\Deriver\Fields;

use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\graphql\Utility\StringHelper;
use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase;
use Drupal\views\Views;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Derive fields from configured views.
*/
class SubViewDeriver extends ViewDeriverBase implements ContainerDeriverInterface {

protected $entityTypeBundleInfo;

/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $basePluginId) {
return new static(
$container->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);
}

}
201 changes: 201 additions & 0 deletions src/Plugin/GraphQL/Fields/SubView.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
<?php

namespace Drupal\graphql_views\Plugin\GraphQL\Fields;

use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\graphql\GraphQL\Execution\ResolveContext;
use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
use GraphQL\Type\Definition\ResolveInfo;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
* Expose views as root fields.
*
* @GraphQLField(
* id = "subview",
* secure = true,
* parents = {"Root"},
* provider = "views",
* deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\SubViewDeriver"
* )
*/
class SubView extends FieldPluginBase implements ContainerFactoryPluginInterface {
use DependencySerializationTrait;

/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;

/**
* {@inheritdoc}
*/
public function __construct(
array $configuration,
$pluginId,
$pluginDefinition,
EntityTypeManagerInterface $entityTypeManager
) {
$this->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;
}

}

0 comments on commit 2543156

Please sign in to comment.