@amazeelabs/silverback-gatsby 3.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/drupal/silverback_gatsby/.prettierignore +1 -0
  3. package/drupal/silverback_gatsby/CHANGELOG.md +1216 -0
  4. package/drupal/silverback_gatsby/README.md +297 -0
  5. package/drupal/silverback_gatsby/composer.json +15 -0
  6. package/drupal/silverback_gatsby/directives.gql +63 -0
  7. package/drupal/silverback_gatsby/directives.graphql +62 -0
  8. package/drupal/silverback_gatsby/drush.services.yml +9 -0
  9. package/drupal/silverback_gatsby/graphql/entity.directive.graphqls +2 -0
  10. package/drupal/silverback_gatsby/graphql/menu.directive.graphqls +17 -0
  11. package/drupal/silverback_gatsby/graphql/silverback_gatsby.base.graphqls +25 -0
  12. package/drupal/silverback_gatsby/graphql/silverback_gatsby.extension.graphqls +10 -0
  13. package/drupal/silverback_gatsby/graphql/stringTranslation.directive.graphqls +4 -0
  14. package/drupal/silverback_gatsby/graphql/translatableString.directive.graphqls +2 -0
  15. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/directives.gql +6 -0
  16. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/graphql/.graphqlrc.json +4 -0
  17. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/graphql/silverback_gatsby_example.graphqls +39 -0
  18. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/silverback_gatsby_example.info.yml +7 -0
  19. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/silverback_gatsby_example.module +16 -0
  20. package/drupal/silverback_gatsby/modules/silverback_gatsby_example/src/Directives.php +24 -0
  21. package/drupal/silverback_gatsby/silverback_gatsby.info.yml +8 -0
  22. package/drupal/silverback_gatsby/silverback_gatsby.install +180 -0
  23. package/drupal/silverback_gatsby/silverback_gatsby.links.task.yml +6 -0
  24. package/drupal/silverback_gatsby/silverback_gatsby.module +119 -0
  25. package/drupal/silverback_gatsby/silverback_gatsby.permissions.yml +8 -0
  26. package/drupal/silverback_gatsby/silverback_gatsby.post_update.php +15 -0
  27. package/drupal/silverback_gatsby/silverback_gatsby.routing.yml +28 -0
  28. package/drupal/silverback_gatsby/silverback_gatsby.services.yml +50 -0
  29. package/drupal/silverback_gatsby/src/Annotation/GatsbyFeed.php +41 -0
  30. package/drupal/silverback_gatsby/src/Commands/SilverbackGatsbyCommands.php +102 -0
  31. package/drupal/silverback_gatsby/src/Controller/BuildController.php +45 -0
  32. package/drupal/silverback_gatsby/src/Controller/PublisherController.php +34 -0
  33. package/drupal/silverback_gatsby/src/Directives.php +51 -0
  34. package/drupal/silverback_gatsby/src/GatsbyBuildTrigger.php +230 -0
  35. package/drupal/silverback_gatsby/src/GatsbyBuildTriggerInterface.php +52 -0
  36. package/drupal/silverback_gatsby/src/GatsbyUpdate.php +38 -0
  37. package/drupal/silverback_gatsby/src/GatsbyUpdateHandler.php +181 -0
  38. package/drupal/silverback_gatsby/src/GatsbyUpdateTracker.php +98 -0
  39. package/drupal/silverback_gatsby/src/GatsbyUpdateTrackerInterface.php +59 -0
  40. package/drupal/silverback_gatsby/src/GatsbyUpdateTrigger.php +69 -0
  41. package/drupal/silverback_gatsby/src/GatsbyUpdateTriggerInterface.php +22 -0
  42. package/drupal/silverback_gatsby/src/GraphQL/Build.php +235 -0
  43. package/drupal/silverback_gatsby/src/GraphQL/ComposableSchema.php +73 -0
  44. package/drupal/silverback_gatsby/src/LocaleStorageDecorator.php +181 -0
  45. package/drupal/silverback_gatsby/src/MenuTreeStorageDecorator.php +139 -0
  46. package/drupal/silverback_gatsby/src/Plugin/FeedBase.php +121 -0
  47. package/drupal/silverback_gatsby/src/Plugin/FeedInterface.php +132 -0
  48. package/drupal/silverback_gatsby/src/Plugin/FeedPluginManager.php +39 -0
  49. package/drupal/silverback_gatsby/src/Plugin/Gatsby/Feed/EntityFeed.php +252 -0
  50. package/drupal/silverback_gatsby/src/Plugin/Gatsby/Feed/MenuFeed.php +292 -0
  51. package/drupal/silverback_gatsby/src/Plugin/Gatsby/Feed/StringTranslationFeed.php +171 -0
  52. package/drupal/silverback_gatsby/src/Plugin/Gatsby/Feed/TranslatableStringFeed.php +128 -0
  53. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/CurrentUserEntity.php +43 -0
  54. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/EntityQueryBase.php +40 -0
  55. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/FetchEntity.php +371 -0
  56. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/FetchString.php +64 -0
  57. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/FetchTranslatableString.php +74 -0
  58. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/FocalPoint.php +47 -0
  59. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/GatsbyBuildId.php +36 -0
  60. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/GatsbyExtractId.php +28 -0
  61. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/GatsbyExtractLangcode.php +28 -0
  62. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/ImageProps.php +149 -0
  63. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/ListEntities.php +88 -0
  64. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/ListStrings.php +87 -0
  65. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/StringId.php +37 -0
  66. package/drupal/silverback_gatsby/src/Plugin/GraphQL/DataProducer/StringTranslations.php +94 -0
  67. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/EntityFetch.php +51 -0
  68. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/EntityTranslationsWithDefault.php +30 -0
  69. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/FocalPoint.php +27 -0
  70. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/ImageProps.php +27 -0
  71. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/MenuLangcode.php +27 -0
  72. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/MenuTranslations.php +36 -0
  73. package/drupal/silverback_gatsby/src/Plugin/GraphQL/Directive/SilverbackGatsbyEntityId.php +39 -0
  74. package/drupal/silverback_gatsby/src/Plugin/GraphQL/SchemaExtension/SilverbackGatsbySchemaExtension.php +384 -0
  75. package/drupal/silverback_gatsby/src/SilverbackGatsbyServiceProvider.php +43 -0
  76. package/drupal/silverback_gatsby/src/SilverbackGatsbySessionConfiguration.php +30 -0
  77. package/drupal/silverback_gatsby/src/SilverbackReverseProxyMiddleware.php +49 -0
  78. package/drupal/silverback_gatsby/tests/queries/create-page-fields.gql +12 -0
  79. package/drupal/silverback_gatsby/tests/queries/current-user.gql +6 -0
  80. package/drupal/silverback_gatsby/tests/queries/feed_info.gql +12 -0
  81. package/drupal/silverback_gatsby/tests/queries/load-entity.gql +5 -0
  82. package/drupal/silverback_gatsby/tests/queries/menus.gql +31 -0
  83. package/drupal/silverback_gatsby/tests/queries/multilingual-menus.gql +11 -0
  84. package/drupal/silverback_gatsby/tests/queries/revisionable-translatable.gql +15 -0
  85. package/drupal/silverback_gatsby/tests/queries/revisionable.gql +8 -0
  86. package/drupal/silverback_gatsby/tests/queries/translatable.gql +14 -0
  87. package/drupal/silverback_gatsby/tests/queries/untranslatable.gql +10 -0
  88. package/drupal/silverback_gatsby/tests/schema/.graphqlrc.json +4 -0
  89. package/drupal/silverback_gatsby/tests/schema/translatable-strings.graphql +7 -0
  90. package/drupal/silverback_gatsby/tests/src/Kernel/CurrentUserTest.php +37 -0
  91. package/drupal/silverback_gatsby/tests/src/Kernel/EntityFeedTest.php +490 -0
  92. package/drupal/silverback_gatsby/tests/src/Kernel/EntityFeedTestBase.php +178 -0
  93. package/drupal/silverback_gatsby/tests/src/Kernel/GatsbyBuildTriggerTest.php +155 -0
  94. package/drupal/silverback_gatsby/tests/src/Kernel/GatsbyFeedInfoTest.php +319 -0
  95. package/drupal/silverback_gatsby/tests/src/Kernel/GatsbyUpdateHandlerTest.php +131 -0
  96. package/drupal/silverback_gatsby/tests/src/Kernel/GatsbyUpdateTrackerTest.php +196 -0
  97. package/drupal/silverback_gatsby/tests/src/Kernel/GatsbyUpdateTriggerTest.php +177 -0
  98. package/drupal/silverback_gatsby/tests/src/Kernel/MenuFeedTest.php +192 -0
  99. package/drupal/silverback_gatsby/tests/src/Kernel/TranslatableStringFeedTest.php +210 -0
  100. package/drupal/silverback_gatsby/tests/src/Traits/NotificationCheckTrait.php +43 -0
  101. package/package.json +15 -0
