Bot releases are hidden (Show)
Published by jasonbahl over 4 years ago
This feature focused primarily on #1045, exposing PostTypes and Taxonomies at the root of the graph.
PageToPageConnection
for revisions is now named PageToRevisionConnection
in the SchemapostTypeInfo
field from connections to PostTypeObjects. Each PostTypeObject node now has a contentType
field to access the ContentType node it's associated withtaxonomy
field is now different as well.contentType
instead of postType
category.taxonomy
and tag.taxonomy
field{
tags {
taxonomyInfo { #This returns a Taxonomy node, similar to calling get_taxonomy( 'post_tag' )
id
name
}
nodes {
id
name
taxonomy { #This returns a Taxonomy type, similar to calling get_taxonomy( 'post_tag' )
name
}
}
}
}
{
tags {
nodes {
id
name
taxonomy { # This now returns a oneToOne connection to a Taxonomy node
# Note this edge space between taxonomy and node.
# Contextual info about the connection between the Tag node and the Taxonomy
# it belongs to (should there ever be any) can be added here.
node { #This returns a Taxonomy node, similar to calling get_taxonomy( 'post_tag' )
id
name
}
}
}
}
}
{
posts {
postTypeInfo { # This returned a PostType type, similar to calling get_post_type_object( 'post' );
name
}
nodes {
id
title
# Note: there is no field to query for postType or contentType from a Post object node
}
}
}
{
posts {
nodes {
id
title
contentType {
# Note this edge space between contentType and node.
# Contextual info about the connection between the Post node and the ContentType node
# it belongs to (should there ever be any) can be added here.
node { # This returns a ContentType node, similar to calling get_post_type_object( 'post' );
name
}
}
}
}
}
This RootQuery->ContentType connection allows for Content Types (post types) to be queried from the root. All properties of the ContentType can be asked for, such as labels, etc.
{
contentTypes {
nodes {
id
name
graphqlSingleName
graphqlPluralName
}
}
}
{
taxonomy(id: "dGF4b25vbXk6cG9zdF90YWc=") {
id
name
graphqlSingleName
graphqlPluralName
}
}
{
taxonomy(id: "post_tag" idType: NAME ) {
id
name
graphqlSingleName
graphqlPluralName
}
}
{
contentType(id: "Y29udGVudFR5cGU6cG9zdA==") {
id
name
graphqlSingleName
graphqlPluralName
}
}
{
contentType( id: "post", idType: NAME ) {
id
name
graphqlSingleName
graphqlPluralName
}
}
Published by jasonbahl over 4 years ago
Published by jasonbahl over 4 years ago
This fixes #1144, a bug in the Model Layer that was causing cached values of a Model to attempt to execute if the string name was a function. For example, if a field's value was the string count
the Model would attempt to execute the count()
method instead of returning the string count
.
Published by jasonbahl over 4 years ago
This is a minor release that should have no breaking changes.
menuOrder
field wasn't being registered to post types that support page attributes. This was fixed by #1131. Thanks for reporting @moonmeister!Published by jasonbahl over 4 years ago
This release focused primarily on adding Interfaces for Post and Term objects.
Interfaces in GraphQL allow for common fields to be registered and shared across Types. Similar to Unions, they allow for multiple types to be queried from the same field.
By having Post Objects (posts, pages, etc) and Terms (Tags, Categories, etc) implement common interfaces, various Types can be queried for from the same entry point. So, instead of having to query only Posts or only Pages, interfaces make it possible to query for Post Objects of any type and have predictable results.
See below for examples of such queries.
uri
fields when querying nodesauthor
input on its mutation anymore. Or a non-hierarchical Post Type doesn't have a parent
input field by default in its mutation anymore. Might be a breaking change depending on your use of mutations and fields that a Post Type doesn't supportauthor
, you could have queried for the author
on that node, and got null. Now the author field will not exist on that Type and your query would not work.__get
methods from WPObjectType, WPUnionType, etc in favor of making the type_registry a public property (@kidunot89 this will affect WPGraphQL for WooCommerce)parent
field to revisionOf
field for Types that support revisions. If the node is a revision, the revisionOf
field will return the node that it is a revision of. This is applied only to post types that support revisions via the NodeWithRevisions
Interfaceabstract public function is_valid_offset( $offset );
to AbstractConnectionResolver. If you extend the AbstractConnectionResolver in your code, you will need to have your extending class implement this method. It is used for pagination to respect the Relay spec.post format
taxonomy - thanks @aberkow!RootQuery.terms
connection to query terms of any Taxonomy type from the root of the graphpageTemplate
field to the Post model layerpostBy( slug: "slug" ) { ...postFields }
, but are encouraged to query by the single entry point with the newly added idType
input instead, like so: post( id: "slug" idType: SLUG ) { ...postFields }
idType
inputs. Now single resources can be queried for using various types of unique identifiers such as database id, slug, name, uri. The type of ID available depends on the type of node.graphql()
function in PHP to break the admin barWe're deprecating the $postType.'By'
entry points, and have added a new idType
field and enum for single entry points.
This means queries such as the following will still work, but will not show in the Schema documentation, and may be formally removed from the codebase at a later date:
{
postBy( slug: "some-slug" ) {
id
title
}
}
{
pageBy( uri: "some-uri" ) {
id
title
}
}
These are deprecated in favor of existing single entry points for each post type, and the newly added idType
field on those entry points.
With the added idType
field on the singular post object entry points, below is a series of example queries now made possible:
{
post(id: 1739, idType: DATABASE_ID) {
id
title
uri
slug
postId
}
}
{
post(id: "/test-5/", idType: URI) {
id
title
uri
slug
postId
}
}
{
post(id: "cG9zdDoxNzM5", idType: ID) {
id
title
uri
slug
postId
}
}
or without specifying idType
as the default is the hashed ID
{
post(id: "cG9zdDoxNzM5") {
id
title
uri
slug
}
}
{
post(id: "test-5", idType: SLUG) {
id
title
uri
slug
}
}
This release also introduces the ContentNode
Interface that is implemented by all Post Object types (posts, pages, custom post types).
This Interface allows for new types of queries to be executed that span many post types.
Here's an example of a new query:
{
contentNodes {
nodes {
__typename
id
title
link
uri
isRevision
... on Page {
isFrontPage
}
}
}
}
In the results above, we can see that a mix of Posts and Pages have been returned.
We can also pass arguments to do things like search across multiple post types:
{
contentNodes(where: {search: "test"}) {
nodes {
__typename
id
title
link
uri
isRevision
... on Page {
isFrontPage
}
}
}
}
We can now also fetch single Content (post) nodes like so:
{
contentNode(id: "cG9zdDoxNzM5") {
__typename
id
title
link
uri
isRevision
... on Page {
isFrontPage
}
}
}
But, we can fetch single nodes of any Post type by other unique identifiers as well, such as the URI:
{
contentNode(id: "/2019/12/05/test-5/", idType: URI) {
__typename
id
title
link
uri
isRevision
... on Page {
isFrontPage
}
}
}
or
{
contentNode(id: "/test/", idType: URI) {
__typename
id
title
link
uri
isRevision
... on Page {
isFrontPage
}
}
}
This is a SUPER powerful feature as it allows for fragments to be more easily shared and re-used across queries and components.
Prior to this release, there wasn't really great ways of fetching singular term objects. You could do a query for a connection of terms and limit it to the first: 1
, and in some cases that worked fine, but it wasn't always ideal.
This release adds better support for fetching individual Term Nodes.
Instead of only fetching by the hashed global ID, we can now fetch by other unique identifiers using the idType
field.
{
tag(id: "cG9zdF90YWc6Mw==") {
id
name
slug
tagId
}
}
{
tag(id: 3, idType: DATABASE_ID) {
id
name
slug
tagId
}
}
{
tag(id: "Another Test", idType: NAME) {
id
name
slug
tagId
}
}
{
tag(id: "another-test", idType: SLUG) {
id
name
slug
tagId
}
}
{
tag(id: "tag/another-test/", idType: URI) {
id
name
slug
tagId
uri
}
}
This release also introduces the TermNode interface, which allows for querying terms across Taxonomy types.
For example:
{
terms {
nodes {
id
__typename
name
uri
}
}
}
This enables us to do things, like search terms across multiple taxonomies and get both Categories and Tags (and custom taxonomies) back in the same query:
{
terms(where: {search: "test"}) {
nodes {
id
__typename
name
uri
... on Tag {
tagId
}
... on Category {
categoryId
}
}
}
}
We can also query single term nodes by various identifiers, such as Database ID
, ID
, Name
, Slug
or URI
:
{
termNode(id: "cG9zdF90YWc6Mw==") {
__typename
id
name
link
slug
uri
}
}
{
termNode(id: 3, idType: DATABASE_ID) {
__typename
id
name
link
slug
uri
databaseId
}
}
{
termNode(id: "Another Test", idType: NAME, taxonomy: TAG) {
__typename
id
name
link
slug
uri
databaseId
}
}
{
termNode(id: "another-test", idType: SLUG, taxonomy: TAG) {
__typename
id
name
link
slug
uri
databaseId
}
}
{
termNode(id: "tag/another-test/", idType: URI) {
__typename
id
name
link
slug
uri
databaseId
}
}
Another thing this release provides is a way to get any node by URI, across types. This comes in handy because in many cases when a URI is provided, the type of content it is isn't always known ahead of time. By having an entry point for all Types that implement the 1UniformResourceIdentifiable` Interface, we can create a main entry point for entire applications.
Below is an example. This is showing many queries at once, but this could easily be one query with fragments for each Type, controlled by their appropriate component for rendering said type of content:
{
page: nodeByUri(uri: "about/") {
...URI
}
post: nodeByUri(uri: "2019/12/05/test-5/") {
...URI
}
tag: nodeByUri(uri: "tag/8bit/") {
...URI
}
category: nodeByUri(uri: "category/alignment/") {
...URI
}
user: nodeByUri(uri: "author/jasonbahl/") {
...URI
}
}
fragment URI on UniformResourceIdentifiable {
__typename
... on Page {
pageId
}
... on Post {
postId
}
... on Category {
categoryId
}
... on Tag {
tagId
}
... on User {
userId
}
}
Published by jasonbahl almost 5 years ago
Apology: This was intended to be a non-breaking minor release, but I moved too quickly and it was brought to my attention that there actually were some regressions not on my radar. I'll try to be more careful and communicate breaking changes better in the future, namely by not releasing them as minor point releases
/graphql
endpoint worked for both site_url()
and home_url()
, but in a Headless WordPress world we live in, many people are changing their WordPress Address and Site Address to be different things.For example, I might have my WordPress dashboard at data.site.com/wp-admin
and my headless site at site.com
, so my settings in general settings might look like:
Previously, WPGraphQL would resolve under the the site address /graphql, but if the site is actually not WordPress at all, say a Gatsby site, this doesn't make sense.
This release changed so that WPGraphQL endpoint is active at site_url(). /graphql
, so in the example above that would be data.site.com/graphql
We didn't fully think through the weight of this change, largely because many of the environments I test on aren't set up with these URLs being different, so it wasn't top of mind.
I apologize for releasing this without fully thinking through the implications for users that are already in environments where their WordPress URL and Site Address are configured to be different.
Thanks @aberkow and @mlbrgl for bringing this to my attention!
There was one breaking change introduced in this release in the way PostObjectConnectionResolver
behaves. Previously this resolver class allowed only a single post_type
to be passed to the class for resolutions, so $this->post_type
was always a string.
Now, it is usually cast as an array, with the exception being attachment
and revision
as these are validated differently because of their inherit
post_type nature.
If you were using the graphql_map_input_fields_to_wp_query
filter, the 7th argument passed through will likely have different behavior for you now.
postBy()
query doesn't return a node (#1059)extensions
are not passed in the response (#1071)is_graphql_http_request
to use site_url()
instead of home_url
(thanks @kidunot89!)graphql()
function to be used on page templates without breaking the admin header bar (#964)is_graphql_http_request()
and add tests (Thanks @esamattis!)Published by jasonbahl almost 5 years ago
This release is a breaking change release.
The focus of this release is on how Revisions are handled.
Prior to this release, Revisions are addd to the WPGraphQL Schema as their own Revision
Type. This largely mimics WordPress implementation, in that revisions are a different Post Type. However, this doesn't lend itself well to actual use.
Revisions of a Post, declaratively speaking, are still Posts. If I paint my house, it's still a house, just with different properties. If I change the title of a Post, it's still a Post, it should be considered as much in the Graph.
The intent of revisions is to show a thing (Post, Page, etc) as it exists over time.
The Revision
Type declares that the thing is in fact, a different thing, and the utility of Revisions is compromised.
By changing Revisions in the WPGraphQL Schema to be of the same Type of the object being revised, clients can actually represent their objects as they've been revised over time.
The Revision
Type no longer exists. If you were querying for Revisions your queries will need to be updated as the Type being returned will no longer be a Revision
.
When querying revisions from the Root, the Type returned to the connection is a ContentRevisionUnion
, which is comprised of any Types that support revisions (for example, Post and Page, or any Post Type that has registered support for revisions
)
You can now query revisions from the Root like so:
{
revisions {
nodes {
__typename
... on Post {
id
title
postId
}
... on Page {
id
title
pageId
}
}
}
}
and you would get results similar to the following (dependent on your data set):
{
"data": {
"revisions": {
"nodes": [
{
"__typename": "Post",
"id": "cmV2aXNpb246MTcxNQ==",
"title": "Another Test"
"postId":1715
},
{
"__typename": "Page",
"id": "cmV2aXNpb246MTY0Mg==",
"title": "Test Page Revision",
"pageId": 1642,
}
{
"__typename": "Post",
"id": "cmV2aXNpb246MTcxNA==",
"title": "Test revision",
"postId": 1543
}
]
}
},
}
If you were extending the Revision
Type, you will need to consider alternatives for extending the Schema for your use case.
Querying revisions of a Post will return a connection to Posts. The WordPress database will still fetch posts of the "revision" post type, but the GraphQL Schema will represent the revisions of a Post as a Post.
Querying revisions of a Page will return a connection to Pages. The WordPress database will still fetch posts of the "revision" post type, but the GraphQL Schema will represent the revisions of Pages as a Page.
Same goes for Custom Post Types.
Since revisions of a Post are always a Post, the connection is not a Union as it is when querying Revisions from the RootQuery, so we can query like so (of course this will vary based on your data set):
{
post(id: "cG9zdDoxNzEz") {
__typename
id
title
date
revisions {
nodes {
__typename
id
title
date
}
}
}
}
and you might get results like so:
{
"data": {
"post": {
"__typename": "Post",
"id": "cG9zdDoxNzEz",
"title": "Test",
"date": "2019-11-30T17:32:39",
"revisions": {
"nodes": [
{
"__typename": "Post",
"id": "cmV2aXNpb246MTcxNQ==",
"title": "Test",
"date": "2019-11-30T17:33:10"
},
{
"__typename": "Post",
"id": "cmV2aXNpb246MTcxNA==",
"title": "Test",
"date": "2019-11-30T17:32:20"
}
]
}
}
}
Because Revisions of a Post are now represented as a Post, we can make use of some great GraphQL features, such as Query Fragments. We could refactor the above query like below, using the same fragment for the primary Post and the revisions of it, allowing for the same components to be used to render a Post and a revision of a Post. Powerful stuff!:
{
post(id: "cG9zdDoxNzEz") {
__typename
...PostFields
revisions {
nodes {
__typename
...PostFields
}
}
}
}
fragment PostFields on Post {
id
title
date
}
closes #1017
Published by jasonbahl almost 5 years ago
is_graphql_request
and is_graphql_http_request
Thanks @esamattis @kidunot89!TypeRegistry->getTypes()
not available to graphql_register_types
actiondisplayName
to UserRole typePublished by jasonbahl almost 5 years ago
Published by jasonbahl almost 5 years ago
Router::is_graphql_request()
method - Thanks @esamattis!Page.isFrontPage
field to the Schema - Thanks @zgordon!Published by jasonbahl almost 5 years ago
This release is centered around Issue #846. Plugins that extend the WPGraphQL Schema are very difficult to test because once the Schema is created, it can't be cleared or modified. This is because of the heavy use of Statics, which make testing very difficult.
This Release changes many of the Static classes to classic objects that are instantiated and passed down using Dependency Injection.
This makes it much easier to create instances of the Schema on the fly, which is essential for extension plugins to do when testing their extensions. It also helps the core plugin properly test API methods like register_graphql_field
, register_graphql_mutation
, etc.
These changes make it easier for extensions to write and execute tests.
Because of the statics, the WPGraphQL Schema could be generated just once for the entire test suite, which means to test things like registering custom fields in ACF will add fields to the WPGraphQL Schema would mean that all fields would need to be added in the Bootstrap of the test suite, and not within the individual test that's testing the fields.
This is problematic to scaling tests. Ideally, we want to follow the AAA method of testing:
What this means is that if we wanted to test something like adding an ACF Text field (doesn't have to be ACF, just an example) we would want a test that:
We cannot do that today, but with this release, we can! Now, each test can "own" everything needed to test its behavior.
The primary breaking change here is making the TypeRegistry class no longer a singleton, so the methods are not statically accessible anymore. Instead, the TypeRegistry is an object that is passed down in the context of the graphql_register_types
action.
If your code was previously making use of any static methods from the TypeRegistry
class, such as TypeRegistry::get_type( 'TypeName' )
you will need to refactor a bit.
Upgrading
Below is an example from the WPGraphQL for Gravity Forms plugin that needs to change:
@kellenmace pointed out:
I am using TypeRegistry::get_type(), here: https://github.com/harness-software/wp-graphql-gravity-forms/blob/master/src/Types/Union/ObjectFieldUnion.php#L45
src/Types/Union/ObjectFieldUnion.php:45
To upgrade to v0.4.0, this code should be changed from:
public function register_type() {
$field_mappings = $this->get_field_type_mappings();
register_graphql_union_type( self::TYPE, [
'typeNames' => array_values( $field_mappings ),
'resolveType' => function( GF_Field $field ) use ( $field_mappings ) {
if ( isset( $field_mappings[ $field['type'] ] ) ) {
return TypeRegistry::get_type( $field_mappings[ $field['type'] ] );
}
return null;
},
] );
}
to:
public function register_type( $type_registry ) {
$field_mappings = $this->get_field_type_mappings();
register_graphql_union_type( self::TYPE, [
'typeNames' => array_values( $field_mappings ),
'resolveType' => function( GF_Field $field ) use ( $field_mappings, $type_registry ) {
if ( isset( $field_mappings[ $field['type'] ] ) ) {
return $type_registry->get_type( $field_mappings[ $field['type'] ] );
}
return null;
},
] );
}
Here we can see that the callback is passed the $type_registry
as it's a callback of the graphql_register_types
action. The $type_registry
is the instance of the TypeRegistry, and now the get_type
method can be used. We changed TypeRegistry::get_type()
to $type_registry->get_type()
.
NOTE:, we are using a closure for the resolveType
so we have to pass $typeRegistry
through with the use
statement.
Published by jasonbahl almost 5 years ago
none
none
Published by jasonbahl about 5 years ago
Shouldn't be any. Please report if you come across any issues.
Published by jasonbahl about 5 years ago
Published by jasonbahl about 5 years ago
If you were making use of filtering users by role, the behavior has changed due to #890 to respect access control properly.
contentType
field in Comments Schema. Thanks @let00!Published by jasonbahl over 5 years ago
Implemented enhancements:
Fixed bugs:
Closed issues:
Merged pull requests:
Published by jasonbahl over 5 years ago
If you were using WPGraphQL::$allowed_post_types
or WP_GraphQL::$allowed_taxonomies
in your custom code, we recommend updating to use WPGraphQL::get_allowed_post_types()
and WPGraphQL::get_allowed_taxonomies()
instead, as some changes in this PR could potentially cause you issues.
This release fixes several regressions that snuck by in the v0.3.0 release cycle.
$where
args for querying menus was not properly being respected post v0.3.0. Thanks for reporting and your patience as we resolved, @josephCarrington!WPGraphQL::$allowed_post_types
were causing funkiness.hideEmpty
description on Term connection args. Thanks @epeli!createComment
payload so the mutation can respond with something indicating the createComment
succeeded, even if the user doesn't have access to get an unapproved comment returned as a result of the mutation. Thanks for working through this with me @paulisloud!Published by jasonbahl over 5 years ago
show_in_graphql
during init
instead of init_graphql_request
now. This allows for get_post_types( [ 'show_in_graphql' => true ])
to work even outside the context of a GraphQL request. This is especially helpful for tools that want to know what Post Types and Taxonomies are set to show_in_graphql
.menuItems
was introduced as a regression during the v0.3.0 release. Thanks, @JosephCarrington for reporting and working through to confirm it's resolution.TypeRegistry::get_type()
was used for a not-yet registered Type.Published by jasonbahl over 5 years ago
This fixes a critical bug that was introduced by v0.3.0
There was a filter intended only to affect WP_Query calls within the GraphQL context, but it wasn't scoped properly and was affecting WP_Query calls outside of GraphQL requests, causing some funkiness in folks Dashboards.
It is recommended to update from v0.3.0 to v0.3.01 as soon as possible.
Published by jasonbahl over 5 years ago
These features required re-architecting quite a bit of the internals of the plugin. Some classes, functions and filters no longer exist, so if you had a codebase that extended WPGraphQL in any way, we recommend reading the upgrade guide.
This release had 2 primary focuses: Model Layer and DataLoader (Deferred Resolvers). These 2 features are critical to the performance and security of WPGraphQL. Below are some highlights of these features, but you can read more about them here as well.
There are also other new features and bugfixes listed below.
The role of DataLoader is to efficiently load data. It makes use of Deferred resolution of fields, allowing for the fetching of data to happen in batches. You can read more about it here and here:
The primary role of the Model Layer is to centralize access checks for objects. Some examples: a "draft" post should only be accessible to authenticated users with proper capabilities to edit posts. User email addresses should only be exposed to authenticated users with the "list_users" capability. Themes and plugins should only be accessible to authenticated users with proper capabilities as well.
Prior to this release, WPGraphQL helped expose various Types of data, but left the responsibility of controlling "Access" to the site owner by filtering field resolvers and handling Auth checks themselves.
This release puts much more responsibility on the core WPGraphQL plugin where it has a much more restrictive by default approach to exposing data. Data that a vanilla WordPress install doesn't publicly exposed is no longer publicly exposed by WPGraphQL.
Each field can be modified by filters, so if the restrictions WPGraphQL imposes are too restrictive, site owners can still adjust the restrictions to allow access to whatever they want users to have access to.
You can read a bit more about this here and here
As mentioned above, there are many breaking changes, specifically to how internal data is resolved. Below we've listed some high level breaking changes to look out for, but we recommend reading the upgrade guide for more information.
{user { id, roles { nodes { id, name } } } }
)postId
to commentOn
. The field is the ID of the Post Object that the comment should be connected to, but can be an ID of any Post Type.We had a formal security audit performed on the plugin by Simone Quatrini of Pen Test Partners. Fortunately, most of the issues reported by the audit were already being resolved by the Model Layer we had in progress at the time we received the report. Some additional issues were brought to our attention that were not on our radar and were addressed in this release. We recommend updating to this latest version to benefit from all the new features as well as the security fixes. In a week or two, we will publish more information about the security audit, but want to give folks the chance to update the plugin to the latest version before disclosing the issues that were reported.
Some of these new features were also mentioned above in Breaking Changes.
$context
from resolvers to the DataSource methods now, to take advantage of Deferred resolution.\WP_Post
or \WP_User
, etc. If you were registering fields to PostObject Types, or Term Types or User Types, or almost anything, if the object being passed as the first argument of the resolver was a core \WP_*
class instance, it will now be a WPGraphQL Model
instance. . .for example: \WP_Post
is now \WPGraphQL\Model\Post
being passed in.