diff --git a/composer.json b/composer.json index deebc954fe9ccf8b7d93bdc232675e59e69978d3..bb9f35412c1d9a0891352d6d5994bdb4c266258d 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "drupal/dynamic_entity_reference": "^3", "drupal/eck": "^2", "drupal/geofield": "^1", + "drupal/menu_item_extras": "^3", "drupal/metatag": "^2", "drupal/paragraphs": "^1", "drupal/layout_paragraphs": "^2", diff --git a/modules/graphql_compose_comments/src/CommentableTrait.php b/modules/graphql_compose_comments/src/CommentableTrait.php index 50958cdc266cdba13612814e3c786a8b7115d0ec..371c1ea27cda1c02c7fdf464cdca221833530eec 100644 --- a/modules/graphql_compose_comments/src/CommentableTrait.php +++ b/modules/graphql_compose_comments/src/CommentableTrait.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_comments; use Drupal\Core\Entity\Entity\EntityFormDisplay; diff --git a/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php b/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php index d4e98859e1f642c809e8e69898e52576bf317c24..58baeb30894bb8fd50802f196af77f8b380a3c63 100644 --- a/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php +++ b/modules/graphql_compose_comments/src/Plugin/GraphQL/DataProducer/CreateComment.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_comments\Plugin\GraphQL\DataProducer; use Drupal\comment\CommentInterface; diff --git a/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php b/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php index a03016ccaa30665a04d03220b8a4afb6f99825ca..b07c4f4e3d99d60fa68d06ec7ecda76972baa67b 100644 --- a/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php +++ b/modules/graphql_compose_edges/src/Filters/EdgeFilterBase.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_edges\Filters; use Drupal\Core\Config\ConfigFactoryInterface; diff --git a/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php b/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php index 64dbe3cb3fa6b88f360267e5c38b5b27879ca6ae..94a5aad0e3ee9f1cd20e18afe1de90fca84d5a31 100644 --- a/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php +++ b/modules/graphql_compose_edges/src/Filters/EdgeFilterInterface.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_edges\Filters; use Drupal\Core\Entity\Query\QueryInterface; diff --git a/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php b/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php index 6c6714d08478eedc8e6e51aef5d5223a188f5037..6904cd16fcbe280d9034b5527916ec1c99ddf535 100644 --- a/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php +++ b/modules/graphql_compose_edges/src/Filters/EntityFilterLanguage.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_edges\Filters; use Drupal\Core\Entity\Query\QueryInterface; diff --git a/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php b/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php index f370cca8eb089f955e3663a3e98c7bd4b12ae511..69a7ba1eda3e720193308cca35d0b6a08258fe11 100644 --- a/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php +++ b/modules/graphql_compose_edges/src/Filters/EntityFilterPublished.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_edges\Filters; use Drupal\Core\Entity\Query\QueryInterface; diff --git a/modules/graphql_compose_menus/graphql_compose_menus.module b/modules/graphql_compose_menus/graphql_compose_menus.module index 017791278c41c2be77a5d83ee36472faebc1cce5..37eb55b5b0483b1c2e0d48a7dfdfc54a518a054a 100644 --- a/modules/graphql_compose_menus/graphql_compose_menus.module +++ b/modules/graphql_compose_menus/graphql_compose_menus.module @@ -46,9 +46,29 @@ function graphql_compose_menus_graphql_compose_entity_type_form_alter(array &$fo '%path' => 'route.entity', ]), ]; + + // Get the fields for the menu if menu_item_extras is installed. + // Squashed in here to try and improve UX on the form. + if (\Drupal::moduleHandler()->moduleExists('menu_item_extras')) { + $ml_entity_type = \Drupal::entityTypeManager()->getDefinition('menu_link_content'); + /** @var \Drupal\graphql_compose\Form\SchemaForm $instance */ + $instance = $form_state->getFormObject(); + $instance->buildEntityTypeBundleFields($form, $form_state, $ml_entity_type, $bundle_id); + } } } +/** + * Implements hook_form_FORM_ID_alter(). + * + * Hide the menu link content settings in an attempt to improve UX. + * This is intended to be used with the menu_item_extras module. + */ +function graphql_compose_menus_form_graphql_compose_schema_alter(array &$form, FormStateInterface $form_state) { + unset($form['settings']['menu_link_content']); + unset($form['layout']['entity_tabs']['entity_type__menu_link_content']); +} + /** * Implements hook_config_schema_info_alter(). */ diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php similarity index 55% rename from modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php rename to modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php index 3465bd5819bee0463aeddf3726bd3f77a7eca21e..3f0cf683c35874c3846df37b81b4eb9ab34ce1c3 100644 --- a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlTranslated.php +++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkEntity.php @@ -4,34 +4,34 @@ declare(strict_types=1); namespace Drupal\graphql_compose_menus\Plugin\GraphQL\DataProducer; +use Drupal\Component\Uuid\Uuid; use Drupal\Core\Entity\EntityRepositoryInterface; -use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; -use Drupal\Core\Url; use Drupal\graphql\GraphQL\Buffers\EntityUuidBuffer; use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent; use GraphQL\Deferred; use Symfony\Component\DependencyInjection\ContainerInterface; /** - * Returns the translated URL object of a menu link. + * Returns the menu link content entity of a menu link. * * @DataProducer( - * id = "menu_link_url_translated", - * name = @Translation("Menu link translated url"), - * description = @Translation("Returns the translated URL of a menu link."), + * id = "menu_link_entity", + * name = @Translation("Menu link content entity"), + * description = @Translation("Returns the menu link content of a menu link."), * produces = @ContextDefinition("any", - * label = @Translation("URL"), + * label = @Translation("Menu Link Content entity"), * ), * consumes = { * "link" = @ContextDefinition("any", - * label = @Translation("Menu link"), + * label = @Translation("Menu link tree element") * ), * }, * ) */ -class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerFactoryPluginInterface { +class MenuLinkEntity extends DataProducerPluginBase implements ContainerFactoryPluginInterface { /** * Create the menu link translated URL resolver. @@ -42,8 +42,6 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF * The plugin id. * @param mixed $plugin_definition * The plugin definition. - * @param \Drupal\Core\Extension\ModuleHandlerInterface $moduleHandler - * The module handler service. * @param \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository * The entity repository service. * @param \Drupal\graphql\GraphQL\Buffers\EntityUuidBuffer $entityBuffer @@ -53,7 +51,6 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF array $configuration, $plugin_id, $plugin_definition, - protected ModuleHandlerInterface $moduleHandler, protected EntityRepositoryInterface $entityRepository, protected EntityUuidBuffer $entityBuffer, ) { @@ -68,49 +65,40 @@ class MenuLinkUrlTranslated extends DataProducerPluginBase implements ContainerF $configuration, $plugin_id, $plugin_definition, - $container->get('module_handler'), $container->get('entity.repository'), $container->get('graphql.buffer.entity_uuid'), ); } /** - * Resolve the translated menu link url. + * Resolve the language of the menu item. * * @param \Drupal\Core\Menu\MenuLinkInterface $link - * The menu link to resolve the url off of. + * The menu link plugin to resolve the entity. * - * @return \GraphQL\Deferred|\Drupal\Core\Url - * The Url or the deferred Url. + * @return \GraphQL\Deferred|null + * The menu link content entity or null. */ - public function resolve(MenuLinkInterface $link): Deferred|Url { + public function resolve(MenuLinkInterface $link): ?Deferred { - if (!$this->moduleHandler->moduleExists('translatable_menu_link_uri')) { - return $link->getUrlObject(); + if (!$link instanceof MenuLinkContent) { + return NULL; } - $plugin_id = $link->getBaseId(); $derivative_id = $link->getDerivativeId(); - if ($plugin_id !== 'menu_link_content' || empty($derivative_id)) { - return $link->getUrlObject(); + if (!Uuid::isValid($derivative_id)) { + return NULL; } $resolver = $this->entityBuffer->add('menu_link_content', $derivative_id); - return new Deferred(function () use ($link, $resolver) { + return new Deferred(function () use ($resolver) { if ($entity = $resolver()) { - $translated_entity = $this->entityRepository->getTranslationFromContext($entity); - - /** @var \Drupal\link\Plugin\Field\FieldType\LinkItem $link_item|null */ - $link_item = $translated_entity->link_override->first(); - - if (!$link_item->isEmpty()) { - return $link_item->getUrl(); - } + return $this->entityRepository->getTranslationFromContext($entity); } - return $link->getUrlObject(); + return NULL; }); } diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php new file mode 100644 index 0000000000000000000000000000000000000000..134ae0ac1cc35f2e3d8979ac98f0a2eb83b3f265 --- /dev/null +++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/DataProducer/MenuLinkUrlOverride.php @@ -0,0 +1,52 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\graphql_compose_menus\Plugin\GraphQL\DataProducer; + +use Drupal\Core\Url; +use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\menu_link_content\Entity\MenuLinkContent; + +/** + * Returns the translated URL object of a menu link. + * + * @DataProducer( + * id = "menu_link_url_override", + * name = @Translation("Menu link translated url"), + * description = @Translation("Returns the translated URL of a menu link."), + * produces = @ContextDefinition("any", + * label = @Translation("URL"), + * ), + * consumes = { + * "entity" = @ContextDefinition("any", + * label = @Translation("Menu link content entity"), + * ), + * }, + * ) + */ +class MenuLinkUrlOverride extends DataProducerPluginBase { + + /** + * Resolve the translated menu link url. + * + * @param \Drupal\menu_link_content\Entity\MenuLinkContent $entity + * The menu link content entity to resolve the url off of. + * + * @return \Drupal\Core\Url + * The Url. + */ + public function resolve(MenuLinkContent $entity): Url { + if ($entity->hasField('link_override')) { + /** @var \Drupal\link\LinkItemInterface|null $link_override */ + $link_override = $entity->get('link_override')->first(); + + if ($link_override && !$link_override->isEmpty()) { + return $link_override->getUrl(); + } + } + + return $entity->getUrlObject(); + } + +} diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php b/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php index ac388d2df098165a5774c616cad006ec596ca882..4c1e0509a8391dcae5ed44564c11bec974e09e06 100644 --- a/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php +++ b/modules/graphql_compose_menus/src/Plugin/GraphQL/SchemaExtension/MenusSchemaExtension.php @@ -4,11 +4,13 @@ declare(strict_types=1); namespace Drupal\graphql_compose_menus\Plugin\GraphQL\SchemaExtension; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Menu\MenuLinkInterface; use Drupal\Core\Url; use Drupal\graphql\GraphQL\ResolverBuilder; use Drupal\graphql\GraphQL\ResolverRegistryInterface; use Drupal\graphql_compose\Plugin\GraphQL\SchemaExtension\ResolverOnlySchemaExtensionPluginBase; +use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent; use function Symfony\Component\String\u; @@ -43,6 +45,8 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase { ->map('type', $builder->fromValue('MenuAvailable')) ->map('value', $builder->fromArgument('name')), ), + + $builder->context('menu', $builder->fromParent()), ) ); @@ -69,31 +73,87 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase { // Menu title. $registry->addFieldResolver('MenuItem', 'title', $builder->produce('menu_link_label') - ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())) + ->map('link', $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent())), ); // Menu description. $registry->addFieldResolver('MenuItem', 'description', $builder->produce('menu_link_description') - ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())) + ->map('link', $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent())), ); // Menu url. $registry->addFieldResolver('MenuItem', 'url', $builder->compose( - $builder->produce('menu_link_url_translated') - ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())), + $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent()), + + $builder->cond([ + [ + // Condition: Does the translatable_menu_link_uri module exist? + $builder->callback(function (MenuLinkInterface $link) { + $module_exists = $this->moduleHandler->moduleExists('translatable_menu_link_uri'); + return $module_exists && $link instanceof MenuLinkContent; + }), + + $builder->produce('menu_link_url_override') + ->map('entity', $builder->produce('menu_link_entity') + ->map('link', $builder->fromParent())), + ], [ + // Condition: Default. Url as normal. + $builder->fromValue(TRUE), + + $builder->produce('menu_link_url') + ->map('link', $builder->fromParent()), + ], + ]), $builder->produce('url_path') ->map('url', $builder->fromParent()), ) ); + // Menu link language. + $registry->addFieldResolver('MenuItem', 'langcode', + $builder->compose( + $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent()), + + $builder->cond([ + [ + // Condition: Is the link a MenuLinkContent entity? + $builder->callback(function (MenuLinkInterface $link) { + return $link instanceof MenuLinkContent; + }), + + $builder->produce('entity_language') + ->map('entity', $builder->produce('menu_link_entity') + ->map('link', $builder->fromParent())), + ], [ + // Condition: Default. The parent menu language. + $builder->fromValue(TRUE), + $builder->produce('entity_language') + ->map('entity', $builder->fromContext('menu')), + ], + ]), + + // Match the shape of the language type. + $builder->callback(fn(LanguageInterface $language) => [ + 'id' => $language->getId(), + 'name' => $language->getName(), + 'direction' => $language->getDirection(), + ]), + ) + ); + // Menu internal. $registry->addFieldResolver('MenuItem', 'internal', $builder->compose( $builder->produce('menu_link_url') - ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())), + ->map('link', $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent())), $builder->callback(fn(Url $url) => $url->isRouted()), ) @@ -102,7 +162,8 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase { // Menu expanded. $registry->addFieldResolver('MenuItem', 'expanded', $builder->produce('menu_link_expanded') - ->map('link', $builder->produce('menu_tree_link')->map('element', $builder->fromParent())) + ->map('link', $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent())), ); // Menu children. @@ -111,12 +172,12 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase { ->map('element', $builder->fromParent()) ); - // Menu children. + // Menu attributes. $registry->addFieldResolver('MenuItem', 'attributes', - $builder->produce('menu_tree_link')->map('element', $builder->fromParent()), + $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent()), ); - // Menu attributes. $attributes = ['class']; if ($this->moduleHandler->moduleExists('menu_link_attributes')) { $menu_link_attributes = $this->configFactory->get('menu_link_attributes.config')->get('attributes') ?: []; @@ -133,11 +194,21 @@ class MenusSchemaExtension extends ResolverOnlySchemaExtensionPluginBase { ); } + // Menu link extras. + if ($this->moduleHandler->moduleExists('menu_item_extras')) { + $registry->addFieldResolver('MenuItem', 'extras', + $builder->produce('menu_link_entity') + ->map('link', $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent())), + ); + } + // Menu route. // This is toggled by users. $registry->addFieldResolver('MenuItem', 'route', $builder->compose( - $builder->produce('menu_tree_link')->map('element', $builder->fromParent()), + $builder->produce('menu_tree_link') + ->map('element', $builder->fromParent()), $builder->cond([ [ diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php new file mode 100644 index 0000000000000000000000000000000000000000..a5198eb21d2a2df652da44277d7ce9f3cf99db0e --- /dev/null +++ b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/EntityType/MenuLinkContent.php @@ -0,0 +1,32 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\graphql_compose_menus\Plugin\GraphQLCompose\EntityType; + +use Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeEntityTypeBase; +use Drupal\graphql_compose\Wrapper\EntityTypeWrapper; +use Drupal\graphql_compose_menus\Wrapper\MenuLinkContentWrapper; + +/** + * {@inheritdoc} + * + * Re-wrap the bundles in a utility wrap to change what is enabled. + * This is intended to be used with the menu_item_extras module. + * + * @GraphQLComposeEntityType( + * id = "menu_link_content", + * prefix = "MenuLinkContent", + * base_fields = {}, + * ) + */ +class MenuLinkContent extends GraphQLComposeEntityTypeBase { + + /** + * {@inheritdoc} + */ + public function wrapBundle($bundle): EntityTypeWrapper { + return new MenuLinkContentWrapper($this, $bundle); + } + +} diff --git a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php index b26a42b63e01cff16231775dfa93e9cd7b9fcff0..89cb75d3dabc09f6bc544f1156d70da082dc4cb1 100644 --- a/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php +++ b/modules/graphql_compose_menus/src/Plugin/GraphQLCompose/SchemaType/MenuItem.php @@ -43,6 +43,10 @@ class MenuItem extends GraphQLComposeSchemaTypeBase { 'type' => Type::string(), 'description' => (string) $this->t('The URL of the menu item.'), ], + 'langcode' => [ + 'type' => Type::nonNull(static::type('Language')), + 'description' => (string) $this->t('The language of the menu item.'), + ], 'internal' => [ 'type' => Type::nonNull(Type::boolean()), 'description' => (string) $this->t('Whether this menu item links to an internal route.'), @@ -69,4 +73,25 @@ class MenuItem extends GraphQLComposeSchemaTypeBase { return $types; } + /** + * {@inheritdoc} + */ + public function getExtensions(): array { + $extensions = parent::getExtensions(); + + if ($this->moduleHandler->moduleExists('menu_item_extras')) { + $extensions[] = new ObjectType([ + 'name' => $this->getPluginId(), + 'fields' => fn() => [ + 'extras' => [ + 'type' => Type::nonNull(static::type('MenuLinkContentUnion')), + 'description' => (string) $this->t('The menu link content entity associated with this menu link.'), + ], + ], + ]); + } + + return $extensions; + } + } diff --git a/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php b/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php new file mode 100644 index 0000000000000000000000000000000000000000..4243b4f8c3b7c27e900f3bd94563f07d21e6865d --- /dev/null +++ b/modules/graphql_compose_menus/src/Wrapper/MenuLinkContentWrapper.php @@ -0,0 +1,25 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\graphql_compose_menus\Wrapper; + +use Drupal\graphql_compose\Wrapper\EntityTypeWrapper; + +/** + * Override EntityTypeWrapper for MenuLinkContent. + */ +class MenuLinkContentWrapper extends EntityTypeWrapper { + + /** + * Enable the menu link content type if the menu is enabled. + * + * @return bool + * True if the bundle is enabled. + */ + public function isEnabled(): bool { + $settings = $this->configFactory->get('graphql_compose.settings'); + return $settings->get('entity_config.menu.' . $this->entity->id() . '.enabled') ?: FALSE; + } + +} diff --git a/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php b/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php index fe821ca83b0e6da4fab2cdf5d74a9e862a2a6fbe..924e8382d711d40c4e8f3eae69700b49fff2babc 100644 --- a/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php +++ b/modules/graphql_compose_routes/src/GraphQL/Buffers/EntityPreviewBuffer.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_routes\GraphQL\Buffers; use Drupal\Core\ParamConverter\ParamConverterInterface; diff --git a/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php b/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php index d7edbc93dd5344d8f8b9258398000ad8fe7e2b66..a33d21082a438e2c09b3a8f1bd962766f8ae9e5a 100644 --- a/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php +++ b/modules/graphql_compose_routes/src/GraphQL/Buffers/SubrequestBuffer.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose_routes\GraphQL\Buffers; use Drupal\Core\Cache\CacheableDependencyInterface; diff --git a/src/Form/SchemaForm.php b/src/Form/SchemaForm.php index ab30d37631b6ad46d92ea79d909ef4e7a431aceb..cfd9a34f7dc8e527a2ed3c2fd80c69dee1039b67 100644 --- a/src/Form/SchemaForm.php +++ b/src/Form/SchemaForm.php @@ -174,7 +174,7 @@ class SchemaForm extends ConfigFormBase { foreach ($entity_bundles as $bundle) { $this->buildEntityTypeBundle($form, $form_state, $entity_type, $bundle); if ($entity_type->entityClassImplements(FieldableEntityInterface::class)) { - $this->buildEntityTypeBundleFields($form, $form_state, $entity_type, $bundle); + $this->buildEntityTypeBundleFields($form, $form_state, $entity_type, $bundle->id()); } } } @@ -195,7 +195,7 @@ class SchemaForm extends ConfigFormBase { * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\EntityTypeInterface $bundle * The entity bundle. */ - protected function buildEntityTypeBundle(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) { + public function buildEntityTypeBundle(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) { $entity_type_id = $entity_type->id(); $bundle_id = $bundle->id(); $settings = $this->getConfig()->get("entity_config.$entity_type_id.$bundle_id") ?: []; @@ -255,11 +255,10 @@ class SchemaForm extends ConfigFormBase { * The form state. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type * The entity type. - * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\EntityTypeInterface $bundle - * The entity bundle. + * @param string $bundle_id + * The entity bundle id. */ - protected function buildEntityTypeBundleFields(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, EntityInterface|EntityTypeInterface $bundle) { - $bundle_id = $bundle->id(); + public function buildEntityTypeBundleFields(array &$form, FormStateInterface $form_state, EntityTypeInterface $entity_type, string $bundle_id) { $entity_type_id = $entity_type->id(); $fields = $this->entityFieldManager->getFieldDefinitions($entity_type_id, $bundle_id); diff --git a/src/Plugin/GraphQL/DataProducer/ContextLanguage.php b/src/Plugin/GraphQL/DataProducer/ContextLanguage.php index 8f1705e25d46be15d60c47322d66b429a7faa75a..bc26257e3f0ecce3da80fde55c546f7410ce8420 100644 --- a/src/Plugin/GraphQL/DataProducer/ContextLanguage.php +++ b/src/Plugin/GraphQL/DataProducer/ContextLanguage.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose\Plugin\GraphQL\DataProducer; use Drupal\Core\Language\LanguageManagerInterface; diff --git a/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php b/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php index bd680e5d7ac045095d7813981cf273611b8cb101..28139b9dc779d4fbbb563c83cb88a644e00f6e09 100644 --- a/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php +++ b/src/Plugin/GraphQL/DataProducer/EntityLoadByUuidOrId.php @@ -1,5 +1,7 @@ <?php +declare(strict_types=1); + namespace Drupal\graphql_compose\Plugin\GraphQL\DataProducer; use Drupal\Component\Uuid\Uuid; diff --git a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php index e2b69b188cf4597dccc3538939945b53614c17f0..06576905c68121c5a5f1f4c8f1d126e8f2f35efb 100644 --- a/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php +++ b/src/Plugin/GraphQLCompose/GraphQLComposeEntityTypeBase.php @@ -191,6 +191,19 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ return $bundles[$bundle_id] ?? NULL; } + /** + * Wrap a bundle into a utility wrapper. + * + * @param mixed $bundle + * The bundle to wrap. + * + * @return \Drupal\graphql_compose\Wrapper\EntityTypeWrapper + * The wrapped bundle. + */ + protected function wrapBundle($bundle): EntityTypeWrapper { + return new EntityTypeWrapper($this, $bundle); + } + /** * {@inheritdoc} */ @@ -209,7 +222,7 @@ abstract class GraphQLComposeEntityTypeBase extends PluginBase implements GraphQ } foreach (array_keys($bundle_info) as $bundle_id) { - $bundle = new EntityTypeWrapper($this, $entity_types[$bundle_id] ?? $entity_type); + $bundle = $this->wrapBundle($entity_types[$bundle_id] ?? $entity_type); if ($bundle->isEnabled()) { $this->bundles[$bundle_id] = $bundle; } diff --git a/src/Plugin/GraphQLComposeFieldTypeManager.php b/src/Plugin/GraphQLComposeFieldTypeManager.php index 26229c0e47bdaeb849dfdb47d9d77b3785e5033f..02ad82d666e6e3df2d27596413c90462b542f262 100644 --- a/src/Plugin/GraphQLComposeFieldTypeManager.php +++ b/src/Plugin/GraphQLComposeFieldTypeManager.php @@ -149,7 +149,7 @@ class GraphQLComposeFieldTypeManager extends DefaultPluginManager { throw new \Exception('Property config missing field_name'); } - // Set required property config if not set yet. + // Set required property config is not set yet. $config = array_merge([ 'field_type' => 'property', 'name_sdl' => $config['field_name'], @@ -191,8 +191,9 @@ class GraphQLComposeFieldTypeManager extends DefaultPluginManager { /** * All defined fields that have been created at time of invocation. * - * @return \Drupal\graphql_compose\Plugin\GraphQLCompose\GraphQLComposeFieldTypeInterface[] - * An array of fields that have been initialized. + * @return array + * An array of fields that have been initialized, + * keyed by entity type and bundle. */ public function getFields(): array { return $this->fields; diff --git a/tests/src/Functional/Contrib/MenuItemExtrasTest.php b/tests/src/Functional/Contrib/MenuItemExtrasTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fec884968995cadb9831baaf87942ef479bd1bb8 --- /dev/null +++ b/tests/src/Functional/Contrib/MenuItemExtrasTest.php @@ -0,0 +1,212 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\graphql_compose\Functional\Core; + +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; +use Drupal\menu_link_content\Entity\MenuLinkContent; +use Drupal\system\Entity\Menu; +use Drupal\Tests\graphql_compose\Functional\GraphQLComposeBrowserTestBase; + +/** + * Tests specific to GraphQL Compose menus with menu item extras. + * + * @group legacy + */ +class MenuItemExtrasTest extends GraphQLComposeBrowserTestBase { + + /** + * The test menu. + * + * @var \Drupal\system\MenuInterface[] + */ + protected array $menus; + + /** + * The test links. + * + * @var \Drupal\menu_link_content\Entity\MenuLinkContent[] + */ + protected array $links; + + /** + * The test nodes. + * + * @var \Drupal\node\NodeInterface[] + */ + protected array $nodes; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'menu_link_content', + 'menu_item_extras', + 'graphql_compose_menus', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->nodes[1] = $this->createNode([ + 'title' => 'Test node 1', + ]); + + $this->nodes[2] = $this->createNode([ + 'title' => 'Test node 2', + ]); + + $this->menus[1] = Menu::create([ + 'id' => 'test_with', + 'label' => 'Test Menu with fields', + ]); + + $this->menus[2] = Menu::create([ + 'id' => 'test_without', + 'label' => 'Test Menu without fields', + ]); + + $this->menus[1]->save(); + $this->menus[2]->save(); + + FieldStorageConfig::create([ + 'field_name' => 'bingo', + 'type' => 'string', + 'entity_type' => 'menu_link_content', + ])->save(); + + FieldConfig::create([ + 'field_name' => 'bingo', + 'entity_type' => 'menu_link_content', + 'bundle' => $this->menus[1]->id(), + 'label' => 'Bingo', + 'required' => FALSE, + ])->save(); + + $this->links[1] = MenuLinkContent::create([ + 'title' => 'Test link 1', + 'link' => ['uri' => 'internal:/node/' . $this->nodes[1]->id()], + 'menu_name' => $this->menus[1]->id(), + 'bingo' => 'Bingo!', + 'weight' => 1, + ]); + + $this->links[2] = MenuLinkContent::create([ + 'title' => 'Test link 2', + 'link' => ['uri' => 'internal:/node/' . $this->nodes[2]->id()], + 'menu_name' => $this->menus[1]->id(), + 'weight' => 2, + ]); + + $this->links[3] = MenuLinkContent::create([ + 'title' => 'Test external', + 'link' => ['uri' => 'https://www.google.com'], + 'menu_name' => $this->menus[1]->id(), + 'weight' => 3, + ]); + + $this->links[4] = MenuLinkContent::create([ + 'title' => 'Test link on without menu', + 'link' => ['uri' => 'internal:/node/' . $this->nodes[1]->id()], + 'menu_name' => $this->menus[2]->id(), + 'weight' => 1, + ]); + + foreach ($this->links as $link) { + $link->save(); + } + + $this->setEntityConfig('menu', 'test_with', [ + 'enabled' => TRUE, + ]); + + $this->setEntityConfig('menu', 'test_without', [ + 'enabled' => TRUE, + ]); + + $this->setFieldConfig('menu_link_content', 'test_with', 'bingo', [ + 'enabled' => TRUE, + ]); + } + + /** + * Test menu loads a menu with a field. + */ + public function testMenuLoadWithField(): void { + $query = <<<GQL + query { + menu(name: TEST_WITH) { + id + name + items { + title + extras { + ... on MenuLinkContentInterface { + id + } + ... on MenuLinkContentTestWith { + bingo + } + } + } + } + } + GQL; + + $content = $this->executeQuery($query); + + $menu = $content['data']['menu']; + + $this->assertEquals($this->menus[1]->uuid(), $menu['id']); + $this->assertEquals($this->menus[1]->label(), $menu['name']); + + $this->assertCount(3, $menu['items']); + + $this->assertEquals('Test link 1', $menu['items'][0]['title']); + $this->assertEquals('Test link 2', $menu['items'][1]['title']); + + $this->assertEquals('Bingo!', $menu['items'][0]['extras']['bingo']); + $this->assertNull($menu['items'][1]['extras']['bingo']); + } + + /** + * Test menu loads a menu with a field. + */ + public function testMenuLoadWithoutField(): void { + $query = <<<GQL + query { + menu(name: TEST_WITHOUT) { + id + name + items { + title + extras { + ... on MenuLinkContentInterface { + id + } + } + } + } + } + GQL; + + $content = $this->executeQuery($query); + + $menu = $content['data']['menu']; + + $this->assertEquals($this->menus[2]->uuid(), $menu['id']); + $this->assertEquals($this->menus[2]->label(), $menu['name']); + + $this->assertCount(1, $menu['items']); + + $this->assertEquals('Test link on without menu', $menu['items'][0]['title']); + + $this->assertArrayNotHasKey('bingo', $menu['items'][0]['extras']); + } + +} diff --git a/tests/src/Functional/Core/MenusTest.php b/tests/src/Functional/Core/MenusTest.php index 8cd2b5c490b203f728882d389586eb31e6586f52..b51d8e5a40c29ff7b422aaecf51638515d791a45 100644 --- a/tests/src/Functional/Core/MenusTest.php +++ b/tests/src/Functional/Core/MenusTest.php @@ -95,6 +95,13 @@ class MenusTest extends GraphQLComposeBrowserTestBase { 'enabled' => FALSE, ]); + $this->links[5] = MenuLinkContent::create([ + 'title' => 'Test external', + 'link' => ['uri' => 'https://www.google.com'], + 'menu_name' => $this->menu->id(), + 'weight' => 6, + ]); + foreach ($this->links as $link) { $link->save(); } @@ -129,13 +136,16 @@ class MenusTest extends GraphQLComposeBrowserTestBase { $this->assertEquals($this->menu->uuid(), $menu['id']); $this->assertEquals($this->menu->label(), $menu['name']); - $this->assertCount(2, $menu['items']); + $this->assertCount(3, $menu['items']); // Sort weight sorting. $this->assertEquals('Test link 2', $menu['items'][0]['title']); + $this->assertEquals('Test external', $menu['items'][1]['title']); + $this->assertEquals('Test link 1', $menu['items'][2]['title']); // Test internal link. $this->assertTrue($menu['items'][0]['internal']); + $this->assertFalse($menu['items'][1]['internal']); } /** @@ -162,7 +172,7 @@ class MenusTest extends GraphQLComposeBrowserTestBase { $this->assertEmpty($menu['items'][0]['children']); - $this->assertEquals('Test link child', $menu['items'][1]['children'][0]['title']); + $this->assertEquals('Test link child', $menu['items'][2]['children'][0]['title']); } }