diff --git a/_graphql/config.yml b/_graphql/config.yml index c0bd2ba8..30fcedf6 100644 --- a/_graphql/config.yml +++ b/_graphql/config.yml @@ -7,3 +7,7 @@ modelConfig: plugins: getByLink: after: filter + readOne: + plugins: + getByLink: + after: filter diff --git a/code/GraphQL/LinkablePlugin.php b/code/GraphQL/LinkablePlugin.php index 1694d764..3e9fa7f9 100644 --- a/code/GraphQL/LinkablePlugin.php +++ b/code/GraphQL/LinkablePlugin.php @@ -12,6 +12,7 @@ use SilverStripe\GraphQL\Schema\Exception\SchemaBuilderException; use SilverStripe\GraphQL\Schema\Field\ModelQuery; use SilverStripe\GraphQL\Schema\Interfaces\ModelQueryPlugin; use SilverStripe\GraphQL\Schema\Schema; +use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; if (!interface_exists(ModelQueryPlugin::class)) { @@ -29,7 +30,18 @@ class LinkablePlugin implements ModelQueryPlugin * @var string * @config */ - private static $field_name = 'link'; + private static $single_field_name = 'link'; + + /** + * @var string + * @config + */ + private static $list_field_name = 'links'; + + /** + * @var array + */ + private static $resolver = [__CLASS__, 'applyLinkFilter']; /** * @return string @@ -43,7 +55,6 @@ class LinkablePlugin implements ModelQueryPlugin * @param ModelQuery $query * @param Schema $schema * @param array $config - * @throws SchemaBuilderException */ public function apply(ModelQuery $query, Schema $schema, array $config = []): void { @@ -52,43 +63,42 @@ class LinkablePlugin implements ModelQueryPlugin if ($class !== SiteTree::class && !is_subclass_of($class, SiteTree::class)) { return; } - Schema::invariant( - !$query->isList(), - 'Plugin %s cannot be applied to queries that return lists. Query "%s" is a list', - static::getIdentifier(), - $query->getName() + $singleFieldName = $this->config()->get('single_field_name'); + $listFieldName = $this->config()->get('list_field_name'); + $fieldName = $query->isList() ? $listFieldName : $singleFieldName; + $type = $query->isList() ? '[String]' : 'String'; + $query->addArg($fieldName, $type); + $query->addResolverAfterware( + $config['resolver'] ?? static::config()->get('resolver') ); - $fieldName = $this->config()->get('field_name'); - - $query->addArg($fieldName, 'String'); - $query->addResolverAfterware([static::class, 'applyLinkFilter']); } /** - * @param $obj - * @param array $args * @param array $context - * @param ResolveInfo $info - * @param callable $done - * @return SiteTree|DataList|null + * @return callable */ - public static function applyLinkFilter( - $obj, - array $args, - array $context, - ResolveInfo $info, - callable $done - ) { - $fieldName = static::config()->get('field_name'); - $filterLink = $args['filter'][$fieldName] ?? null; - $argLink = $args[$fieldName] ?? null; - $filterLink = $filterLink ?: $argLink; - - if ($filterLink) { - $done(); - return SiteTree::get_by_link($filterLink); + public static function applyLinkFilter($obj, array $args, array $context, ResolveInfo $info) + { + $singleFieldName = static::config()->get('single_field_name'); + $listFieldName = static::config()->get('list_field_name'); + $filterLink = $args['filter'][$singleFieldName] ?? ($args['filter'][$listFieldName] ?? null); + $argLink = $args[$singleFieldName] ?? ($args[$listFieldName] ?? null); + $linkData = $filterLink ?: $argLink; + if (!$linkData) { + return $obj; } + // Normalise to an array for both cases. The readOne operation will get + // ->first() run on it by the firstResult plugin. + $links = is_array($linkData) ? $linkData : [$linkData]; - return $obj; + $result = ArrayList::create(); + + foreach ($links as $link) { + $page = SiteTree::get_by_link($link); + if ($page) { + $result->push($page); + } + } + return $result; } } diff --git a/tests/php/GraphQL/LinkablePluginTest.php b/tests/php/GraphQL/LinkablePluginTest.php new file mode 100644 index 00000000..cd80eb14 --- /dev/null +++ b/tests/php/GraphQL/LinkablePluginTest.php @@ -0,0 +1,112 @@ +markTestSkipped('GraphQL 4 test ' . __CLASS__ . ' skipped'); + } + } + + /** + * @param bool $list + * @dataProvider provideApply + */ + public function testApply(bool $list) + { + $query = new ModelQuery( + new DataObjectModel(SiteTree::class, new SchemaConfig()), + 'testQuery' + ); + $query->setType($list ? '[SiteTree]' : 'SiteTree'); + + $plugin = new LinkablePlugin(); + $plugin->apply($query, new Schema('test')); + $args = $query->getArgs(); + $field = $list ? 'links' : 'link'; + $this->assertArrayHasKey($field, $args); + $this->assertEquals($list ? '[String]' : 'String', $args[$field]->getType()); + } + + public function testResolver() + { + $page = SiteTree::create([ + 'Title' => 'Test page', + 'URLSegment' => 'test-page', + 'ParentID' => 0, + ]); + $page->write(); + $page->publishRecursive(); + + $page = SiteTree::create([ + 'Title' => 'Other test page', + 'URLSegment' => 'other-test-page', + 'ParentID' => 0, + ]); + + $page->write(); + $page->publishRecursive(); + + $result = LinkablePlugin::applyLinkFilter('test', ['link' => 'test-page'], [], new FakeResolveInfo()); + $this->assertTrue($result->exists()); + $this->assertEquals('Test page', $result->first()->Title); + + $result = LinkablePlugin::applyLinkFilter('test', ['links' => ['test-page']], [], new FakeResolveInfo()); + $this->assertTrue($result->exists()); + $this->assertEquals('Test page', $result->first()->Title); + + $result = LinkablePlugin::applyLinkFilter( + 'test', + ['links' => ['test-page', 'other-test-page']], + [], + new FakeResolveInfo() + ); + $this->assertTrue($result->exists()); + $this->assertCount(2, $result); + $titles = $result->column('Title'); + $this->assertTrue(in_array('Test page', $titles)); + $this->assertTrue(in_array('Other test page', $titles)); + + $result = LinkablePlugin::applyLinkFilter( + 'test', + ['links' => ['test-page', 'fail-page']], + [], + new FakeResolveInfo() + ); + $this->assertTrue($result->exists()); + $this->assertCount(1, $result); + $this->assertEquals('Test page', $result->first()->Title); + + $result = LinkablePlugin::applyLinkFilter('test', ['link' => 'fail-page'], [], new FakeResolveInfo()); + $this->assertFalse($result->exists()); + + $result = LinkablePlugin::applyLinkFilter('test', ['links' => ['fail-page']], [], new FakeResolveInfo()); + $this->assertFalse($result->exists()); + + $result = LinkablePlugin::applyLinkFilter('test', ['notAnArg' => 'fail'], [], new FakeResolveInfo()); + $this->assertEquals('test', $result); + } + + + public function provideApply() + { + return [ + [true], + [false], + ]; + } +}