From 93950f28c48df6a16b0d19f02657373bebd0c22b Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 28 Nov 2024 06:40:05 -0800 Subject: [PATCH] "Full data" GraphQL mode for Link fields Resolves #16104 --- src/fields/Link.php | 81 ++++++++++++++++++++++- src/gql/types/LinkData.php | 41 ++++++++++++ src/gql/types/generators/LinkDataType.php | 63 ++++++++++++++++++ src/translations/en/app.php | 3 + 4 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 src/gql/types/LinkData.php create mode 100644 src/gql/types/generators/LinkDataType.php diff --git a/src/fields/Link.php b/src/fields/Link.php index e9a34f3fb3d..3ce074ed15f 100644 --- a/src/fields/Link.php +++ b/src/fields/Link.php @@ -28,6 +28,8 @@ use craft\fields\linktypes\Entry; use craft\fields\linktypes\Phone; use craft\fields\linktypes\Url as UrlType; +use craft\gql\GqlEntityRegistry; +use craft\gql\types\generators\LinkDataType; use craft\helpers\ArrayHelper; use craft\helpers\Component; use craft\helpers\Cp; @@ -35,6 +37,8 @@ use craft\helpers\StringHelper; use craft\validators\ArrayValidator; use craft\validators\StringValidator; +use GraphQL\Type\Definition\InputObjectType; +use GraphQL\Type\Definition\Type; use Illuminate\Support\Collection; use yii\base\InvalidArgumentException; use yii\db\Schema; @@ -180,6 +184,12 @@ private static function types(): array */ public int $maxLength = 255; + /** + * @var bool Whether GraphQL values should be returned as objects with `type`, + * `value`, `label`, `urlSuffix`, and `url` keys. + */ + public bool $fullGraphqlData = true; + /** * @inheritdoc */ @@ -198,6 +208,15 @@ public function __construct($config = []) unset($config['placeholder']); } + if (isset($config['graphqlMode'])) { + $config['fullGraphqlData'] = ArrayHelper::remove($config, 'graphqlMode') === 'full'; + } + + // Default fullGraphqlData to false for existing fields + if (isset($config['id']) && !isset($config['fullGraphqlData'])) { + $config['fullGraphqlData'] = false; + } + parent::__construct($config); } @@ -354,7 +373,7 @@ public function getSettingsHtml(): ?string } } - return $html . + $html .= Html::tag('hr') . Cp::lightswitchFieldHtml([ 'label' => Craft::t('app', 'Show the “Label” field'), @@ -374,6 +393,15 @@ public function getSettingsHtml(): ?string 'name' => 'showTargetField', 'on' => $this->showTargetField, ]) . + Html::tag('hr') . + Html::a(Craft::t('app', 'Advanced'), options: [ + 'class' => 'fieldtoggle', + 'data' => ['target' => 'advanced'], + ]) . + Html::beginTag('div', [ + 'id' => 'advanced', + 'class' => 'hidden', + ]) . Cp::textFieldHtml([ 'label' => Craft::t('app', 'Max Length'), 'instructions' => Craft::t('app', 'The maximum length (in bytes) the field can hold.'), @@ -386,6 +414,24 @@ public function getSettingsHtml(): ?string 'errors' => $this->getErrors('maxLength'), 'data' => ['error-key' => 'maxLength'], ]); + + if (Craft::$app->getConfig()->getGeneral()->enableGql) { + $html .= + Cp::selectFieldHtml([ + 'label' => Craft::t('app', 'GraphQL Mode'), + 'id' => 'graphql-mode', + 'name' => 'graphqlMode', + 'options' => [ + ['label' => Craft::t('app', 'Full data'), 'value' => 'full'], + ['label' => Craft::t('app', 'URL only'), 'value' => 'url'], + ], + 'value' => $this->fullGraphqlData ? 'full' : 'url', + ]); + } + + $html .= Html::endTag('div'); + + return $html; } /** @@ -707,6 +753,39 @@ public function previewPlaceholderHtml(mixed $value, ?ElementInterface $element) return $this->getPreviewHtml($value, new EntryElement()); } + /** + * @inheritdoc + */ + public function getContentGqlType(): Type|array + { + if (!$this->fullGraphqlData) { + return parent::getContentGqlType(); + } + + return LinkDataType::generateType($this); + } + + /** + * @inheritdoc + */ + public function getContentGqlMutationArgumentType(): Type|array + { + if (!$this->fullGraphqlData) { + return parent::getContentGqlMutationArgumentType(); + } + + $typeName = 'LinkDataInput'; + return GqlEntityRegistry::getOrCreate($typeName, fn() => new InputObjectType([ + 'name' => $typeName, + 'fields' => [ + 'type' => Type::string(), + 'value' => Type::string(), + 'label' => Type::string(), + 'urlSuffix' => Type::string(), + ], + ])); + } + /** * @inheritdoc */ diff --git a/src/gql/types/LinkData.php b/src/gql/types/LinkData.php new file mode 100644 index 00000000000..029f162e589 --- /dev/null +++ b/src/gql/types/LinkData.php @@ -0,0 +1,41 @@ + + * @since 5.6.0 + */ +class LinkData extends ObjectType +{ + /** + * @inheritdoc + */ + protected function resolve(mixed $source, array $arguments, mixed $context, ResolveInfo $resolveInfo): mixed + { + /** @var FieldLinkData $source */ + $fieldName = $resolveInfo->fieldName; + return match ($fieldName) { + 'type' => $source->getType(), + 'value' => $source->getValue(), + 'label' => $source->getLabel(true), + 'url' => $source->getUrl(), + 'elementType' => $source->getElement() ? $source->getElement()::class : null, + 'elementId' => $source->getElement()?->id, + 'elementSiteId' => $source->getElement()?->siteId, + 'elementTitle' => $source->getElement() ? (string)$source->getElement() : null, + default => $source->$fieldName, + }; + } +} diff --git a/src/gql/types/generators/LinkDataType.php b/src/gql/types/generators/LinkDataType.php new file mode 100644 index 00000000000..0da7fb574f3 --- /dev/null +++ b/src/gql/types/generators/LinkDataType.php @@ -0,0 +1,63 @@ + + * @since 5.6.0 + */ +class LinkDataType implements GeneratorInterface, SingleGeneratorInterface +{ + /** + * @inheritdoc + */ + public static function generateTypes(mixed $context = null): array + { + return [static::generateType($context)]; + } + + /** + * Returns the generator name. + */ + public static function getName(): string + { + return 'LinkData'; + } + + /** + * @inheritdoc + */ + public static function generateType(mixed $context): ObjectType + { + $typeName = self::getName(); + return GqlEntityRegistry::getOrCreate($typeName, fn() => new LinkData([ + 'name' => $typeName, + 'fields' => fn() => Craft::$app->getGql()->prepareFieldDefinitions([ + 'type' => Type::string(), + 'value' => Type::string(), + 'label' => Type::string(), + 'urlSuffix' => Type::string(), + 'url' => Type::string(), + 'elementType' => Type::string(), + 'elementId' => Type::int(), + 'elementSiteId' => Type::int(), + 'elementTitle' => Type::string(), + ], $typeName), + ])); + } +} diff --git a/src/translations/en/app.php b/src/translations/en/app.php index 19946414d60..0963fa8b559 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -728,6 +728,7 @@ 'Fuchsia' => 'Fuchsia', 'Full Name' => 'Full Name', 'Full Schema' => 'Full Schema', + 'Full data' => 'Full data', 'General Settings' => 'General Settings', 'General settings saved.' => 'General settings saved.', 'General' => 'General', @@ -749,6 +750,7 @@ 'Go to Craft CMS' => 'Go to Craft CMS', 'Go to Updates' => 'Go to Updates', 'Got it' => 'Got it', + 'GraphQL Mode' => 'GraphQL Mode', 'GraphQL Schemas' => 'GraphQL Schemas', 'GraphQL Tokens' => 'GraphQL Tokens', 'GraphQL queries' => 'GraphQL queries', @@ -1791,6 +1793,7 @@ 'URI' => 'URI', 'URL Format' => 'URL Format', 'URL Suffix' => 'URL Suffix', + 'URL only' => 'URL only', 'URL type' => 'URL type', 'URL' => 'URL', 'Unable to fetch updates at this time.' => 'Unable to fetch updates at this time.',