Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions config/graphql.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,33 @@
'users' => false,
],

/*
|--------------------------------------------------------------------------
| Improved Types
|--------------------------------------------------------------------------
|
| When enabled, fields like entries and terms can return dynamically
| generated union types when multiple blueprints are possible. Also will
| use non-nullable types for entries and terms.
|
| You may also register per-collection and per-taxonomy queries that
| return typed results. List collection or taxonomy handles under
| "collections" and "terms", or use "*" to enable all.
|
*/

'improved_types' => [
'enabled' => env('STATAMIC_GRAPHQL_IMPROVED_TYPES', true),
'collections' => [
// 'blog_posts',
// '*',
],
'terms' => [
// 'tags',
// '*',
],
],

/*
|--------------------------------------------------------------------------
| Authentication
Expand Down
17 changes: 15 additions & 2 deletions src/Fieldtypes/Assets/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use Statamic\Fields\Fieldtype;
use Statamic\Fieldtypes\UpdatesReferences;
use Statamic\GraphQL\Types\AssetInterface;
use Statamic\GraphQL\Types\AssetType;
use Statamic\Http\Resources\CP\Assets\AssetsFieldtypeAsset as AssetResource;
use Statamic\Query\Scopes\Filter;
use Statamic\Support\Arr;
Expand Down Expand Up @@ -492,10 +493,22 @@ protected function getItemsForPreProcessIndex($values): Collection

public function toGqlType()
{
$type = GraphQL::type(AssetInterface::NAME);
// Fallback to old behaviour if improved types are disabled.
if (! config('statamic.graphql.improved_types.enabled', false)) {
$type = GraphQL::type(AssetInterface::NAME);

if ($this->config('max_files') !== 1) {
$type = GraphQL::listOf($type);
}

return $type;
}

$container = $this->container();
$type = GraphQL::type(AssetType::buildName($container));

if ($this->config('max_files') !== 1) {
$type = GraphQL::listOf($type);
$type = GraphQL::listOf(GraphQL::nonNull($type));
}

return $type;
Expand Down
52 changes: 50 additions & 2 deletions src/Fieldtypes/Entries.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
use Statamic\Facades\Search;
use Statamic\Facades\Site;
use Statamic\Facades\User;
use Statamic\GraphQL\Types\DynamicEntryUnionType;
use Statamic\GraphQL\Types\EntryInterface;
use Statamic\GraphQL\Types\EntryType;
use Statamic\Http\Resources\CP\Entries\EntriesFieldtypeEntries;
use Statamic\Http\Resources\CP\Entries\EntriesFieldtypeEntry as EntryResource;
use Statamic\Query\OrderBy;
Expand Down Expand Up @@ -455,10 +458,55 @@ protected function getConfiguredCollections()

public function toGqlType()
{
$type = GraphQL::type('EntryInterface');
// Fallback to old behaviour if improved types are disabled.
if (! config('statamic.graphql.improved_types.enabled', false)) {
$type = GraphQL::type('EntryInterface');

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf($type);
}

return $type;
}

// If the fieldtype isn't constrained to specific collections, return the generic EntryInterface.
if (empty($this->config('collections'))) {
$type = GraphQL::type(EntryInterface::NAME);

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf(GraphQL::nonNull($type));
}

return $type;
}

$configuredCollections = $this->getConfiguredCollections();

$combinations = collect($configuredCollections)->flatMap(function ($collectionHandle) {
$collection = Collection::find($collectionHandle);

if (! $collection) {
return [];
}

return $collection->entryBlueprints()->map(fn ($blueprint) => [
'collection' => $collection,
'blueprint' => $blueprint,
]);
})->values()->all();

if (count($combinations) === 1) {
$collection = $combinations[0]['collection'];
$blueprint = $combinations[0]['blueprint'];
$type = GraphQL::type(EntryType::buildName($collection, $blueprint));
} else {
$newType = new DynamicEntryUnionType($combinations);
GraphQL::addType($newType);
$type = GraphQL::type($newType->name);
}

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf($type);
$type = GraphQL::listOf(GraphQL::nonNull($type));
}

return $type;
Expand Down
54 changes: 52 additions & 2 deletions src/Fieldtypes/Terms.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
use Statamic\Facades\Taxonomy;
use Statamic\Facades\Term;
use Statamic\Facades\User;
use Statamic\GraphQL\Types\DynamicTermUnionType;
use Statamic\GraphQL\Types\TermInterface;
use Statamic\GraphQL\Types\TermType;
use Statamic\Http\Resources\CP\Taxonomies\TermsFieldtypeTerms as TermsResource;
use Statamic\Query\OrderBy;
use Statamic\Query\OrderedQueryBuilder;
Expand Down Expand Up @@ -529,10 +531,58 @@ protected function getConfiguredTaxonomies()

public function toGqlType()
{
$type = GraphQL::type(TermInterface::NAME);
if (! config('statamic.graphql.improved_types.enabled', false)) {
$type = GraphQL::type(TermInterface::NAME);

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf($type);
}

return $type;
}

// If the fieldtype isn't constrained to specific taxonomies, return the generic TermInterface.
if (empty($this->field()->config()['taxonomies'])) {
$type = GraphQL::type(TermInterface::NAME);

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf(GraphQL::nonNull($type));
}

return $type;
}

$configuredTaxonomies = $this->getConfiguredTaxonomies();

$combinations = collect($configuredTaxonomies)->flatMap(function ($taxonomyHandle) {
$taxonomy = Taxonomy::find($taxonomyHandle);

if (! $taxonomy) {
return [];
}

$blueprints = $taxonomy->termBlueprints();

return $blueprints->map(function ($blueprint) use ($taxonomy) {
return [
'taxonomy' => $taxonomy,
'blueprint' => $blueprint,
];
});
})->values()->all();

if (count($combinations) === 1) {
$taxonomy = $combinations[0]['taxonomy'];
$blueprint = $combinations[0]['blueprint'];
$type = GraphQL::type(TermType::buildName($taxonomy, $blueprint));
} else {
$newType = new DynamicTermUnionType($combinations);
GraphQL::addType($newType);
$type = GraphQL::type($newType->name);
}

if ($this->config('max_items') !== 1) {
$type = GraphQL::listOf($type);
$type = GraphQL::listOf(GraphQL::nonNull($type));
}

return $type;
Expand Down
72 changes: 72 additions & 0 deletions src/GraphQL/DefaultSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use Facades\Statamic\API\ResourceAuthorizer;
use Rebing\GraphQL\Support\Contracts\ConfigConvertible;
use Statamic\Facades\Collection;
use Statamic\Facades\GraphQL;
use Statamic\Facades\Taxonomy;
use Statamic\GraphQL\Middleware\CacheResponse;
use Statamic\GraphQL\Middleware\HandleAuthentication;
use Statamic\GraphQL\Queries\AssetContainerQuery;
Expand All @@ -23,6 +25,8 @@
use Statamic\GraphQL\Queries\NavsQuery;
use Statamic\GraphQL\Queries\PingQuery;
use Statamic\GraphQL\Queries\SitesQuery;
use Statamic\GraphQL\Queries\SpecificEntriesQuery;
use Statamic\GraphQL\Queries\SpecificTermsQuery;
use Statamic\GraphQL\Queries\TaxonomiesQuery;
use Statamic\GraphQL\Queries\TaxonomyQuery;
use Statamic\GraphQL\Queries\TermQuery;
Expand Down Expand Up @@ -69,12 +73,80 @@ private function getQueries()
$queries = $queries->merge(ResourceAuthorizer::isAllowed('graphql', $resource) ? $qs : []);
});

$queries = $queries
->merge($this->getSpecificEntriesQueries())
->merge($this->getSpecificTermsQueries());

return $queries
->merge(config('statamic.graphql.queries', []))
->merge(GraphQL::getExtraQueries())
->all();
}

private function getSpecificEntriesQueries(): array
{
// rebing/graphql-laravel calls toConfig() eagerly during boot
// at which point the Stache is not yet ready.
// The schema is rebuilt when an actual request hits the controller,
// where the Stache is fully booted, so wildcards still expand correctly there.
if (! app()->isBooted()) {
return [];
}

if (! ResourceAuthorizer::isAllowed('graphql', 'collections')) {
return [];
}

$configured = config('statamic.graphql.improved_types.collections', []);

if (empty($configured)) {
return [];
}

if (in_array('*', $configured)) {
$handles = Collection::handles()->all();
} else {
$handles = $configured;
}

$allowed = ResourceAuthorizer::allowedSubResources('graphql', 'collections');

return collect($handles)
->filter(fn ($handle) => in_array($handle, $allowed))
->map(fn ($handle) => new SpecificEntriesQuery($handle))
->all();
}

private function getSpecificTermsQueries(): array
{
if (! app()->isBooted()) {
return [];
}

if (! ResourceAuthorizer::isAllowed('graphql', 'taxonomies')) {
return [];
}

$configured = config('statamic.graphql.improved_types.terms', []);

if (empty($configured)) {
return [];
}

if (in_array('*', $configured)) {
$handles = Taxonomy::handles()->all();
} else {
$handles = $configured;
}

$allowed = ResourceAuthorizer::allowedSubResources('graphql', 'taxonomies');

return collect($handles)
->filter(fn ($handle) => in_array($handle, $allowed))
->map(fn ($handle) => new SpecificTermsQuery($handle))
->all();
}

private function getMiddleware()
{
return array_merge(
Expand Down
Loading
Loading