@@ -0,0 +1,292 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gatsby\Plugin\Gatsby\Feed;
4
+
5
+ use Drupal\content_translation\ContentTranslationManagerInterface;
6
+ use Drupal\Core\Entity\EntityTypeManagerInterface;
7
+ use Drupal\Core\Entity\TranslatableInterface;
8
+ use Drupal\Core\Language\LanguageInterface;
9
+ use Drupal\Core\Language\LanguageManagerInterface;
10
+ use Drupal\Core\Menu\MenuLinkTreeElement;
11
+ use Drupal\Core\Menu\MenuLinkTreeInterface;
12
+ use Drupal\Core\Menu\MenuTreeParameters;
13
+ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
14
+ use Drupal\Core\Session\AccountInterface;
15
+ use Drupal\graphql\GraphQL\Execution\FieldContext;
16
+ use Drupal\graphql\GraphQL\Execution\ResolveContext;
17
+ use Drupal\graphql\GraphQL\Resolver\ResolverInterface;
18
+ use Drupal\graphql\GraphQL\ResolverBuilder;
19
+ use Drupal\graphql\GraphQL\ResolverRegistryInterface;
20
+ use Drupal\graphql_directives\Plugin\GraphQL\DataProducer\FilterMenuItems;
21
+ use Drupal\silverback_gatsby\Annotation\GatsbyFeed;
22
+ use Drupal\silverback_gatsby\Plugin\FeedBase;
23
+ use Drupal\silverback_gatsby\Plugin\GraphQL\DataProducer\GatsbyBuildId;
24
+ use Drupal\system\Entity\Menu;
25
+ use GraphQL\Deferred;
26
+ use GraphQL\Language\AST\DocumentNode;
27
+ use GraphQL\Type\Definition\ResolveInfo;
28
+ use Symfony\Component\DependencyInjection\ContainerInterface;
29
+
30
+ /**
31
+ * Feed plugin that creates Gatsby feeds based on Drupal menus.
32
+ *
33
+ * @GatsbyFeed(
34
+ * id = "menu"
35
+ * )
36
+ */
37
+ class MenuFeed extends FeedBase implements ContainerFactoryPluginInterface {
38
+
39
+ /**
40
+ * The target menu id.
41
+ *
42
+ * @var string | null
43
+ */
44
+ protected $menu_id;
45
+
46
+ /**
47
+ * Internal menu id's. The first one the current user has access to will be
48
+ * picked.
49
+ *
50
+ * @var string[] | null
51
+ */
52
+ protected $menu_ids;
53
+
54
+ /**
55
+ * The maximum menu level.
56
+ *
57
+ * @var int
58
+ */
59
+ protected int $max_level;
60
+
61
+ /**
62
+ * The GraphQL item type used for menu items.
63
+ *
64
+ * @var string | null
65
+ */
66
+ protected $item_type;
67
+
68
+ /**
69
+ * @var \Drupal\content_translation\ContentTranslationManagerInterface|null
70
+ */
71
+ protected ?ContentTranslationManagerInterface $contentTranslationManager;
72
+
73
+ /**
74
+ * @var \Drupal\Core\Language\LanguageManagerInterface
75
+ */
76
+ protected LanguageManagerInterface $languageManager;
77
+
78
+ /**
79
+ * @var \Drupal\Core\Menu\MenuLinkTreeInterface
80
+ */
81
+ protected MenuLinkTreeInterface $menuLinkTree;
82
+
83
+ /**
84
+ * @var \Drupal\Core\Entity\EntityTypeManagerInterface
85
+ */
86
+ protected EntityTypeManagerInterface $entityTypeManager;
87
+
88
+ /**
89
+ * {@inheritDoc}
90
+ */
91
+ public static function create(
92
+ ContainerInterface $container,
93
+ array $configuration,
94
+ $plugin_id,
95
+ $plugin_definition
96
+ ) {
97
+ return new static(
98
+ $configuration,
99
+ $plugin_id,
100
+ $plugin_definition,
101
+ $container->has('content_translation.manager')
102
+ ? $container->get('content_translation.manager')
103
+ : NULL,
104
+ $container->get('menu.link_tree'),
105
+ $container->get('language_manager'),
106
+ $container->get('entity_type.manager')
107
+ );
108
+ }
109
+
110
+ public function __construct(
111
+ $config,
112
+ $plugin_id,
113
+ $plugin_definition,
114
+ ?ContentTranslationManagerInterface $contentTranslationManager,
115
+ MenuLinkTreeInterface $menuLinkTree,
116
+ LanguageManagerInterface $languageManager,
117
+ EntityTypeManagerInterface $entityTypeManager
118
+ ) {
119
+ $this->menu_id = $config['menu_id'] ?? NULL;
120
+ $this->menu_ids = $config['menu_ids'] ?? NULL;
121
+ $this->max_level = $config['max_level'] ?? -1;
122
+ $this->item_type = $config['item_type'] ?? NULL;
123
+ $this->contentTranslationManager = $contentTranslationManager;
124
+ $this->menuLinkTree = $menuLinkTree;
125
+ $this->languageManager = $languageManager;
126
+ $this->entityTypeManager = $entityTypeManager;
127
+
128
+ parent::__construct(
129
+ $config,
130
+ $plugin_id,
131
+ $plugin_definition
132
+ );
133
+ }
134
+
135
+ /**
136
+ * Expose the configured max level so it can be used by the schema extension.
137
+ *
138
+ * @return int|mixed
139
+ */
140
+ public function getMaxLevel() {
141
+ return $this->max_level;
142
+ }
143
+
144
+ /**
145
+ * The menu ids to be loaded and negotiated.
146
+ *
147
+ * @return string[]
148
+ */
149
+ public function menuIds() {
150
+ $ids = $this->menu_ids ?: [];
151
+ if ($this->menu_id) {
152
+ $ids[] = $this->menu_id;
153
+ }
154
+ return $ids;
155
+ }
156
+
157
+ /**
158
+ * {@inheritDoc}
159
+ */
160
+ public function isTranslatable(): bool {
161
+ return $this->contentTranslationManager &&
162
+ $this->contentTranslationManager->isEnabled('menu_link_content', 'menu_link_content');
163
+ }
164
+
165
+ /**
166
+ * {@inheritDoc}
167
+ */
168
+ public function getUpdateIds($context, ?AccountInterface $account) : array {
169
+ $params = new MenuTreeParameters();
170
+ if ($this->max_level > 0) {
171
+ $params->maxDepth = $this->max_level;
172
+ }
173
+
174
+ // Get all menus that are associated with this feed, and pick the first one
175
+ // the account has access to.
176
+ /** @var \Drupal\system\Entity\Menu[] $menus */
177
+ $menus = $this->entityTypeManager->getStorage('menu')->loadMultiple($this->menuIds());
178
+ $relevantMenu = NULL;
179
+ foreach($menus as $menu) {
180
+ if (!$account || $menu->access('view label', $account)) {
181
+ $relevantMenu = $menu;
182
+ break;
183
+ }
184
+ }
185
+
186
+ if (!$relevantMenu) {
187
+ return [];
188
+ }
189
+
190
+ $tree = $this->menuLinkTree->load($relevantMenu->id(), $params);
191
+ $items = FilterMenuItems::flatten($tree, -1);
192
+
193
+ $ids = [$context];
194
+ $match = count(array_filter($items, function (MenuLinkTreeElement $item) use ($ids) {
195
+ return in_array($item->link->getPluginId(), $ids);
196
+ })) > 0;
197
+
198
+ // If the menu item is not in any exposed menus, don't return any updates.
199
+ if (!$match) {
200
+ return [];
201
+ }
202
+
203
+ if ($this->isTranslatable()) {
204
+ // If menu items are translatable, trigger an update in each language,
205
+ // since we can't determine easily which language was affected.
206
+ return array_map(
207
+ fn (LanguageInterface $lang) =>
208
+ GatsbyBuildId::build($relevantMenu->id(), $lang->getId())
209
+ , $this->languageManager->getLanguages());
210
+ }
211
+ // Else, simply return the menu id.
212
+ return [$relevantMenu->id()];
213
+ }
214
+
215
+ /**
216
+ * {@inheritDoc}
217
+ */
218
+ public function resolveItem(ResolverInterface $id, ?ResolverInterface $langcode = null): ResolverInterface {
219
+ $resolver = $this->builder->produce('entity_load')
220
+ ->map('type', $this->builder->fromValue('menu'))
221
+ ->map('id', $id)
222
+ ->map('access', $this->builder->fromValue(true))
223
+ ->map('access_operation', $this->builder->fromValue('view label'));
224
+ if ($this->isTranslatable() && $langcode) {
225
+ $resolver->map('language', $langcode);
226
+ $resolver = $this->builder->compose(
227
+ $this->builder->context('current_menu_language', $langcode),
228
+ $resolver,
229
+ $this->builder->callback(function ($value, $args, ResolveContext $context, ResolveInfo $info, FieldContext $fieldContext) {
230
+ $langcode = $fieldContext->getContextValue('current_menu_language');
231
+ if ($langcode && $langcode !== $value->language()->getId()) {
232
+ $clone = clone $value;
233
+ $clone->set('langcode', $langcode);
234
+ return $clone;
235
+ }
236
+ return $value;
237
+ })
238
+ );
239
+ }
240
+ return $resolver;
241
+ }
242
+
243
+ /**
244
+ * {@inheritDoc}
245
+ */
246
+ public function resolveItems(ResolverInterface $limit, ResolverInterface $offset): ResolverInterface {
247
+ return $this->builder->produce('entity_load_multiple')
248
+ ->map('type', $this->builder->fromValue('menu'))
249
+ ->map('ids', $this->builder->fromValue($this->menuIds()))
250
+ ->map('access', $this->builder->fromValue(true))
251
+ ->map('access_operation', $this->builder->fromValue('view label'));
252
+ }
253
+
254
+ /**
255
+ * {@inheritDoc}
256
+ */
257
+ public function resolveId(): ResolverInterface {
258
+ return $this->builder->produce('entity_id')
259
+ ->map('entity', $this->builder->fromParent());
260
+ }
261
+
262
+ /**
263
+ * {@inheritDoc}
264
+ */
265
+ public function resolveLangcode(): ResolverInterface {
266
+ return $this->builder->callback(
267
+ fn(Menu $value) => $value->language()->getId()
268
+ );
269
+ }
270
+
271
+ /**
272
+ * {@inheritDoc}
273
+ */
274
+ public function resolveDefaultTranslation(): ResolverInterface {
275
+ return $this->builder->callback(
276
+ fn(Menu $value) => $value->language()->isDefault()
277
+ );
278
+ }
279
+
280
+ /**
281
+ * {@inheritDoc}
282
+ */
283
+ public function resolveTranslations(): ResolverInterface {
284
+ return $this->builder->compose($this->builder->callback(function (Menu $menu) {
285
+ return array_map(function (LanguageInterface $lang) use ($menu) {
286
+ $clone = clone $menu;
287
+ $clone->set('langcode', $lang->getId());
288
+ return $clone;
289
+ }, $this->languageManager->getLanguages());
290
+ }));
291
+ }
292
+ }
@@ -0,0 +1,171 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gatsby\Plugin\Gatsby\Feed;
4
+
5
+ use Drupal\Core\Session\AccountInterface;
6
+ use Drupal\graphql\GraphQL\Resolver\ResolverInterface;
7
+ use Drupal\graphql\GraphQL\ResolverBuilder;
8
+ use Drupal\graphql\GraphQL\ResolverRegistryInterface;
9
+ use Drupal\locale\SourceString;
10
+ use Drupal\locale\StringInterface;
11
+ use Drupal\locale\TranslationString;
12
+ use Drupal\silverback_gatsby\Plugin\FeedBase;
13
+ use GraphQL\Language\AST\DocumentNode;
14
+
15
+ /**
16
+ * Feed plugin that creates Gatsby feeds based on Drupal string translations.
17
+ *
18
+ * DEPRECATED: Use @translatableString instead, when working with a directive
19
+ * based schema.
20
+ *
21
+ * @GatsbyFeed(
22
+ * id = "stringTranslation"
23
+ * )
24
+ */
25
+ class StringTranslationFeed extends FeedBase {
26
+
27
+ protected $contextPrefix;
28
+
29
+ public function __construct(
30
+ $config,
31
+ $plugin_id,
32
+ $plugin_definition
33
+ ) {
34
+ $this->contextPrefix = $config['contextPrefix'];
35
+ parent::__construct($config, $plugin_id, $plugin_definition);
36
+ }
37
+
38
+ public function getTranslationTypeName() {
39
+ return $this->getTypeName() . 'Translation';
40
+ }
41
+
42
+ /**
43
+ * {@inheritDoc}
44
+ */
45
+ function getUpdateIds($context, ?AccountInterface $account): array {
46
+ if (!$context instanceof StringInterface) {
47
+ return [];
48
+ }
49
+
50
+ // If the string has the 'context' property, we only return its id if it has
51
+ // the context prefix match. If there's no context property, then we return
52
+ // its id in any case.
53
+ if (!isset($context->context) || empty($this->contextPrefix)) {
54
+ return [$context->getId()];
55
+ }
56
+ if (strpos($context->context, $this->contextPrefix) === 0) {
57
+ return [$context->getId()];
58
+ }
59
+ return [];
60
+ }
61
+
62
+ /**
63
+ * {@inheritDoc}
64
+ */
65
+ public function isTranslatable(): bool {
66
+ return FALSE;
67
+ }
68
+
69
+ /**
70
+ * {@inheritDoc}
71
+ */
72
+ public function resolveId(): ResolverInterface {
73
+ return $this->builder->produce('string_id')
74
+ ->map('string', $this->builder->fromParent());
75
+ }
76
+
77
+ /**
78
+ * {@inheritDoc}
79
+ */
80
+ public function resolveLangcode(): ResolverInterface {
81
+ return $this->builder->callback(
82
+ fn(StringInterface $value) => $value instanceof TranslationString ? $value->language : ''
83
+ );
84
+ }
85
+
86
+ /**
87
+ * {@inheritDoc}
88
+ */
89
+ public function resolveDefaultTranslation(): ResolverInterface {
90
+ return $this->builder->callback(
91
+ fn(StringInterface $value) => FALSE
92
+ );
93
+ }
94
+
95
+ /**
96
+ * {@inheritDoc}
97
+ */
98
+ public function resolveTranslations(): ResolverInterface {
99
+ return $this->builder->produce('string_translations')
100
+ ->map('sourceString', $this->builder->fromParent());
101
+ }
102
+
103
+ public function resolveItems(ResolverInterface $limit, ResolverInterface $offset): ResolverInterface {
104
+ return $this->builder->produce('list_strings')
105
+ ->map('offset', $this->builder->defaultValue(
106
+ $this->builder->fromArgument('offset'),
107
+ $this->builder->fromValue(0)
108
+ ))
109
+ ->map('limit', $this->builder->defaultValue(
110
+ $this->builder->fromArgument('limit'),
111
+ $this->builder->fromValue(10),
112
+ ))
113
+ // For now, the schema extension does not allow array arguments. However,
114
+ // the list strings resolver can do that, so we just convert the context
115
+ // prefix to an array here. By doing that, when we add support for array
116
+ // arguments in directives, we don't have to change this code.
117
+ ->map('translationContext', $this->builder->fromValue(is_array($this->contextPrefix) ? $this->contextPrefix : [$this->contextPrefix]));
118
+ }
119
+
120
+ /**
121
+ * {@inheritDoc}
122
+ */
123
+ public function resolveItem(ResolverInterface $id, ?ResolverInterface $langcode = null): ResolverInterface {
124
+ $resolver = $this->builder->produce('fetch_string')
125
+ ->map('id', $id);
126
+ return $resolver;
127
+ }
128
+
129
+ public function getExtensionDefinition(DocumentNode $parentAst): string {
130
+ $typeName = $this->getTypeName();
131
+ $translationTypeName = $this->getTranslationTypeName();
132
+ $def = [];
133
+ $def[] = "extend type $typeName {";
134
+ $def[] = " source: String!";
135
+ $def[] = " context: String";
136
+ $def[] = " translations: [$translationTypeName]";
137
+ $def[] = "}";
138
+ $def[] = "type $translationTypeName {";
139
+ $def[] = " id: String!";
140
+ $def[] = " source: String!";
141
+ $def[] = " langcode: String!";
142
+ $def[] = " translation: String!";
143
+ $def[] = "}";
144
+ return implode("\n", $def);
145
+ }
146
+
147
+ public function addExtensionResolvers(
148
+ ResolverRegistryInterface $registry,
149
+ ResolverBuilder $builder
150
+ ): void {
151
+ $registry->addFieldResolver($this->getTypeName(), 'source', $builder->callback(
152
+ fn (StringInterface $value) => $value->getString()
153
+ ));
154
+ $registry->addFieldResolver($this->getTypeName(), 'context', $builder->callback(
155
+ fn (StringInterface $value) => $value->context
156
+ ));
157
+ $registry->addFieldResolver($this->getTypeName(), 'translations', $this->resolveTranslations());
158
+
159
+ $translationTypeName = $this->getTranslationTypeName();
160
+ $registry->addFieldResolver($translationTypeName, 'id', $builder->produce('gatsby_build_id')
161
+ ->map('id', $this->resolveId())
162
+ ->map('langcode', $this->resolveLangcode()));
163
+ $registry->addFieldResolver($translationTypeName, 'source', $builder->callback(
164
+ fn (StringInterface $value) => $value->source
165
+ ));
166
+ $registry->addFieldResolver($translationTypeName, 'langcode', $this->resolveLangcode());
167
+ $registry->addFieldResolver($translationTypeName, 'translation', $builder->callback(
168
+ fn (StringInterface $value) => $value->getString()
169
+ ));
170
+ }
171
+ }
@@ -0,0 +1,128 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gatsby\Plugin\Gatsby\Feed;
4
+
5
+ use Drupal\Core\Session\AccountInterface;
6
+ use Drupal\graphql\GraphQL\Resolver\ResolverInterface;
7
+ use Drupal\locale\StringInterface;
8
+ use Drupal\locale\TranslationString;
9
+ use Drupal\silverback_gatsby\Plugin\FeedBase;
10
+
11
+ /**
12
+ * Feed plugin that sources string translations into Gatsby.
13
+ *
14
+ * @GatsbyFeed(
15
+ * id = "translatableString"
16
+ * )
17
+ */
18
+ class TranslatableStringFeed extends FeedBase {
19
+
20
+ /**
21
+ * The context prefix, matching the drupal translation context.
22
+ */
23
+ protected string $contextPrefix;
24
+
25
+ /**
26
+ * {@inheritDoc}
27
+ */
28
+ public function __construct(
29
+ $config,
30
+ $plugin_id,
31
+ $plugin_definition
32
+ ) {
33
+ $this->contextPrefix = $config['contextPrefix'];
34
+ parent::__construct($config, $plugin_id, $plugin_definition);
35
+ }
36
+
37
+ /**
38
+ * {@inheritDoc}
39
+ */
40
+ public function getUpdateIds($context, ?AccountInterface $account): array {
41
+ if (!$context instanceof StringInterface) {
42
+ return [];
43
+ }
44
+
45
+ // If the string has the 'context' property, we only return its id if it has
46
+ // the context prefix match. If there's no context property, then we return
47
+ // its id in any case.
48
+ if (!isset($context->context) || empty($this->contextPrefix)) {
49
+ return [$context->getId()];
50
+ }
51
+ if (strpos($context->context, $this->contextPrefix) === 0) {
52
+ return [$context->getId()];
53
+ }
54
+ return [];
55
+ }
56
+
57
+ /**
58
+ * {@inheritDoc}
59
+ */
60
+ public function isTranslatable(): bool {
61
+ return TRUE;
62
+ }
63
+
64
+ /**
65
+ * {@inheritDoc}
66
+ */
67
+ public function resolveId(): ResolverInterface {
68
+ return $this->builder->produce('string_id')
69
+ ->map('string', $this->builder->fromParent());
70
+ }
71
+
72
+ /**
73
+ * {@inheritDoc}
74
+ */
75
+ public function resolveLangcode(): ResolverInterface {
76
+ return $this->builder->callback(
77
+ fn(StringInterface $value) => $value instanceof TranslationString ? $value->language : 'en'
78
+ );
79
+ }
80
+
81
+ /**
82
+ * {@inheritDoc}
83
+ */
84
+ public function resolveDefaultTranslation(): ResolverInterface {
85
+ return $this->builder->callback(
86
+ fn(StringInterface $value) => FALSE
87
+ );
88
+ }
89
+
90
+ /**
91
+ * {@inheritDoc}
92
+ */
93
+ public function resolveTranslations(): ResolverInterface {
94
+ return $this->builder->produce('string_translations')
95
+ ->map('sourceString', $this->builder->fromParent());
96
+ }
97
+
98
+ /**
99
+ * {@inheritDoc}
100
+ */
101
+ public function resolveItems(ResolverInterface $limit, ResolverInterface $offset): ResolverInterface {
102
+ return $this->builder->produce('list_strings')
103
+ ->map('offset', $this->builder->defaultValue(
104
+ $this->builder->fromArgument('offset'),
105
+ $this->builder->fromValue(0)
106
+ ))
107
+ ->map('limit', $this->builder->defaultValue(
108
+ $this->builder->fromArgument('limit'),
109
+ $this->builder->fromValue(10),
110
+ ))
111
+ // For now, the schema extension does not allow array arguments. However,
112
+ // the list strings resolver can do that, so we just convert the context
113
+ // prefix to an array here. By doing that, when we add support for array
114
+ // arguments in directives, we don't have to change this code.
115
+ ->map('translationContext', $this->builder->fromValue(is_array($this->contextPrefix) ? $this->contextPrefix : [$this->contextPrefix]));
116
+ }
117
+
118
+ /**
119
+ * {@inheritDoc}
120
+ */
121
+ public function resolveItem(ResolverInterface $id, ?ResolverInterface $langcode = NULL): ResolverInterface {
122
+ $resolver = $this->builder->produce('fetch_translatable_string')
123
+ ->map('id', $id)
124
+ ->map('language', $langcode);
125
+ return $resolver;
126
+ }
127
+
128
+ }
@@ -0,0 +1,43 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gatsby\Plugin\GraphQL\DataProducer;
4
+
5
+ use Drupal\Core\Session\AccountProxyInterface;
6
+ use Drupal\graphql\GraphQL\Execution\FieldContext;
7
+ use Drupal\graphql\Plugin\GraphQL\DataProducer\User\CurrentUser;
8
+ use Drupal\user\Entity\User;
9
+ use Drupal\user\UserInterface;
10
+
11
+ /**
12
+ * @DataProducer(
13
+ * id = "current_user_entity",
14
+ * name = @Translation("Current User Entity"),
15
+ * description = @Translation("Returns the current authenticated user. Extends the core current_user, to return the User entity instead of the AccountProxy."),
16
+ * produces = @ContextDefinition("User",
17
+ * label = @Translation("User")
18
+ * )
19
+ * )
20
+ */
21
+ class CurrentUserEntity extends CurrentUser {
22
+
23
+ /**
24
+ * Returns the current user.
25
+ *
26
+ * @param \Drupal\graphql\GraphQL\Execution\FieldContext $field_context
27
+ * Field context.
28
+ *
29
+ * @return \Drupal\user\UserInterface
30
+ * The current user.
31
+ */
32
+ public function resolve(FieldContext $field_context): UserInterface {
33
+ // Response must be cached based on current user as a cache context,
34
+ // otherwise a new user would become a previous user.
35
+ $field_context->addCacheableDependency($this->currentUser);
36
+ $user = NULL;
37
+ if ($this->currentUser instanceof AccountProxyInterface) {
38
+ $user = User::load($this->currentUser->id());
39
+ }
40
+ return $user;
41
+ }
42
+
43
+ }
@@ -0,0 +1,40 @@
1
+ <?php
2
+
3
+ namespace Drupal\silverback_gatsby\Plugin\GraphQL\DataProducer;
4
+
5
+ use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
6
+ use Drupal\Core\Entity\Query\QueryInterface;
7
+ use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
8
+
9
+ class EntityQueryBase extends DataProducerPluginBase {
10
+
11
+ protected function getQuery(string $type, RefinableCacheableDependencyInterface $metadata): QueryInterface {
12
+ $currentUser = \Drupal::currentUser();
13
+ $entityType = \Drupal::entityTypeManager()->getDefinition($type);
14
+
15
+ $query = \Drupal::entityQuery($type)
16
+ ->currentRevision()
17
+ ->accessCheck();
18
+
19
+ // Add access conditions. Because accessCheck() does nothing by default. For
20
+ // example it does not filter out unpublished nodes for anonymous users.
21
+ // Special permission for nodes.
22
+ if ($type === 'node' && !$currentUser->hasPermission('bypass node access')) {
23
+ $query->condition('status', 1);
24
+ }
25
+ // Admin permission for other entity types.
26
+ if (
27
+ ($publishedKey = $entityType->getKey('published')) &&
28
+ ($adminPermission = $entityType->getAdminPermission()) &&
29
+ !$currentUser->hasPermission($adminPermission)
30
+ ) {
31
+ $query->condition($publishedKey, 1);
32
+ }
33
+
34
+ $metadata->addCacheTags($entityType->getListCacheTags());
35
+ $metadata->addCacheContexts($entityType->getListCacheContexts());
36
+
37
+ return $query;
38
+ }
39
+
40
+ }