Skip to content

Commit 24b8936

Browse files
committed
Create new subView to allow embedding a view inside and entity
1 parent a78f8fd commit 24b8936

File tree

2 files changed

+310
-0
lines changed

2 files changed

+310
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Drupal\graphql_views\Plugin\Deriver\Fields;
4+
5+
use Drupal\Component\Plugin\PluginManagerInterface;
6+
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
7+
use Drupal\Core\Entity\EntityTypeManagerInterface;
8+
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
9+
use Drupal\graphql\Utility\StringHelper;
10+
use Drupal\graphql_views\Plugin\Deriver\ViewDeriverBase;
11+
use Drupal\views\Views;
12+
use Symfony\Component\DependencyInjection\ContainerInterface;
13+
14+
/**
15+
* Derive fields from configured views.
16+
*/
17+
class SubViewDeriver extends ViewDeriverBase implements ContainerDeriverInterface {
18+
19+
protected $entityTypeBundleInfo;
20+
21+
/**
22+
* {@inheritdoc}
23+
*/
24+
public static function create(ContainerInterface $container, $basePluginId) {
25+
return new static(
26+
$container->get('entity_type.manager'),
27+
$container->get('plugin.manager.graphql.interface'),
28+
$container->get('entity_type.bundle.info')
29+
);
30+
}
31+
32+
/**
33+
* {@inheritdoc}
34+
*/
35+
public function __construct(EntityTypeManagerInterface $entityTypeManager, PluginManagerInterface $interfacePluginManager, EntityTypeBundleInfoInterface $entityTypeBundleInfo) {
36+
parent::__construct($entityTypeManager, $interfacePluginManager);
37+
$this->entityTypeBundleInfo = $entityTypeBundleInfo;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function getDerivativeDefinitions($basePluginDefinition) {
44+
if ($this->entityTypeManager->hasDefinition('view')) {
45+
$viewStorage = $this->entityTypeManager->getStorage('view');
46+
47+
foreach (Views::getApplicableViews('graphql_display') as [$viewId, $displayId]) {
48+
/** @var \Drupal\views\ViewEntityInterface $view */
49+
$view = $viewStorage->load($viewId);
50+
if (!$this->getRowResolveType($view, $displayId)) {
51+
continue;
52+
}
53+
54+
/** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
55+
$display = $this->getViewDisplay($view, $displayId);
56+
$arg_options = $display->getOption('arguments');
57+
58+
if (count($arg_options) == 1) {
59+
$arg_option = reset($arg_options);
60+
61+
if (!empty($arg_option['validate'])) {
62+
[$type, $entityTypeId] = explode(':', $arg_option['validate']['type']);
63+
if ($type == 'entity' && isset($entityTypeId)) {
64+
$entityType = $this->entityTypeManager->getDefinition($entityTypeId);
65+
$supportsBundles = $entityType->hasKey('bundle');
66+
$bundles = $supportsBundles && isset($arg_option['validate_options']['bundles']) ? array_values($arg_option['validate_options']['bundles']) : [];
67+
$id = implode('-', [$viewId, $displayId, 'sub-view']);
68+
$arguments = [];
69+
$arguments += $this->getPagerArguments($display);
70+
$arguments += $this->getSortArguments($display, $id);
71+
$arguments += $this->getFilterArguments($display, $id);
72+
73+
$parents = [];
74+
if (empty($bundles)) {
75+
$parents[] = StringHelper::camelCase($entityTypeId);
76+
77+
if ($supportsBundles) {
78+
$bundleInfo = array_keys($this->entityTypeBundleInfo->getBundleInfo($entityTypeId));
79+
foreach ($bundleInfo as $bundle) {
80+
$parents[] = StringHelper::camelCase($entityTypeId, $bundle);
81+
}
82+
}
83+
}
84+
else {
85+
foreach ($bundles as $bundle) {
86+
$parents[] = StringHelper::camelCase($entityTypeId, $bundle);
87+
}
88+
}
89+
90+
$this->derivatives[$id] = [
91+
'id' => $id,
92+
'name' => $display->getGraphQLQueryName(),
93+
'type' => $display->getGraphQLResultName(),
94+
'parents' => $parents,
95+
'arguments' => $arguments,
96+
'view' => $viewId,
97+
'display' => $displayId,
98+
'paged' => $this->isPaged($display),
99+
] + $this->getCacheMetadataDefinition($view, $display) + $basePluginDefinition;
100+
}
101+
}
102+
}
103+
}
104+
}
105+
106+
return parent::getDerivativeDefinitions($basePluginDefinition);
107+
}
108+
109+
}

src/Plugin/GraphQL/Fields/SubView.php

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php
2+
3+
namespace Drupal\graphql_views\Plugin\GraphQL\Fields;
4+
5+
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
6+
use Drupal\Core\Entity\EntityInterface;
7+
use Drupal\Core\Entity\EntityTypeManagerInterface;
8+
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
9+
use Drupal\graphql\GraphQL\Execution\ResolveContext;
10+
use Drupal\graphql\Plugin\GraphQL\Fields\FieldPluginBase;
11+
use GraphQL\Type\Definition\ResolveInfo;
12+
use Symfony\Component\DependencyInjection\ContainerInterface;
13+
14+
/**
15+
* Expose views as root fields.
16+
*
17+
* @GraphQLField(
18+
* id = "subview",
19+
* secure = true,
20+
* parents = {"Root"},
21+
* provider = "views",
22+
* deriver = "Drupal\graphql_views\Plugin\Deriver\Fields\SubViewDeriver"
23+
* )
24+
*/
25+
class SubView extends FieldPluginBase implements ContainerFactoryPluginInterface {
26+
use DependencySerializationTrait;
27+
28+
/**
29+
* The entity type manager.
30+
*
31+
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
32+
*/
33+
protected $entityTypeManager;
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function __construct(
39+
array $configuration,
40+
$pluginId,
41+
$pluginDefinition,
42+
EntityTypeManagerInterface $entityTypeManager
43+
) {
44+
$this->entityTypeManager = $entityTypeManager;
45+
parent::__construct($configuration, $pluginId, $pluginDefinition);
46+
}
47+
48+
/**
49+
* {@inheritdoc}
50+
*/
51+
public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition) {
52+
return new static(
53+
$configuration,
54+
$pluginId,
55+
$pluginDefinition,
56+
$container->get('entity_type.manager')
57+
);
58+
}
59+
60+
/**
61+
* {@inheritdoc}
62+
*/
63+
public function resolveValues($value, array $args, ResolveContext $context, ResolveInfo $info) {
64+
$storage = $this->entityTypeManager->getStorage('view');
65+
$definition = $this->getPluginDefinition();
66+
67+
/** @var \Drupal\views\Entity\View $view */
68+
if ($view = $storage->load($definition['view'])) {
69+
$executable = $view->getExecutable();
70+
$executable->setDisplay($definition['display']);
71+
/** @var \Drupal\graphql_views\Plugin\views\display\GraphQL $display */
72+
$display = $executable->getDisplay($definition['display']);
73+
74+
// a subview can only work on an entity, so return null it it is not.
75+
76+
if (!$value instanceof EntityInterface) {
77+
return;
78+
}
79+
80+
// Set the first argument to the id of the current entity.
81+
$executable->setArguments([$value->id()]);
82+
83+
$filters = $executable->getDisplay()->getOption('filters');;
84+
$input = $this->extractExposedInput($value, $args, $filters);
85+
$executable->setExposedInput($input);
86+
87+
// This is a workaround for the Taxonomy Term filter which requires a full
88+
// exposed form to be sent OR the display being an attachment to just
89+
// accept input values.
90+
$executable->is_attachment = TRUE;
91+
$executable->exposed_raw_input = $input;
92+
93+
if (!empty($definition['paged'])) {
94+
// Set paging parameters.
95+
$executable->setItemsPerPage($args['pageSize']);
96+
$executable->setCurrentPage($args['page']);
97+
}
98+
99+
if (isset($args['offset']) && !empty($args['offset'])) {
100+
$executable->setOffset($args['offset']);
101+
}
102+
103+
$result = $executable->render($definition['display']);
104+
/** @var \Drupal\Core\Cache\CacheableMetadata $cache */
105+
if ($cache = $result['cache']) {
106+
$cache->setCacheContexts(
107+
array_filter($cache->getCacheContexts(), function ($context) {
108+
// Don't emit the url cache contexts.
109+
return $context !== 'url' && strpos($context, 'url.') !== 0;
110+
})
111+
);
112+
}
113+
yield $result;
114+
}
115+
}
116+
117+
/**
118+
* {@inheritdoc}
119+
*/
120+
protected function getCacheDependencies(array $result, $value, array $args, ResolveContext $context, ResolveInfo $info) {
121+
return array_map(function ($item) {
122+
return $item['cache'];
123+
}, $result);
124+
}
125+
126+
/**
127+
* Retrieves the contextual filter argument from the parent value or args.
128+
*
129+
* @param $value
130+
* The resolved parent value.
131+
* @param $args
132+
* The arguments provided to the field.
133+
*
134+
* @return array
135+
* An array of arguments containing the contextual filter value from the
136+
* parent or provided args if any.
137+
*/
138+
protected function extractContextualFilters($value, $args) {
139+
$definition = $this->getPluginDefinition();
140+
$arguments = [];
141+
142+
foreach ($definition['arguments_info'] as $argumentId => $argumentInfo) {
143+
if (isset($args['contextualFilter'][$argumentId])) {
144+
$arguments[$argumentInfo['index']] = $args['contextualFilter'][$argumentId];
145+
}
146+
elseif (
147+
$value instanceof EntityInterface &&
148+
$value->getEntityTypeId() === $argumentInfo['entity_type'] &&
149+
(empty($argumentInfo['bundles']) ||
150+
in_array($value->bundle(), $argumentInfo['bundles'], TRUE))
151+
) {
152+
$arguments[$argumentInfo['index']] = $value->id();
153+
}
154+
else {
155+
$arguments[$argumentInfo['index']] = NULL;
156+
}
157+
}
158+
159+
return $arguments;
160+
}
161+
162+
/**
163+
* Retrieves sort and filter arguments from the provided field args.
164+
*
165+
* @param $value
166+
* The resolved parent value.
167+
* @param $args
168+
* The array of arguments provided to the field.
169+
* @param $filters
170+
* The available filters for the configured view.
171+
*
172+
* @return array
173+
* The array of sort and filter arguments to execute the view with.
174+
*/
175+
protected function extractExposedInput($value, $args, $filters) {
176+
// Prepare arguments for use as exposed form input.
177+
$input = array_filter([
178+
// Sorting arguments.
179+
'sort_by' => isset($args['sortBy']) ? $args['sortBy'] : NULL,
180+
'sort_order' => isset($args['sortDirection']) ? $args['sortDirection'] : NULL,
181+
]);
182+
183+
// If some filters are missing from the input, set them to an empty string
184+
// explicitly. Otherwise views module generates "Undefined index" notice.
185+
foreach ($filters as $filterKey => $filterRow) {
186+
if (!isset($filterRow['expose']['identifier'])) {
187+
continue;
188+
}
189+
190+
$inputKey = $filterRow['expose']['identifier'];
191+
if (!isset($args['filter'][$inputKey])) {
192+
$input[$inputKey] = $filterRow['value'];
193+
} else {
194+
$input[$inputKey] = $args['filter'][$inputKey];
195+
}
196+
}
197+
198+
return $input;
199+
}
200+
201+
}

0 commit comments

Comments
 (0)