From 31bca1db599a525dddbe97ef8043c578d5579df2 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Tue, 20 Apr 2021 12:37:14 -0600 Subject: [PATCH 01/12] - Refactor Fields, resolve field group prefixing issues, re-use of cloned field groups - deprecate class-config.php (will be removed for release) --- .gitignore | 1 - src/Fields/AcfField.php | 342 ++++ src/Fields/File.php | 50 + src/Fields/FlexibleContent.php | 125 ++ src/Fields/Gallery.php | 41 + src/Fields/Group.php | 34 + src/Fields/Image.php | 4 + src/Fields/PageLink.php | 4 + src/Fields/PostObject.php | 64 + src/Fields/Relationship.php | 45 + src/Fields/Repeater.php | 16 + src/Fields/Select.php | 37 + src/Fields/Taxonomy.php | 55 + src/Fields/User.php | 69 + src/Registry.php | 519 ++++++ .../InterfaceType/AcfFieldGroupInterface.php | 55 + src/Types/ObjectType/AcfGoogleMap.php | 107 ++ src/Types/ObjectType/AcfLink.php | 29 + src/class-acf.php | 4 +- src/class-acfsettings.php | 33 + src/deprecated-class-config.php | 1423 +++++++++++++++++ src/location-rules.php | 6 + tests/wpunit/PostObjectFieldsTest.php | 111 +- vendor/autoload.php | 2 +- vendor/composer/InstalledVersions.php | 4 +- vendor/composer/autoload_classmap.php | 17 + vendor/composer/autoload_real.php | 8 +- vendor/composer/autoload_static.php | 25 +- vendor/composer/installed.php | 4 +- vendor/composer/platform_check.php | 26 + wp-graphql-acf.php | 1 - 31 files changed, 3190 insertions(+), 71 deletions(-) create mode 100644 src/Fields/AcfField.php create mode 100644 src/Fields/File.php create mode 100644 src/Fields/FlexibleContent.php create mode 100644 src/Fields/Gallery.php create mode 100644 src/Fields/Group.php create mode 100644 src/Fields/Image.php create mode 100644 src/Fields/PageLink.php create mode 100644 src/Fields/PostObject.php create mode 100644 src/Fields/Relationship.php create mode 100644 src/Fields/Repeater.php create mode 100644 src/Fields/Select.php create mode 100644 src/Fields/Taxonomy.php create mode 100644 src/Fields/User.php create mode 100644 src/Registry.php create mode 100644 src/Types/InterfaceType/AcfFieldGroupInterface.php create mode 100644 src/Types/ObjectType/AcfGoogleMap.php create mode 100644 src/Types/ObjectType/AcfLink.php create mode 100644 src/deprecated-class-config.php create mode 100644 vendor/composer/platform_check.php diff --git a/.gitignore b/.gitignore index e452ba4..e6c29ad 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ codeception.yml vendor/* !vendor/autoload.php !vendor/composer -vendor/composer/platform_check.php vendor/composer/installed.json vendor/composer/*/ composer.lock diff --git a/src/Fields/AcfField.php b/src/Fields/AcfField.php new file mode 100644 index 0000000..9ef0d95 --- /dev/null +++ b/src/Fields/AcfField.php @@ -0,0 +1,342 @@ +registry = $registry; + $this->field_group = $field_group; + $this->field_config = $field; + $this->should_format = false; + $this->field_name = isset( $this->field_config['graphql_field_name'] ) ? Utils::format_field_name( $this->field_config['graphql_field_name'] ) : Utils::format_field_name( $this->field_config['name'] ); + $this->field_type = $this->field_config['type']; + } + + /** + * Given a node and an ACF Field Config, this determines the ID to use to resolve the field + * + * @param mixed $node The node the field belongs to + * @param array $acf_field The ACF Field config + * + * @return int|mixed|string + */ + public function get_acf_node_id( $node, array $acf_field ) { + + if ( is_array( $node ) && isset( $node['node'] ) && isset( $node['node']->ID ) ) { + return absint( $node['node']->ID ); + } + + switch ( true ) { + case $node instanceof Term: + $id = 'term_' . $node->term_id; + break; + case $node instanceof Post: + $id = absint( $node->databaseId ); + break; + case $node instanceof MenuItem: + $id = absint( $node->menuItemId ); + break; + case $node instanceof Menu: + $id = 'term_' . $node->menuId; + break; + case $node instanceof User: + $id = 'user_' . absint( $node->userId ); + break; + case $node instanceof Comment: + $id = 'comment_' . absint( $node->databaseId ); + break; + case is_array( $node ) && isset ( $node['node']['post_id'] ) && 'options' === $node['node']['post_id']: + $id = $node['node']['post_id']; + break; + default: + $id = 0; + break; + } + + return $id; + + } + + /** + * Returns the GraphQL Type of the parent field group + * + * @return string + */ + public function get_parent_type() { + return $this->registry->get_field_group_type_name( $this->field_group ); + } + + /** + * Returns the config array for the ACF Field + * + * @return array + */ + public function get_field_config() { + return $this->field_config; + } + + /** + * Determine if the field should ask ACF to format the response when retrieving + * the field using get_field() + * + * @return bool + */ + public function should_format_field_value() { + + if ( 'wysiwyg' === $this->field_type || 'select' === $this->field_type ) { + $this->should_format = true; + } + + return $this->should_format; + } + + /** + * Get the GraphQL Type to return for the field + * + * @return string|string[] + */ + public function get_graphql_type() { + + switch ( $this->field_config['type'] ) { + case 'number': + case 'range': + $type = 'float'; + break; + case 'true_false': + $type = 'boolean'; + break; + case 'link': + $type = 'AcfLink'; + break; + case 'checkbox': + $type = [ 'list_of' => 'String' ]; + break; + case 'gallery': + case 'date_picker': + case 'time_picker': + case 'date_time_picker': + case 'button_group': + case 'color_picker': + case 'email': + case 'text': + case 'message': + case 'oembed': + case 'password': + case 'wysiwyg': + case 'url': + case 'textarea': + case 'radio': + default: + $type = 'String'; + break; + } + + return $type; + } + + /** + * Extending classes must implement this function + * + * @param mixed $node The node the field being resolved is connected with + * @param array $args The arguments passed to the field in the GraphQL query + * @param AppContext $context The AppContext passed down to all resolvers + * @param ResolveInfo $info The ResolveInfo passed down to all resolvers + * + * @return mixed + */ + public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { + + // If the node is an array, and the type is options page, return the value early + // 🤔 This seems fragile 🤔 + if ( is_array( $node ) && ! ( ! empty( $node['type'] ) && 'options_page' === $node['type'] ) ) { + + if ( isset( $node[ $this->field_config['key'] ] ) ) { + $value = $node[ $this->field_config['key'] ]; + + if ( 'wysiwyg' === $this->field_config['type'] ) { + return apply_filters( 'the_content', $value ); + } + + } + } + + $node_id = $this->get_acf_node_id( $node, $this->field_config ); + $value = null; + + if ( is_array( $node ) && isset( $node[ $this->field_config['key'] ] ) ) { + return $this->prepare_acf_field_value( $node[ $this->field_config['key'] ], $node, $node_id ); + } + + if ( empty( $node_id ) ) { + return null; + } + + /** + * Filter the field value before resolving. + * + * @param mixed $value The value of the ACF Field stored on the node + * @param mixed $node The object the field is connected to + * @param mixed|string|int $node_id The ACF ID of the node to resolve the field with + * @param array $acf_field The ACF Field config + * @param bool $format Whether to apply formatting to the field + */ + $value = apply_filters( 'graphql_acf_pre_resolve_acf_field', $value, $node, $node_id, $this->get_field_config(), $this->should_format_field_value() ); + + if ( empty( $value ) ) { + + /** + * Check if cloned field and retrieve the key accordingly. + */ + if ( ! empty( $acf_field['_clone'] ) ) { + $key = $this->field_config['__key']; + } else { + $key = $this->field_config['key']; + } + + $value = get_field( $key, $node_id, $this->should_format_field_value() ); + + } + + $value = $this->prepare_acf_field_value( $value, $node, $node_id ); + + /** + * Filters the returned ACF field value + * + * @param mixed $value The resolved ACF field value + * @param array $acf_field The ACF field config + * @param mixed $node The node being resolved. The ID is typically a property of this object. + * @param int $node_id The ID of the node + */ + return apply_filters( 'graphql_acf_field_value', $value, $this->field_config, $node, $node_id ); + + } + + /** + * Prepares the ACF Field Value to be returned. + * + * @param mixed $value The value of the ACF field to return + * @param mixed $node The node the field belongs to + * @param mixed|string|int $node_id The ID of the node the field belongs to + * + * @return mixed + */ + public function prepare_acf_field_value( $value, $node, $node_id ) { + + if ( isset( $this->field_config['new_lines'] ) ) { + if ( 'wpautop' === $this->field_config['new_lines'] ) { + $value = wpautop( $value ); + } + if ( 'br' === $this->field_config['new_lines'] ) { + $value = nl2br( $value ); + } + } + + // @todo: This was ported over, but I'm not 💯 sure what this is solving and + // why it's only applied on options pages and not other pages 🤔 + if ( is_array( $node ) && ! ( ! empty( $node['type'] ) && 'options_page' === $node['type'] ) ) { + + if ( isset( $root[ $this->field_config['key'] ] ) ) { + $value = $root[ $this->field_config['key'] ]; + if ( 'wysiwyg' === $this->field_config['type'] ) { + $value = apply_filters( 'the_content', $value ); + } + + } + } + + if ( in_array( $this->field_type, [ + 'date_picker', + 'time_picker', + 'date_time_picker' + ], true ) ) { + + if ( ! empty( $value ) && isset( $this->field_config['return_format'] ) && ! empty( $this->field_config['return_format'] ) ) { + $value = date( $this->field_config['return_format'], strtotime( $value ) ); + } + } + + return $value; + } + + /** + * Registers a field to the WPGraphQL Schema + */ + public function register_field() { + + $type_name = $this->registry->get_field_group_type_name( $this->field_group ); + $graphql_field_name = Utils::format_field_name( $this->field_name ); + + if ( ! empty( $this->get_graphql_type() ) ) { + + register_graphql_field( $type_name, $graphql_field_name, [ + 'type' => $this->get_graphql_type(), + 'resolve' => function( $source, $args, $context, $info ) use ( $graphql_field_name ) { + return $this->resolve( $source, $args, $context, $info ); + } + ] ); + } + } + +} diff --git a/src/Fields/File.php b/src/Fields/File.php new file mode 100644 index 0000000..50f1fb2 --- /dev/null +++ b/src/Fields/File.php @@ -0,0 +1,50 @@ +get_parent_type(); + + $type_registry = $this->registry->get_type_registry(); + + $connection_name = ucfirst( $type_name ) . 'ToSingleMediaItemConnection'; + + $type_registry->register_connection([ + 'fromType' => $type_name, + 'toType' => 'MediaItem', + 'fromFieldName' => $this->field_name, + 'connectionTypeName' => $connection_name, + 'oneToOne' => true, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! absint( $value ) ) { + return null; + } + + $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'attachment' ); + return $resolver + ->one_to_one() + ->set_query_arg( 'p', absint( $value ) ) + ->get_connection(); + } + ]); + + return null; + + } + +} diff --git a/src/Fields/FlexibleContent.php b/src/Fields/FlexibleContent.php new file mode 100644 index 0000000..18fcf12 --- /dev/null +++ b/src/Fields/FlexibleContent.php @@ -0,0 +1,125 @@ +field_config['layouts'] ) ? $this->field_config['layouts'] : []; + + if ( empty( $layouts ) ) { + return null; + } + + $parent_type = $this->get_parent_type(); + $type_name = $parent_type . '_' . Utils::format_type_name( $this->field_config['name'] ); + $layout_interface_name = $type_name . '_Layout'; + + if ( null === $this->registry->get_type_registry()->get_type( $layout_interface_name ) ) { + + $this->registry->get_type_registry()->register_interface_type( $layout_interface_name, [ + 'description' => sprintf( __( 'Layouts of the %s Flexible Field Type', 'wp-graphql-acf' ), $layout_interface_name ), + 'fields' => [ + 'layoutName' => [ + 'type' => 'String', + 'description' => __( 'The name of the flexibile field layout', 'wp-graphql-acf' ), + ], + ], + 'resolveType' => function( $object ) use ( $layouts, $type_name ) { + return $type_name . '_' . Utils::format_type_name( $object['acf_fc_layout'] ); + } + ] ); + + } + + // Get the raw fields for the field group so we can + // determine which fields are clones and which fields are not + $raw_fields = acf_get_raw_fields( $this->field_config['key'] ); + $layout_type_names = []; + + /** + * Iterate over the layouts to determine their GraphQL Type + */ + foreach ( $layouts as $layout ) { + + $cloned = false; + + if ( ! isset( $layout['name'] ) ) { + continue; + } + + foreach ( $raw_fields as $raw_field ) { + if ( $layout['key'] === $raw_field['parent_layout'] ) { + $layout['raw_fields'][] = $raw_field; + if ( isset( $raw_field['clone'] ) && is_array( $raw_field['clone'] ) && 1 === count( $raw_field['clone'] ) ) { + + if ( 'Hero' === $raw_field['label'] ) { + + if ( false !== strpos( $raw_field['clone'][0], 'group_' ) ) { + $cloned_group = acf_get_field_group( $raw_field['clone'][0] ); + if ( is_array( $cloned_group ) && ! empty( $cloned_group ) ) { + $cloned = true; + $layout_type_names[] = $this->registry->get_field_group_type_name( $cloned_group ); + } + } + + } + } + } + } + + if ( true !== $cloned ) { + + $layout_type_name = $type_name . '_' . Utils::format_type_name( $layout['name'] ); + $layout['title'] = $layout['label']; + $layout['graphql_field_name'] = $layout_type_name; + + + if ( null === $this->registry->get_type_registry()->get_type( $layout_type_name ) ) { + + $this->registry->get_type_registry()->register_object_type( $layout_type_name, [ + 'description' => sprintf( __( '%s Flexible Field Layout', 'wp-graphql' ), $layout_type_name ), + 'interfaces' => [ 'AcfFieldGroup', $layout_interface_name ], + 'fields' => [ + 'layoutName' => [ + 'type' => 'String', + 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), + 'resolve' => function() use ( $layout ) { + return isset( $layout['label'] ) ? $layout['label'] : null; + } + ], + ] + ] ); + + } + + $layout_type_names[] = $layout_type_name; + + } + + if ( ! empty( $layout_type_names ) ) { + register_graphql_interfaces_to_types( $layout_interface_name, $layout_type_names ); + } + + $this->registry->map_acf_fields_to_field_group( $layout ); + + } + + return [ 'list_of' => $layout_interface_name ]; + } + +} diff --git a/src/Fields/Gallery.php b/src/Fields/Gallery.php new file mode 100644 index 0000000..cb0b15e --- /dev/null +++ b/src/Fields/Gallery.php @@ -0,0 +1,41 @@ +get_parent_type(); + + $type_registry = $this->registry->get_type_registry(); + + $type_registry->register_connection([ + 'fromType' => $type_name, + 'toType' => 'MediaItem', + 'fromFieldName' => $this->field_name, + 'oneToOne' => false, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return null; + } + + $value = array_map(function( $id ) { + return absint( $id ); + }, $value ); + + $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'attachment' ); + return $resolver + ->set_query_arg( 'post__in', $value ) + ->get_connection(); + } + ]); + + return null; + } + +} diff --git a/src/Fields/Group.php b/src/Fields/Group.php new file mode 100644 index 0000000..4a2c301 --- /dev/null +++ b/src/Fields/Group.php @@ -0,0 +1,34 @@ +get_parent_type(); + + $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); + + $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); + + $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); + + return ! empty( $type_name ) ? $type_name : null; + + } + +} diff --git a/src/Fields/Image.php b/src/Fields/Image.php new file mode 100644 index 0000000..e2c3b5d --- /dev/null +++ b/src/Fields/Image.php @@ -0,0 +1,4 @@ +get_parent_type(); + + $type_registry = $this->registry->get_type_registry(); + + $connection_config = [ + 'fromType' => $type_name, + 'toType' => 'ContentNode', + 'fromFieldName' => $this->field_name, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return null; + } + + $value = array_map(function( $id ) { + return absint( $id ); + }, $value ); + + $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'any' ); + return $resolver + ->set_query_arg( 'post__in', $value ) + ->get_connection(); + } + ]; + + if ( ! isset( $this->field_config['multiple'] ) || true !== (bool) $this->field_config['multiple'] ) { + $connection_config['connectionTypeName'] = ucfirst( $type_name ) . 'ToSingleContentNodeConnection'; + $connection_config['oneToOne'] = true; + $connection_config['resolve'] = function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! absint( $value ) ) { + return null; + } + + $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'any' ); + return $resolver + ->one_to_one() + ->set_query_arg( 'p', absint( $value ) ) + ->get_connection(); + }; + } + + $type_registry->register_connection( $connection_config ); + + } + +} diff --git a/src/Fields/Relationship.php b/src/Fields/Relationship.php new file mode 100644 index 0000000..0b671ea --- /dev/null +++ b/src/Fields/Relationship.php @@ -0,0 +1,45 @@ +get_parent_type(); + + $type_registry = $this->registry->get_type_registry(); + + $connection_config = [ + 'fromType' => $type_name, + 'toType' => 'ContentNode', + 'fromFieldName' => $this->field_name, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return null; + } + + $value = array_map(function( $id ) { + return absint( $id ); + }, $value ); + + $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'any' ); + return $resolver + ->set_query_arg( 'post__in', $value ) + ->set_query_arg( 'orderby', 'post__in' ) + ->get_connection(); + } + ]; + + $type_registry->register_connection( $connection_config ); + + + + + } + +} diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php new file mode 100644 index 0000000..9ace07c --- /dev/null +++ b/src/Fields/Repeater.php @@ -0,0 +1,16 @@ +get_parent_type(); + $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); + $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); + $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); + return ! empty( $type_name ) ? [ 'list_of' => $type_name ] : null; + } + +} diff --git a/src/Fields/Select.php b/src/Fields/Select.php new file mode 100644 index 0000000..868e160 --- /dev/null +++ b/src/Fields/Select.php @@ -0,0 +1,37 @@ +field_config['multiple'] ) && true === (bool) $this->field_config['multiple'] ) { + return [ 'list_of' => 'String' ]; + } + return 'String'; + } + + /** + * Return the value different based on single or mulitple selection allowed + * + * @param $node + * @param $args + * @param $context + * @param $info + * + * @return array|mixed|null + */ + public function resolve( $node, $args, $context, $info ) { + $value = parent::resolve( $node, $args, $context, $info ); + if ( isset( $this->field_config['multiple'] ) && true === (bool) $this->field_config['multiple'] ) { + return ! empty( $value ) && is_array( $value ) ? $value : []; + } + return $value; + } + +} diff --git a/src/Fields/Taxonomy.php b/src/Fields/Taxonomy.php new file mode 100644 index 0000000..e1ef95b --- /dev/null +++ b/src/Fields/Taxonomy.php @@ -0,0 +1,55 @@ +get_parent_type(); + + $type_registry = $this->registry->get_type_registry(); + + $connection_config = [ + 'fromType' => $type_name, + 'toType' => 'TermNode', + 'fromFieldName' => $this->field_name, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return null; + } + + $value = array_map(function( $id ) { + return absint( $id ); + }, $value ); + + $resolver = new TermObjectConnectionResolver( $root, $args, $context, $info ); + return $resolver + ->set_query_arg( 'include', $value ) + ->get_connection(); + } + ]; + + $type_registry->register_connection( $connection_config ); + + return null; + + } + +} diff --git a/src/Fields/User.php b/src/Fields/User.php new file mode 100644 index 0000000..80edebe --- /dev/null +++ b/src/Fields/User.php @@ -0,0 +1,69 @@ +get_parent_type(); + $type_registry = $this->registry->get_type_registry(); + $connection_config = [ + 'fromType' => $type_name, + 'toType' => 'User', + 'fromFieldName' => $this->field_name, + 'resolve' => function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! is_array( $value ) ) { + return null; + } + + $value = array_map(function( $id ) { + return absint( $id ); + }, $value ); + + $resolver = new UserConnectionResolver( $root, $args, $context, $info ); + return $resolver + ->set_query_arg( 'include', $value ) + ->get_connection(); + } + ]; + + if ( ! isset( $this->field_config['multiple'] ) || true !== (bool) $this->field_config['multiple'] ) { + $connection_config['connectionTypeName'] = ucfirst( $type_name ) . 'ToSingleUserConnection'; + $connection_config['oneToOne'] = true; + $connection_config['resolve'] = function( $root, $args, AppContext $context, $info ) { + $value = $this->resolve( $root, $args, $context, $info ); + + if ( empty( $value ) || ! absint( $value ) ) { + return null; + } + + $resolver = new UserConnectionResolver( $root, $args, $context, $info ); + return $resolver + ->one_to_one() + ->set_query_arg( 'include', absint( $value ) ) + ->get_connection(); + }; + } + + $type_registry->register_connection( $connection_config ); + + } + +} diff --git a/src/Registry.php b/src/Registry.php new file mode 100644 index 0000000..9ece1b8 --- /dev/null +++ b/src/Registry.php @@ -0,0 +1,519 @@ +type_registry = $type_registry; + + // Get all ACF Field Groups + $this->acf_field_groups = acf_get_field_groups(); + + // If there are no ACF Field Groups, don't proceed + if ( empty( $this->acf_field_groups ) || ! is_array( $this->acf_field_groups ) ) { + return; + } + + // Filters GraphQL meta resolvers for preview support of ACF Fields + add_filter( 'graphql_resolve_revision_meta_from_parent', [ + $this, + 'resolve_meta_from_parent' + ], 10, 4 ); + + // Register types + $this->map_acf_to_graphql(); + } + + /** + * @return TypeRegistry + */ + public function get_type_registry() { + return $this->type_registry; + } + + /** + * Determines whether meta should resolve from the requested object or the parent. This + * aids with previews. + * + * @param bool $should Whether the meta should resolve from the parent or not. + * @param mixed $object_id The ID of the object the field belongs to + * @param string $meta_key The name of the field + * @param bool $single Whether it's a singular field or a group + * + * @return bool + */ + public function resolve_meta_from_parent( bool $should, $object_id, string $meta_key, bool $single ) { + + // Loop through all registered ACF fields that show in GraphQL. + if ( is_array( $this->registered_field_names ) && ! empty( $this->registered_field_names ) ) { + + $matches = null; + + // Iterate over all field names + foreach ( $this->registered_field_names as $field_name ) { + + // If the field name is an exact match with the $meta_key, the ACF field should + // resolve from the revision meta, so we can return false here, so that meta can + // resolve from the revision instead of the parent + if ( $field_name === $meta_key ) { + return false; + } + + // For flex fields/repeaters, the meta keys are structured a bit funky. + // This checks to see if the $meta_key starts with the same string as one of the + // acf fields (a flex/repeater field) and then checks if it's preceeded by an underscore and a number. + if ( $field_name === substr( $meta_key, 0, strlen( $field_name ) ) ) { + // match any string that starts with the field name, followed by an underscore, followed by a number, followed by another string + // ex my_flex_field_0_text_field or some_repeater_field_12_25MostPopularDogToys + $pattern = '/' . $field_name . '_\d+_\w+/m'; + preg_match( $pattern, $meta_key, $matches ); + } + + // If the meta key matches the pattern, treat it as a sub-field of an ACF Field Group + if ( null !== $matches ) { + return false; + } + + } + + } + + return $should; + + } + + /** + * Register Types to the WPGraphQL Schema + * + * @return void + * @throws Exception + */ + protected function map_acf_to_graphql() { + + // Register initial pre-defined types + $this->register_initial_types(); + $this->register_options_pages(); + + // Map User created Field Groups to the Schema + $this->map_acf_field_groups_to_types(); + + } + + /** + * + */ + public function register_options_pages() { + + $options_pages = acf_get_options_pages(); + if ( empty( $options_pages ) || ! is_array( $options_pages ) ) { + return; + } + + foreach ( $options_pages as $options_page ) { + + if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + continue; + } + + $page_title = $options_page['page_title']; + $page_slug = $options_page['menu_slug']; + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + + if ( null === $this->type_registry->get_type( $type_name ) ) { + + $this->type_registry->register_object_type( $type_name, [ + 'description' => sprintf( __( '%s options. Registered as an ACF Options page.', 'wp-graphql-acf' ), $page_title ), + 'fields' => [ + 'pageTitle' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_title ) { + return ! empty( $page_title ) ? $page_title : null; + }, + ], + 'pageSlug' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_slug ) { + return ! empty( $page_slug ) ? $page_slug : null; + }, + ], + ], + ] ); + + } + + $field_name = Utils::format_field_name( $type_name ); + + $this->type_registry->register_field( + 'RootQuery', + $field_name, + [ + 'type' => $type_name, + 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ), + 'resolve' => function() use ( $options_page ) { + return ! empty( $options_page ) ? $options_page : null; + } + ] + ); + + + } + + } + + /** + * Register initial types to the Schema + * + * @throws Exception + */ + public function register_initial_types() { + + // Interfaces + AcfFieldGroupInterface::register_type( $this ); + + // Object Types + AcfLink::register_type(); + AcfGoogleMap::register_type(); + + $this->type_registry->register_field( 'RootQuery', 'acfFieldGroup', [ + 'description' => __( 'ACF Field Group', 'wp-graphql-acf' ), + 'type' => 'AcfFieldGroup', + 'args' => [ + 'id' => [ + 'type' => [ 'non_null' => 'ID' ], + ], + ], + 'resolve' => function( $root, $args, $context, $info ) { + + $id_parts = Relay::fromGlobalId( $args['id'] ); + $field_group = isset( $id_parts['id'] ) ? acf_get_field_group( $id_parts['id'] ) : null; + + return [ + 'fieldGroupName' => isset( $field_group['title'] ) ? $field_group['title'] : null, + '_fieldGroupConfig' => ! empty( $field_group ) ? $field_group : null, + ]; + + } + ] ); + + } + + /** + * Map user generated field groups to the Schema + */ + public function map_acf_field_groups_to_types() { + + foreach ( $this->acf_field_groups as $field_group ) { + $this->add_acf_field_group_to_graphql( $field_group ); + } + + } + + /** + * @param $field_group + * + * @return mixed|string|null + * + * @throws Exception + */ + public function add_acf_field_group_to_graphql( $field_group ) { + + if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { + return null; + } + + $type_name = $this->get_field_group_type_name( $field_group ); + $interface_name = 'With' . $type_name; + + if ( null === $this->type_registry->get_type( $type_name ) ) { + + $this->type_registry->register_object_type( $type_name, [ + 'description' => __( 'Acf Field Group', 'wp-graphql' ), + 'interfaces' => [ 'AcfFieldGroup', 'Node' ], + 'fields' => [ + 'id' => [ + 'resolve' => function() use ( $field_group ) { + return Relay::toGlobalId( 'AcfFieldGroup', $field_group['ID'] ); + } + ], + '_fieldGroupConfig' => [ + 'resolve' => function( $root ) use ( $field_group ) { + return $field_group; + }, + ], + 'fieldGroupName' => [ + 'resolve' => function() use ( $type_name ) { + return isset( $type_name ) ? lcfirst( $type_name ) : null; + } + ] + ], + ] ); + + } + + $this->map_acf_fields_to_field_group( $field_group ); + + $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); + + if ( ! empty( $graphql_types ) && is_array( $graphql_types ) ) { + + if ( null === $this->type_registry->get_type( $interface_name ) ) { + + $this->type_registry->register_interface_type( $interface_name, [ + 'description' => sprintf( __( 'A node that can have fields of the "%s" Field Group.', 'wp-graphql-acf' ), $type_name ), + 'fields' => [ + lcfirst( $type_name ) => [ + 'type' => $type_name, + 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $type_name ), + 'resolve' => function( $root ) use ( $field_group ) { + return ! empty( $root ) ? $root : $field_group; + + } + ], + ], + ] ); + + } + + register_graphql_interfaces_to_types( [ $interface_name ], $graphql_types ); + + $field_name = isset( $field_group['graphql_field_name'] ) ? lcfirst( $field_group['graphql_field_name'] ) : lcfirst( $type_name ); + + foreach ( $graphql_types as $graphql_type ) { + + $this->type_registry->register_field( $graphql_type, $field_name, [ + 'type' => $type_name, + 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $graphql_type ), + 'resolve' => function( $root ) use ( $field_group ) { + return [ + 'node' => $root, + 'field_group' => $field_group + ]; + } + ] ); + + } + } + + return $type_name; + } + + /** + * @param $field_group + * + * @return array|mixed + */ + public function get_graphql_types_for_field_group( $field_group ) { + + $graphql_types = isset( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : []; + + $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $field_group_name = Utils::format_field_name( $field_group_name ); + + $manually_set_graphql_types = isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false; + + if ( false === $manually_set_graphql_types || empty( $graphql_types ) ) { + if ( ! isset( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) { + $location_rules = $this->get_location_rules(); + if ( isset( $location_rules[ $field_group_name ] ) ) { + $graphql_types = $location_rules[ $field_group_name ]; + } + } + } + + return $graphql_types; + + } + + /** + * Gets the location rules + * + * @return array + */ + protected function get_location_rules() { + + $field_groups = $this->acf_field_groups; + $rules = []; + + // Each field group that doesn't have GraphQL Types explicitly set should get the location + // rules interpreted. + foreach ( $field_groups as $field_group ) { + if ( ! isset( $field_group['graphql_types'] ) || ! is_array( $field_group['graphql_types'] ) ) { + $rules[] = $field_group; + } + } + + if ( empty( $rules ) ) { + return []; + } + + // If there are field groups with no graphql_types field set, inherit the rules from + // ACF Location Rules + $rules = new LocationRules(); + $rules->determine_location_rules(); + + return $rules->get_rules(); + } + + /** + * Determines whether a field group should be exposed to the GraphQL Schema. By default, field + * groups will not be exposed to GraphQL. + * + * @param array $field_group Undocumented. + * + * @return bool + */ + protected function should_field_group_show_in_graphql( $field_group ) { + + /** + * By default, field groups will not be exposed to GraphQL. + */ + $show = false; + + /** + * If the field group is set to show_in_graphql, show it + */ + if ( isset( $field_group['show_in_graphql'] ) && true === (bool) $field_group['show_in_graphql'] ) { + $show = true; + } + + /** + * Whether a field group should show in GraphQL. + * + * @var boolean $show Whether the field group should show in the GraphQL Schema + * @var array $field_group The ACF Field Group + * @var Config $this The Config for the ACF Plugin + */ + return apply_filters( 'wpgraphql_acf_should_field_group_show_in_graphql', $show, $field_group, $this ); + + } + + /** + * @param array $field_group + * + * @return string + */ + public function get_field_group_type_name( array $field_group ) { + $type_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $type_name = ucfirst( $type_name ); + + return $type_name; + } + + /** + * Map Fields to the Field Groups in the Schema + * + * @param array $field_group + */ + public function map_acf_fields_to_field_group( array $field_group ) { + + // Get the ACF Fields for the specified field group + $fields = isset( $field_group['sub_fields'] ) && is_array( $field_group['sub_fields'] ) ? $field_group['sub_fields'] : acf_get_fields( $field_group ); + + // If there are no for the field group, do nothing. + if ( empty( $fields ) || ! is_array( $fields ) ) { + return; + } + + // Store a list of field keys that have been registered + // to help avoid registering the same field twice on one + // field group. This occasionally happens with clone fields. + $registered_field_keys = []; + + foreach ( $fields as $field ) { + + // If a field is empty or not an array, it's not valid + if ( empty( $field ) || ! is_array( $field ) ) { + continue; + } + + // If a field doesn't have a name or key, it's not valid + if ( ! isset( $field['name'] ) || ! isset( $field['key'] ) ) { + continue; + } + + // If a field is specifically set to not show in GraphQL, don't proceed + if ( isset( $field['show_in_graphql'] ) && false === $field['show_in_graphql'] ) { + continue; + } + + // Prevent duplicate cloned fields from being registered to the same field group + if ( in_array( $field['key'], $registered_field_keys, true ) ) { + continue; + } + + $this->register_graphql_field( $field, $field_group ); + + } + + } + + /** + * @param array $field The ACF Field config + * @param array $field_group The ACF Field Group Config + */ + public function register_graphql_field( array $field, array $field_group ) { + + $field_type = isset( $field['type'] ) ? $field['type'] : null; + + $class_name = Utils::format_type_name( $field_type ); + $class_name = '\\WPGraphQL\\ACF\Fields\\' . $class_name; + + /** + * This allows 3rd party extensions to hook and and provide + * a path to their class for registering a field to the Schema + */ + $class_name = apply_filters( 'graphql_acf_field_class', $class_name, $field, $field_group, $this ); + + if ( class_exists( $class_name ) ) { + $field_class = new $class_name( $field, $field_group, $this ); + $field_class->register_field(); + } else { + $field_class = new AcfField( $field, $field_group, $this ); + $field_class->register_field(); + } + + } + +} diff --git a/src/Types/InterfaceType/AcfFieldGroupInterface.php b/src/Types/InterfaceType/AcfFieldGroupInterface.php new file mode 100644 index 0000000..55f2390 --- /dev/null +++ b/src/Types/InterfaceType/AcfFieldGroupInterface.php @@ -0,0 +1,55 @@ + __( 'Configuration settings of an ACF Field Group.', 'wp-graphql' ), + 'fields' => [ + 'databaseId' => [ + 'type' => 'Int', + 'resolve' => function( $field_group ) { + return $field_group['ID']; + } + ], + 'key' => [ + 'type' => 'String', + ], + ], + ] ); + + register_graphql_interface_type('AcfFieldGroup', + [ + 'description' => __( 'A Field Group registered by ACF', 'wp-graphql-acf' ), + 'fields' => [ + 'fieldGroupName' => [ + 'description' => __( 'The name of the ACF Field Group', 'wp-graphql-acf' ), + 'deprecationReason' => __( 'Deprecated in favor of "_fieldGroupConfig"', 'wp-graphql-acf' ), + 'type' => 'String', + ], + '_fieldGroupConfig' => [ + 'type' => 'AcfFieldGroupConfig', + 'description' => __( 'Configuration settings of an ACF Field Group', 'wp-graphql' ), + ] + ], + 'resolveType' => function( $field_group ) use ( $registry ) { + if ( ! empty( $field_group['_fieldGroupConfig'] ) ) { + $field_group = $field_group['_fieldGroupConfig']; + return $registry->get_field_group_type_name( $field_group ); + } + return null; + } + ] + ); + } +} diff --git a/src/Types/ObjectType/AcfGoogleMap.php b/src/Types/ObjectType/AcfGoogleMap.php new file mode 100644 index 0000000..f348332 --- /dev/null +++ b/src/Types/ObjectType/AcfGoogleMap.php @@ -0,0 +1,107 @@ + __( 'A group of fields representing a Google Map', 'wp-graphql-acf' ), + 'fields' => [ + 'streetAddress' => [ + 'type' => 'String', + 'description' => __( 'The street address associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['address'] ) ? $root['address'] : null; + }, + ], + 'latitude' => [ + 'type' => 'Float', + 'description' => __( 'The latitude associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['lat'] ) ? $root['lat'] : null; + }, + ], + 'longitude' => [ + 'type' => 'Float', + 'description' => __( 'The longitude associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['lng'] ) ? $root['lng'] : null; + }, + ], + 'streetName' => [ + 'type' => 'String', + 'description' => __( 'The street name associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['street_name'] ) ? $root['street_name'] : null; + }, + ], + 'streetNumber' => [ + 'type' => 'String', + 'description' => __( 'The street number associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['street_number'] ) ? $root['street_number'] : null; + }, + ], + 'city' => [ + 'type' => 'String', + 'description' => __( 'The city associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['city'] ) ? $root['city'] : null; + }, + ], + 'state' => [ + 'type' => 'String', + 'description' => __( 'The state associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['state'] ) ? $root['state'] : null; + }, + ], + 'stateShort' => [ + 'type' => 'String', + 'description' => __( 'The state abbreviation associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['state_short'] ) ? $root['state_short'] : null; + }, + ], + 'postCode' => [ + 'type' => 'String', + 'description' => __( 'The post code associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['post_code'] ) ? $root['post_code'] : null; + }, + ], + 'country' => [ + 'type' => 'String', + 'description' => __( 'The country associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['country'] ) ? $root['country'] : null; + }, + ], + 'countryShort' => [ + 'type' => 'String', + 'description' => __( 'The country abbreviation associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['country_short'] ) ? $root['country_short'] : null; + }, + ], + 'placeId' => [ + 'type' => 'String', + 'description' => __( 'The country associated with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['place_id'] ) ? $root['place_id'] : null; + }, + ], + 'zoom' => [ + 'type' => 'String', + 'description' => __( 'The zoom defined with the map', 'wp-graphql-acf' ), + 'resolve' => function( $root ) { + return isset( $root['zoom'] ) ? $root['zoom'] : null; + }, + ], + ] + ] ); + + } + +} diff --git a/src/Types/ObjectType/AcfLink.php b/src/Types/ObjectType/AcfLink.php new file mode 100644 index 0000000..0e9364b --- /dev/null +++ b/src/Types/ObjectType/AcfLink.php @@ -0,0 +1,29 @@ + __( 'ACF Link field', 'wp-graphql-acf' ), + 'fields' => [ + 'url' => [ + 'type' => 'String', + 'description' => __( 'The url of the link', 'wp-graphql-acf' ), + ], + 'title' => [ + 'type' => 'String', + 'description' => __( 'The title of the link', 'wp-graphql-acf' ), + ], + 'target' => [ + 'type' => 'String', + 'description' => __( 'The target of the link (_blank, etc)', 'wp-graphql-acf' ), + ], + ], + ] ); + } + +} diff --git a/src/class-acf.php b/src/class-acf.php index e7f8e21..2526a77 100644 --- a/src/class-acf.php +++ b/src/class-acf.php @@ -149,8 +149,8 @@ private function filters() { */ private function init() { - $config = new Config(); - add_action( 'graphql_register_types', [ $config, 'init' ], 10, 1 ); + $registry = new Registry(); + add_action( 'graphql_register_types', [ $registry, 'init' ], 10, 1 ); $acf_settings = new ACF_Settings(); $acf_settings->init(); diff --git a/src/class-acfsettings.php b/src/class-acfsettings.php index da411ac..7611ee4 100644 --- a/src/class-acfsettings.php +++ b/src/class-acfsettings.php @@ -40,6 +40,39 @@ public function init() { */ add_action( 'wp_ajax_get_acf_field_group_graphql_types', [ $this, 'ajax_callback' ] ); + add_filter( 'manage_acf-field-group_posts_columns', [ $this, 'admin_table_columns' ], 15, 1 ); + add_action( 'manage_acf-field-group_posts_custom_column', [ $this, 'admin_table_columns_html' ], 15, 2 ); + + } + + public function admin_table_columns_html( $column_name, $post_id ) { + + $field_group = acf_get_field_group( $post_id ); + $location_rules = new LocationRules( [ $field_group ] ); + $location_rules->determine_location_rules(); + $rules = $location_rules->get_rules(); + + $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $group_name = $location_rules->format_field_name( $group_name ); + + switch( $column_name ) { + case 'graphql_types': + echo isset( $rules[ $group_name ] ) ? implode( ', ', $rules[ $group_name ] ) : ''; + break; + case 'graphql_field_name': + echo isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : ''; + break; + default: + break; + } + + } + + public function admin_table_columns( $columns ) { + $columns['graphql_types'] = __( 'GraphQL Schema Location', 'wp-graphql-acf' ); + $columns['graphql_field_name'] = __( 'GraphQL Field Name', 'wp-graphql-acf' ); + return $columns; + } /** diff --git a/src/deprecated-class-config.php b/src/deprecated-class-config.php new file mode 100644 index 0000000..3a8b490 --- /dev/null +++ b/src/deprecated-class-config.php @@ -0,0 +1,1423 @@ + List of field names registered to the Schema + */ + protected $registered_field_names; + + /** + * @var array List of options page slugs registered to the Schema + */ + protected $registered_options_pages = []; + + /** + * Initialize WPGraphQL to ACF + * + * @param TypeRegistry $type_registry Instance of the WPGraphQL TypeRegistry + * + * @throws Exception + */ + public function init( TypeRegistry $type_registry ) { + + /** + * Set the TypeRegistry + */ + $this->type_registry = $type_registry; + $this->register_initial_types(); + + /** + * Add ACF Fields to GraphQL Types + */ + $this->add_options_pages_to_schema(); + $this->add_acf_fields_to_graphql_types(); + + // This filter tells WPGraphQL to resolve revision meta for ACF fields from the revision's meta, instead + // of the parent (published post) meta. + add_filter( 'graphql_resolve_revision_meta_from_parent', function( $should, $object_id, $meta_key, $single ) { + + // Loop through all registered ACF fields that show in GraphQL. + if ( is_array( $this->registered_field_names ) && ! empty( $this->registered_field_names ) ) { + + $matches = null; + + // Iterate over all field names + foreach ( $this->registered_field_names as $field_name ) { + + // If the field name is an exact match with the $meta_key, the ACF field should + // resolve from the revision meta, so we can return false here, so that meta can + // resolve from the revision instead of the parent + if ( $field_name === $meta_key ) { + return false; + } + + // For flex fields/repeaters, the meta keys are structured a bit funky. + // This checks to see if the $meta_key starts with the same string as one of the + // acf fields (a flex/repeater field) and then checks if it's preceeded by an underscore and a number. + if ( $field_name === substr( $meta_key, 0, strlen( $field_name ) ) ) { + // match any string that starts with the field name, followed by an underscore, followed by a number, followed by another string + // ex my_flex_field_0_text_field or some_repeater_field_12_25MostPopularDogToys + $pattern = '/' . $field_name . '_\d+_\w+/m'; + preg_match( $pattern, $meta_key, $matches ); + } + + // If the meta key matches the pattern, treat it as a sub-field of an ACF Field Group + if ( null !== $matches ) { + return false; + } + + } + + } + + return $should; + }, 10, 4 ); + } + + /** + * Registers initial Types for use with ACF Fields + * + * @throws Exception + */ + public function register_initial_types() { + + $this->type_registry->register_interface_type( + 'AcfFieldGroup', + [ + 'description' => __( 'A Field Group registered by ACF', 'wp-graphql-acf' ), + 'fields' => [ + 'fieldGroupName' => [ + 'description' => __( 'The name of the ACF Field Group', 'wp-graphql-acf' ), + 'type' => 'String', + ], + ] + ] + ); + + $this->type_registry->register_object_type( + 'AcfLink', + [ + 'description' => __( 'ACF Link field', 'wp-graphql-acf' ), + 'fields' => [ + 'url' => [ + 'type' => 'String', + 'description' => __( 'The url of the link', 'wp-graphql-acf' ), + ], + 'title' => [ + 'type' => 'String', + 'description' => __( 'The title of the link', 'wp-graphql-acf' ), + ], + 'target' => [ + 'type' => 'String', + 'description' => __( 'The target of the link (_blank, etc)', 'wp-graphql-acf' ), + ], + ], + ] + ); + + } + + + + /** + * Gets the location rules + * @return array + */ + protected function get_location_rules() { + + $field_groups = acf_get_field_groups(); + if ( empty( $field_groups ) || ! is_array( $field_groups ) ) { + return []; + } + + $rules = []; + + // Each field group that doesn't have GraphQL Types explicitly set should get the location + // rules interpreted. + foreach ( $field_groups as $field_group ) { + if ( ! isset( $field_group['graphql_types'] ) || ! is_array( $field_group['graphql_types'] ) ) { + $rules[] = $field_group; + } + } + + if ( empty( $rules ) ) { + return []; + } + + // If there are field groups with no graphql_types field set, inherit the rules from + // ACF Location Rules + $rules = new LocationRules(); + $rules->determine_location_rules(); + return $rules->get_rules(); + } + + protected function add_options_pages_to_schema() { + + global $acf_options_page; + + if ( ! isset( $acf_options_page ) ) { + return ; + } + + /** + * Get a list of post types that have been registered to show in graphql + */ + $graphql_options_pages = acf_get_options_pages(); + + /** + * If there are no post types exposed to GraphQL, bail + */ + if ( empty( $graphql_options_pages ) || ! is_array( $graphql_options_pages ) ) { + return; + } + + $options_pages_to_register = []; + + /** + * Loop over the post types exposed to GraphQL + */ + foreach ( $graphql_options_pages as $options_page_key => $options_page ) { + if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + continue; + } + + /** + * Get options page properties. + */ + $page_title = $options_page['page_title']; + $page_slug = $options_page['menu_slug']; + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + + $options_pages_to_register[ $type_name ] = [ + 'title' => $page_title, + 'slug' => $page_slug, + 'type_name' => $type_name, + 'options_page' => $options_page, + ]; + + } + + if ( is_array( $options_pages_to_register ) && ! empty( $options_pages_to_register ) ) { + + foreach ( $options_pages_to_register as $page_to_register ) { + + $page_title = $page_to_register['title']; + $page_slug = $page_to_register['slug']; + $type_name = isset( $page_to_register['type_name'] ) ? Utils::format_type_name( $page_to_register['type_name'] ) : Utils::format_type_name( $page_to_register['slug'] ); + $options_page = $page_to_register['options_page']; + + $this->type_registry->register_object_type( $type_name, [ + 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ), + 'fields' => [ + 'pageTitle' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_title ) { + return ! empty( $page_title ) ? $page_title : null; + }, + ], + 'pageSlug' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_slug ) { + return ! empty( $page_slug ) ? $page_slug : null; + }, + ], + ], + ] ); + + $field_name = Utils::format_field_name( $type_name ); + + $this->type_registry->register_field( + 'RootQuery', + $field_name, + [ + 'type' => $type_name, + 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ), + 'resolve' => function() use ( $options_page ) { + return ! empty( $options_page ) ? $options_page : null; + } + ] + ); + + } + } + + } + + /** + * Determines whether a field group should be exposed to the GraphQL Schema. By default, field + * groups will not be exposed to GraphQL. + * + * @param array $field_group Undocumented. + * + * @return bool + */ + protected function should_field_group_show_in_graphql( $field_group ) { + + /** + * By default, field groups will not be exposed to GraphQL. + */ + $show = false; + + /** + * If the field group is set to show_in_graphql, show it + */ + if ( isset( $field_group['show_in_graphql'] ) && true === (bool) $field_group['show_in_graphql'] ) { + $show = true; + } + + /** + * Whether a field group should show in GraphQL. + * + * @var boolean $show Whether the field group should show in the GraphQL Schema + * @var array $field_group The ACF Field Group + * @var Config $this The Config for the ACF Plugin + */ + return apply_filters( 'wpgraphql_acf_should_field_group_show_in_graphql', $show, $field_group, $this ); + + } + + /** + * Undocumented function + * + * @todo: This may be a good utility to add to WPGraphQL Core? May even have something already? + * + * @param string $str Unknown. + * @param array $no_strip Unknown. + * + * @return mixed|null|string|string[] + */ + public static function camel_case( $str, array $no_strip = [] ) { + // non-alpha and non-numeric characters become spaces. + $str = preg_replace( '/[^a-z0-9' . implode( '', $no_strip ) . ']+/i', ' ', $str ); + $str = trim( $str ); + // Lowercase the string + $str = strtolower( $str ); + // uppercase the first character of each word. + $str = ucwords( $str ); + // Replace spaces + $str = str_replace( ' ', '', $str ); + // Lowecase first letter + $str = lcfirst( $str ); + + return $str; + } + + /** + * Undocumented function + * + * @param [type] $root Undocumented. + * @param [type] $acf_field Undocumented. + * @param boolean $format Whether ACF should apply formatting to the field. Default false. + * + * @return mixed + */ + protected function get_acf_field_value( $root, $acf_field, $format = false ) { + + $value = null; + $id = null; + + if ( is_array( $root ) && isset( $root['node'] ) ) { + $id = $root['node']->ID; + } + + if ( is_array( $root ) && ! ( ! empty( $root['type'] ) && 'options_page' === $root['type'] ) ) { + + if ( isset( $root[ $acf_field['key'] ] ) ) { + $value = $root[ $acf_field['key'] ]; + + if ( 'wysiwyg' === $acf_field['type'] ) { + $value = apply_filters( 'the_content', $value ); + } + + } + } else { + + switch ( true ) { + + case $root instanceof Term: + $id = 'term_' . $root->term_id; + break; + case $root instanceof Post: + $id = absint( $root->databaseId ); + break; + case $root instanceof MenuItem: + $id = absint( $root->menuItemId ); + break; + case $root instanceof Menu: + $id = 'term_' . $root->menuId; + break; + case $root instanceof User: + $id = 'user_' . absint( $root->userId ); + break; + case $root instanceof Comment: + $id = 'comment_' . absint( $root->databaseId ); + break; + case is_array( $root ) && ! empty( $root['type'] ) && 'options_page' === $root['type']: + $id = $root['post_id']; + break; + default: + $id = null; + break; + } + } + + if ( empty( $value ) ) { + + /** + * Filters the root ID, allowing additional Models the ability to provide a way to resolve their ID + * + * @param int $id The ID of the object. Default null + * @param mixed $root The Root object being resolved. The ID is typically a property of this object. + */ + $id = apply_filters( 'graphql_acf_get_root_id', $id, $root ); + + if ( empty( $id ) ) { + return null; + } + + $format = false; + + if ( 'wysiwyg' === $acf_field['type'] ) { + $format = true; + } + + if ( 'select' === $acf_field['type'] ) { + $format = true; + } + + /** + * Check if cloned field and retrieve the key accordingly. + */ + if ( ! empty( $acf_field['_clone'] ) ) { + $key = $acf_field['__key']; + } else { + $key = $acf_field['key']; + } + + $field_value = get_field( $key, $id, $format ); + + $value = ! empty( $field_value ) ? $field_value : null; + + } + + /** + * Filters the returned ACF field value + * + * @param mixed $value The resolved ACF field value + * @param array $acf_field The ACF field config + * @param mixed $root The Root object being resolved. The ID is typically a property of this object. + * @param int $id The ID of the object + */ + return apply_filters( 'graphql_acf_field_value', $value, $acf_field, $root, $id ); + + } + + /** + * Get a list of supported fields that WPGraphQL for ACF supports. + * + * This is helpful for determining whether UI should be output for the field, and whether + * the field should be added to the Schema. + * + * Some fields, such as "Accordion" are not supported currently. + * + * @return array + */ + public static function get_supported_fields() { + $supported_fields = [ + 'text', + 'textarea', + 'number', + 'range', + 'email', + 'url', + 'password', + 'image', + 'file', + 'wysiwyg', + 'oembed', + 'gallery', + 'select', + 'checkbox', + 'radio', + 'button_group', + 'true_false', + 'link', + 'post_object', + 'page_link', + 'relationship', + 'taxonomy', + 'user', + 'google_map', + 'date_picker', + 'date_time_picker', + 'time_picker', + 'color_picker', + 'group', + 'repeater', + 'flexible_content' + ]; + + /** + * filter the supported fields + * + * @param array $supported_fields + */ + return apply_filters( 'wpgraphql_acf_supported_fields', $supported_fields ); + } + + /** + * Undocumented function + * + * @param string $type_name The name of the GraphQL Type to add the field to. + * @param string $field_name The name of the field to add to the GraphQL Type. + * @param array $config The GraphQL configuration of the field. + * + * @return mixed + */ + protected function register_graphql_field( string $type_name, string $field_name, array $config ) { + $acf_field = isset( $config['acf_field'] ) ? $config['acf_field'] : null; + $acf_type = isset( $acf_field['type'] ) ? $acf_field['type'] : null; + + if ( empty( $acf_type ) ) { + return false; + } + + /** + * filter the field config for custom field types + * + * @param array $field_config + */ + $field_config = apply_filters( 'wpgraphql_acf_register_graphql_field', [ + 'type' => null, + 'resolve' => isset( $config['resolve'] ) && is_callable( $config['resolve'] ) ? $config['resolve'] : function( $root, $args, $context, $info ) use ( $acf_field, $acf_type ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + return ! empty( $value ) ? $value : null; + }, + ], $type_name, $field_name, $config ); + + switch ( $acf_type ) { + case 'button_group': + case 'color_picker': + case 'email': + case 'text': + case 'message': + case 'oembed': + case 'password': + case 'wysiwyg': + case 'url': + // Even though Selects and Radios in ACF can _technically_ be an integer + // we're choosing to always cast as a string because with + // GraphQL we can't return different types + $field_config['type'] = 'String'; + break; + case 'textarea': + $field_config['type'] = 'String'; + $field_config['resolve'] = function( $root ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + if ( ! empty( $acf_field['new_lines'] ) ) { + if ( 'wpautop' === $acf_field['new_lines'] ) { + $value = wpautop( $value ); + } + if ( 'br' === $acf_field['new_lines'] ) { + $value = nl2br( $value ); + } + } + return $value; + + + }; + break; + case 'select': + + /** + * If the select field is configured to not allow multiple values + * the field will return a string, but if it is configured to allow + * multiple values it will return a list of strings, and an empty array + * if no values are set. + * + * @see: https://github.com/wp-graphql/wp-graphql-acf/issues/25 + */ + if ( empty( $acf_field['multiple'] ) ) { + if('array' === $acf_field['return_format'] ){ + $field_config['type'] = [ 'list_of' => 'String' ]; + $field_config['resolve'] = function( $root ) use ( $acf_field) { + $value = $this->get_acf_field_value( $root, $acf_field, true); + + return ! empty( $value ) && is_array( $value ) ? $value : []; + }; + }else{ + $field_config['type'] = 'String'; + } + } else { + $field_config['type'] = [ 'list_of' => 'String' ]; + $field_config['resolve'] = function( $root ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + return ! empty( $value ) && is_array( $value ) ? $value : []; + }; + } + break; + case 'radio': + $field_config['type'] = 'String'; + break; + case 'number': + case 'range': + $field_config['type'] = 'Float'; + break; + case 'true_false': + $field_config['type'] = 'Boolean'; + break; + case 'date_picker': + case 'time_picker': + case 'date_time_picker': + $field_config = [ + 'type' => 'String', + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + + $value = $this->get_acf_field_value( $root, $acf_field, true ); + + if ( ! empty( $value ) && ! empty( $acf_field['return_format'] ) ) { + $value = date( $acf_field['return_format'], strtotime( $value ) ); + } + return ! empty( $value ) ? $value : null; + }, + ]; + break; + case 'relationship': + + if ( isset( $acf_field['post_type'] ) && is_array( $acf_field['post_type'] ) ) { + + $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); + + if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) { + $type = $field_type_name; + } else { + $type_names = []; + foreach ( $acf_field['post_type'] as $post_type ) { + if ( in_array( $post_type, get_post_types([ 'show_in_graphql' => true ]), true ) ) { + $type_names[ $post_type ] = get_post_type_object( $post_type )->graphql_single_name; + } + } + + if ( empty( $type_names ) ) { + $type = 'PostObjectUnion'; + } else { + register_graphql_union_type( $field_type_name, [ + 'typeNames' => $type_names, + 'resolveType' => function( $value ) use ( $type_names ) { + $post_type_object = get_post_type_object( $value->post_type ); + return ! empty( $post_type_object->graphql_single_name ) ? $this->type_registry->get_type( $post_type_object->graphql_single_name ) : null; + } + ] ); + + $type = $field_type_name; + } + + + } + } else { + $type = 'PostObjectUnion'; + } + + $field_config = [ + 'type' => [ 'list_of' => $type ], + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $relationship = []; + $value = $this->get_acf_field_value( $root, $acf_field ); + + if ( ! empty( $value ) && is_array( $value ) ) { + foreach ( $value as $post_id ) { + $post_object = get_post( $post_id ); + if ( $post_object instanceof \WP_Post ) { + $post_model = new Post( $post_object ); + $relationship[] = $post_model; + } + } + } + + return isset( $value ) ? $relationship : null; + + }, + ]; + break; + case 'page_link': + case 'post_object': + + if ( isset( $acf_field['post_type'] ) && is_array( $acf_field['post_type'] ) ) { + $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); + if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) { + $type = $field_type_name; + } else { + $type_names = []; + foreach ( $acf_field['post_type'] as $post_type ) { + if ( in_array( $post_type, \get_post_types( [ 'show_in_graphql' => true ] ), true ) ) { + $type_names[ $post_type ] = get_post_type_object( $post_type )->graphql_single_name; + } + } + + if ( empty( $type_names ) ) { + $field_config['type'] = null; + break; + } + + register_graphql_union_type( $field_type_name, [ + 'typeNames' => $type_names, + 'resolveType' => function( $value ) use ( $type_names ) { + $post_type_object = get_post_type_object( $value->post_type ); + return ! empty( $post_type_object->graphql_single_name ) ? $this->type_registry->get_type( $post_type_object->graphql_single_name ) : null; + } + ] ); + + $type = $field_type_name; + } + } else { + $type = 'PostObjectUnion'; + } + + // If the field is allowed to be a multi select + if ( 0 !== $acf_field['multiple'] ) { + $type = [ 'list_of' => $type ]; + } + + $field_config = [ + 'type' => $type, + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + $return = []; + if ( ! empty( $value ) ) { + if ( is_array( $value ) ) { + foreach ( $value as $id ) { + $post = get_post( $id ); + if ( ! empty( $post ) ) { + $return[] = new Post( $post ); + } + } + } else { + $post = get_post( absint( $value ) ); + if ( ! empty( $post ) ) { + $return[] = new Post( $post ); + } + } + } + + // If the field is allowed to be a multi select + if ( 0 !== $acf_field['multiple'] ) { + $return = ! empty( $return ) ? $return : null; + } else { + $return = ! empty( $return[0] ) ? $return[0] : null; + } + + /** + * This hooks allows for filtering of the post object source. In case an non-core defined + * post-type is being targeted. + * + * @param mixed|null $source GraphQL Type source. + * @param mixed|null $value Root ACF field value. + * @param AppContext $context AppContext instance. + * @param ResolveInfo $info ResolveInfo instance. + */ + return apply_filters( + 'graphql_acf_post_object_source', + $return, + $value, + $context, + $info + ); + + }, + ]; + break; + case 'link': + $field_config['type'] = 'AcfLink'; + break; + case 'image': + case 'file': + $field_config = [ + 'type' => 'MediaItem', + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + return DataSource::resolve_post_object( (int) $value, $context ); + }, + ]; + break; + case 'checkbox': + $field_config = [ + 'type' => [ 'list_of' => 'String' ], + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + return is_array( $value ) ? $value : null; + }, + ]; + break; + case 'gallery': + $field_config = [ + 'type' => [ 'list_of' => 'MediaItem' ], + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + $gallery = []; + if ( ! empty( $value ) && is_array( $value ) ) { + foreach ( $value as $image ) { + $post_object = get_post( (int) $image ); + if ( $post_object instanceof \WP_Post ) { + $post_model = new Post( $post_object ); + $gallery[] = $post_model; + } + } + } + + return isset( $value ) ? $gallery : null; + }, + ]; + break; + case 'user': + + $type = 'User'; + + if ( isset( $acf_field['multiple'] ) && 1 === $acf_field['multiple'] ) { + $type = [ 'list_of' => $type ]; + } + + $field_config = [ + 'type' => $type, + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + $return = []; + if ( ! empty( $value ) ) { + if ( is_array( $value ) ) { + foreach ( $value as $id ) { + $user = get_user_by( 'id', $id ); + if ( ! empty( $user ) ) { + $user = new User( $user ); + if ( 'private' !== $user->get_visibility() ) { + $return[] = $user; + } + } + } + } else { + $user = get_user_by( 'id', absint( $value ) ); + if ( ! empty( $user ) ) { + $user = new User( $user ); + if ( 'private' !== $user->get_visibility() ) { + $return[] = $user; + } + } + } + } + + // If the field is allowed to be a multi select + if ( 0 !== $acf_field['multiple'] ) { + $return = ! empty( $return ) ? $return : null; + } else { + $return = ! empty( $return[0] ) ? $return[0] : null; + } + + return $return; + }, + ]; + break; + case 'taxonomy': + + $type = 'TermObjectUnion'; + + if ( isset( $acf_field['taxonomy'] ) ) { + $tax_object = get_taxonomy( $acf_field['taxonomy'] ); + if ( isset( $tax_object->graphql_single_name ) ) { + $type = $tax_object->graphql_single_name; + } + } + + $is_multiple = isset( $acf_field['field_type'] ) && in_array( $acf_field['field_type'], array( 'checkbox', 'multi_select' ) ); + + $field_config = [ + 'type' => $is_multiple ? ['list_of' => $type ] : $type, + 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field, $is_multiple ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + /** + * If this is multiple, the value will most likely always be an array. + * If it isn't, we want to return a single term id. + */ + if ( ! empty( $value ) && is_array( $value ) ) { + foreach ( $value as $term ) { + $terms[] = DataSource::resolve_term_object( (int) $term, $context ); + } + return $terms; + } else { + return DataSource::resolve_term_object( (int) $value, $context ); + } + }, + ]; + break; + + // Accordions are not represented in the GraphQL Schema. + case 'accordion': + $field_config = null; + break; + case 'group': + + $field_type_name = ucfirst( Utils::format_type_name( $acf_field['name'] ) ); + + if ( $this->type_registry->get_type( strtolower( $field_type_name ) ) ) { + $field_config['type'] = $field_type_name; + } else { + + if ( isset( $acf_field['parent'] ) && ! empty( $acf_field['parent'] ) ) { + + $parent_group = acf_get_field_group( $acf_field['parent'] ); + + + if ( isset( $parent_group['graphql_field_name' ] ) ) { + if ( $this->type_registry->get_type( strtolower( $parent_group['graphql_field_name' ] ) ) ) { + $field_config['type'] = $parent_group['graphql_field_name' ]; + break; + } + } + + $field_type_name = $type_name . '_' . Utils::format_type_name( $acf_field['name'] ); + } + + $this->type_registry->register_object_type( + $field_type_name, + [ + 'description' => __( 'Field Group', 'wp-graphql-acf' ), + 'interfaces' => [ 'AcfFieldGroup' ], + 'fields' => [ + 'fieldGroupName' => [ + 'resolve' => function( $source ) use ( $acf_field ) { + return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null; + }, + ], + ], + ] + ); + + } + + $this->add_field_group_fields( $acf_field, $field_type_name ); + + $field_config['type'] = $field_type_name; + + break; + + case 'google_map': + $field_type_name = 'ACF_GoogleMap'; + $field_config['type'] = $field_type_name; + break; + case 'repeater': + $field_type_name = $type_name . '_' . self::camel_case( $acf_field['name'] ); + + if ( $this->type_registry->get_type( $field_type_name ) ) { + $field_config['type'] = $field_type_name; + break; + } + + $this->type_registry->register_object_type( + $field_type_name, + [ + 'description' => __( 'Field Group', 'wp-graphql-acf' ), + 'interfaces' => [ 'AcfFieldGroup' ], + 'fields' => [ + 'fieldGroupName' => [ + 'resolve' => function( $source ) use ( $acf_field ) { + return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null; + }, + ], + ], + 'resolve' => function( $source ) use ( $acf_field ) { + $repeater = $this->get_acf_field_value( $source, $acf_field ); + + return ! empty( $repeater ) ? $repeater : []; + }, + ] + ); + + $this->add_field_group_fields( $acf_field, $field_type_name ); + + $field_config['type'] = [ 'list_of' => $field_type_name ]; + break; + + /** + * Flexible content fields should return a Union of the Layouts that can be configured. + * + * + * Example Query of a flex field with the name "flex_field" and 2 groups + * + * { + * post { + * flexField { + * ...on GroupOne { + * textField + * textAreaField + * } + * ...on GroupTwo { + * imageField { + * id + * title + * } + * } + * } + * } + * } + * + */ + case 'flexible_content': + + $field_config = null; + $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); + if ( $this->type_registry->get_type( $field_type_name ) ) { + $field_config['type'] = $field_type_name; + break; + } + + if ( ! empty( $acf_field['layouts'] ) && is_array( $acf_field['layouts'] ) ) { + + $union_types = []; + foreach ( $acf_field['layouts'] as $layout ) { + + $flex_field_layout_name = ! empty( $layout['name'] ) ? ucfirst( self::camel_case( $layout['name'] ) ) : null; + $flex_field_layout_name = ! empty( $flex_field_layout_name ) ? $field_type_name . '_' . $flex_field_layout_name : null; + + /** + * If there are no layouts defined for the Flex Field + */ + if ( empty( $flex_field_layout_name ) ) { + continue; + } + + $layout_type = $this->type_registry->get_type( $flex_field_layout_name ); + + if ( $layout_type ) { + $union_types[ $layout['name'] ] = $layout_type; + } else { + + + $this->type_registry->register_object_type( $flex_field_layout_name, [ + 'description' => __( 'Group within the flex field', 'wp-graphql-acf' ), + 'interfaces' => [ 'AcfFieldGroup' ], + 'fields' => [ + 'fieldGroupName' => [ + 'resolve' => function( $source ) use ( $flex_field_layout_name ) { + return ! empty( $flex_field_layout_name ) ? $flex_field_layout_name : null; + }, + ], + ], + ] ); + + $union_types[ $layout['name'] ] = $flex_field_layout_name; + + + $layout['parent'] = $acf_field; + $layout['show_in_graphql'] = isset( $acf_field['show_in_graphql'] ) ? (bool) $acf_field['show_in_graphql'] : true; + $this->add_field_group_fields( $layout, $flex_field_layout_name, true ); + } + } + + $this->type_registry->register_union_type( $field_type_name, [ + 'typeNames' => $union_types, + 'resolveType' => function( $value ) use ( $union_types ) { + return isset( $union_types[ $value['acf_fc_layout'] ] ) ? $this->type_registry->get_type( $union_types[ $value['acf_fc_layout'] ] ) : null; + } + ] ); + + $field_config['type'] = [ 'list_of' => $field_type_name ]; + $field_config['resolve'] = function( $root, $args, $context, $info ) use ( $acf_field ) { + $value = $this->get_acf_field_value( $root, $acf_field ); + + return ! empty( $value ) ? $value : []; + }; + } + break; + default: + break; + } + + if ( empty( $field_config ) || empty( $field_config['type'] ) ) { + return null; + } + + $config = array_merge( $config, $field_config ); + $this->registered_field_names[] = $acf_field['name']; + + $this->type_registry->register_field( $type_name, $field_name, $config ); + } + + /** + * Given a field group array, this adds the fields to the specified Type in the Schema + * + * @param array $field_group The group to add to the Schema. + * @param string $type_name The Type name in the GraphQL Schema to add fields to. + * @param bool $layout Whether or not these fields are part of a Flex Content layout. + */ + protected function add_field_group_fields( array $field_group, string $type_name, $layout = false ) { + + /** + * If the field group has the show_in_graphql setting configured, respect it's setting + * otherwise default to true (for nested fields) + */ + $field_group['show_in_graphql'] = isset( $field_group['show_in_graphql'] ) ? (boolean) $field_group['show_in_graphql'] : true; + + /** + * Determine if the field group should be exposed + * to graphql + */ + if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { + return; + } + + /** + * Get the fields in the group. + */ + $acf_fields = ! empty( $field_group['sub_fields'] ) || $layout ? $field_group['sub_fields'] : acf_get_fields( $field_group ); + + /** + * If there are no fields, bail + */ + if ( empty( $acf_fields ) || ! is_array( $acf_fields ) ) { + return; + } + + /** + * Stores field keys to prevent duplicate field registration for cloned fields + */ + $processed_keys = []; + + /** + * Loop over the fields and register them to the Schema + */ + foreach ( $acf_fields as $acf_field ) { + if ( in_array( $acf_field['key'], $processed_keys, true ) ) { + continue; + } else { + $processed_keys[] = $acf_field['key']; + } + + /** + * Setup data for register_graphql_field + */ + $explicit_name = ! empty( $acf_field['graphql_field_name'] ) ? $acf_field['graphql_field_name'] : null; + $name = empty( $explicit_name ) && ! empty( $acf_field['name'] ) ? self::camel_case( $acf_field['name'] ) : $explicit_name; + $show_in_graphql = isset( $acf_field['show_in_graphql'] ) ? (bool) $acf_field['show_in_graphql'] : true; + $description = isset( $acf_field['instructions'] ) ? $acf_field['instructions'] : __( 'ACF Field added to the Schema by WPGraphQL ACF' ); + + /** + * If the field is missing a name or a type, + * we can't add it to the Schema. + */ + if ( + empty( $name ) || + true != $show_in_graphql + ) { + + /** + * Uncomment line below to determine what fields are not going to be output + * in the Schema. + */ + continue; + } + + $config = [ + 'name' => $name, + 'description' => $description, + 'acf_field' => $acf_field, + 'acf_field_group' => $field_group, + ]; + + $this->register_graphql_field( $type_name, $name, $config ); + + } + + } + + /** + * Returns all available GraphQL Types + * + * @return array + */ + public static function get_all_graphql_types() { + $graphql_types = array(); + + // Use GraphQL to get the Interface and the Types that implement them + $query = ' + query GetPossibleTypes($name:String!){ + __type(name:$name){ + name + description + possibleTypes { + name + description + } + } + } + '; + + $interfaces = [ + 'ContentNode' => [ + 'label' => __( 'Post Type', 'wp-graphql-acf' ), + 'plural_label' => __( 'All Post Types', 'wp-graphql-acf' ), + ], + 'TermNode' => [ + 'label' => __( 'Taxonomy', 'wp-graphql-acf' ), + 'plural_label' => __( 'All Taxonomies', 'wp-graphql-acf' ), + ], + 'ContentTemplate' => [ + 'label' => __( 'Page Template', 'wp-graphql-acf' ), + 'plural_label' => __( 'All Templates Assignable to Content', 'wp-graphql-acf' ), + ] + ]; + + foreach ( $interfaces as $interface_name => $config ) { + + $interface_query = graphql([ + 'query' => $query, + 'variables' => [ + 'name' => $interface_name + ] + ]); + + $possible_types = $interface_query['data']['__type']['possibleTypes']; + asort( $possible_types ); + + if ( ! empty( $possible_types ) && is_array( $possible_types ) ) { + + // Intentionally not translating "ContentNode Interface" as this is part of the GraphQL Schema and should not be translated. + $graphql_types[ $interface_name ] = '' . $interface_name . ' Interface (' . $config['plural_label'] . ')'; + $label = ' (' . $config['label'] . ')'; + foreach ( $possible_types as $type ) { + $type_label = $type['name'] . $label; + $type_key = $type['name']; + + $graphql_types[ $type_key ] = $type_label; + } + } + + } + + /** + * Add comment to GraphQL types + */ + $graphql_types['Comment'] = __( 'Comment', 'wp-graphql-acf' ); + + /** + * Add menu to GraphQL types + */ + $graphql_types['Menu'] = __( 'Menu', 'wp-graphql-acf' ); + + /** + * Add menu items to GraphQL types + */ + $graphql_types['MenuItem'] = __( 'Menu Item', 'wp-graphql-acf' ); + + /** + * Add users to GraphQL types + */ + $graphql_types['User'] = __( 'User', 'wp-graphql-acf' ); + + + /** + * Add options pages to GraphQL types + */ + global $acf_options_page; + + if ( isset( $acf_options_page ) ) { + /** + * Get a list of post types that have been registered to show in graphql + */ + $graphql_options_pages = acf_get_options_pages(); + + /** + * If there are no post types exposed to GraphQL, bail + */ + if ( ! empty( $graphql_options_pages ) && is_array( $graphql_options_pages ) ) { + + /** + * Prepare type key prefix and label surfix + */ + $label = ' (' . __( 'ACF Options Page', 'wp-graphql-acf' ) . ')'; + + /** + * Loop over the post types exposed to GraphQL + */ + foreach ( $graphql_options_pages as $options_page_key => $options_page ) { + if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + continue; + } + + /** + * Get options page properties. + */ + $page_title = $options_page['page_title']; + $type_label = $page_title . $label; + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + + $graphql_types[ $type_name ] = $type_label; + } + } + } + + return $graphql_types; + } + + /** + * Adds acf field groups to GraphQL types. + */ + protected function add_acf_fields_to_graphql_types() { + /** + * Get all the field groups + */ + $field_groups = acf_get_field_groups(); + + /** + * If there are no acf field groups, bail + */ + if ( empty( $field_groups ) || ! is_array( $field_groups ) ) { + return; + } + + /** + * Loop over all the field groups + */ + foreach ( $field_groups as $field_group ) { + + $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $field_group_name = Utils::format_field_name( $field_group_name ); + + $manually_set_graphql_types = isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false; + + if ( false === $manually_set_graphql_types ) { + if ( ! isset( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) { + $field_group['graphql_types'] = []; + $location_rules = $this->get_location_rules(); + if ( isset( $location_rules[ $field_group_name ] ) ) { + $field_group['graphql_types'] = $location_rules[ $field_group_name ]; + } + } + } + + /** + * Determine if the field group should be exposed + * to graphql + */ + if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { + continue; + } + + $graphql_types = array_unique( $field_group['graphql_types'] ); + $graphql_types = array_filter( $graphql_types ); + + /** + * Prepare default info + */ + $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] ); + $field_group['type'] = 'group'; + $field_group['name'] = $field_name; + $config = [ + 'name' => $field_name, + 'acf_field' => $field_group, + 'acf_field_group' => null, + 'resolve' => function ( $root ) use ( $field_group ) { + return isset( $root ) ? $root : null; + } + ]; + + $qualifier = sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was set to Show in GraphQL.', 'wp-graphql-acf' ), $field_group['title'] ); + $config['description'] = $field_group['description'] ? $field_group['description'] . ' | ' . $qualifier : $qualifier; + + $field_type_name = $field_name; + $interface_name = 'With' . ucfirst( $field_type_name ); + + if ( ! $this->type_registry->get_type( $field_type_name ) ) { + + $this->type_registry->register_object_type( + $field_type_name, + [ + 'description' => __( 'Field Group', 'wp-graphql-acf' ), + 'interfaces' => [ 'AcfFieldGroup' ], + 'fields' => [ + 'fieldGroupName' => [ + 'resolve' => function( $source ) use ( $config ) { + return ! empty( $config['name'] ) ? $config['name'] : null; + }, + ], + ], + ] + ); + + $this->add_field_group_fields( $field_group, $field_type_name ); + + } + + if ( ! $this->type_registry->get_type( strtolower( $interface_name ) ) && ! empty( $graphql_types ) ) { + + $explicit_name = ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null; + $name = empty( $explicit_name ) && ! empty( $field_group['name'] ) ? self::camel_case( $field_group['name'] ) : $explicit_name; + + $this->type_registry->register_interface_type( $interface_name, [ + 'description' => sprintf( __( 'A node that can have fields of the "%s" Field Group.', 'wp-graphql-acf' ), $field_type_name ), + 'fields' => [ + $name => [ + 'type' => $field_type_name, + 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $field_type_name ), + 'resolve' => function( $root ) use ( $field_group ) { + return $root; + } + ], + ], + + ] ); + + register_graphql_interfaces_to_types( [ $interface_name ], $graphql_types ); + + /** + * Loop over the GraphQL types for this field group on + */ + foreach ( $graphql_types as $graphql_type ) { + $this->register_graphql_field( $graphql_type, $field_name, $config ); + } + + } + + } + + } + +} diff --git a/src/location-rules.php b/src/location-rules.php index 36a17f4..760b273 100644 --- a/src/location-rules.php +++ b/src/location-rules.php @@ -382,6 +382,10 @@ public function determine_location_rules() { $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + if ( ! isset( $field_group['active'] ) || false === (bool) $field_group['active'] ) { + continue; + } + if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) { foreach ( $field_group['location'] as $location_rule_group ) { @@ -942,7 +946,9 @@ public function determine_options_rules( string $field_group_name, string $param if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { return; } + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + $this->set_graphql_type( $field_group_name, $type_name ); } diff --git a/tests/wpunit/PostObjectFieldsTest.php b/tests/wpunit/PostObjectFieldsTest.php index 13d8d0e..93bfb3c 100644 --- a/tests/wpunit/PostObjectFieldsTest.php +++ b/tests/wpunit/PostObjectFieldsTest.php @@ -411,11 +411,13 @@ public function testQueryImageField() { title postFields { imageField { - mediaItemId - thumbnail: sourceUrl(size: THUMBNAIL) - medium: sourceUrl(size: MEDIUM) - full: sourceUrl(size: LARGE) - sourceUrl + node { + mediaItemId + thumbnail: sourceUrl(size: THUMBNAIL) + medium: sourceUrl(size: MEDIUM) + full: sourceUrl(size: LARGE) + sourceUrl + } } } } @@ -437,7 +439,7 @@ public function testQueryImageField() { 'medium' => wp_get_attachment_image_src( $img_id, 'medium' )[0], 'full' => wp_get_attachment_image_src( $img_id, 'full' )[0], 'sourceUrl' => wp_get_attachment_image_src( $img_id, 'full' )[0] - ], $actual['data']['postBy']['postFields']['imageField'] ); + ], $actual['data']['postBy']['postFields']['imageField']['node'] ); } @@ -464,8 +466,10 @@ public function testQueryFileField() { title postFields { fileField { - mediaItemId - sourceUrl + node { + mediaItemId + sourceUrl + } } } } @@ -484,7 +488,7 @@ public function testQueryFileField() { $this->assertEquals( [ 'mediaItemId' => $img_id, 'sourceUrl' => wp_get_attachment_image_src( $img_id, 'full' )[0] - ], $actual['data']['postBy']['postFields']['fileField'] ); + ], $actual['data']['postBy']['postFields']['fileField']['node'] ); } @@ -603,8 +607,10 @@ public function testQueryGalleryField() { title postFields { galleryField { - mediaItemId - sourceUrl + nodes { + mediaItemId + sourceUrl + } } } } @@ -621,15 +627,15 @@ public function testQueryGalleryField() { $this->assertArrayNotHasKey( 'errors', $actual ); $this->assertSame( [ - [ - 'mediaItemId' => $img_id_1, - 'sourceUrl' => wp_get_attachment_image_src( $img_id_1, 'full' )[0] - ], [ 'mediaItemId' => $img_id_2, 'sourceUrl' => wp_get_attachment_image_src( $img_id_2, 'full' )[0] + ], + [ + 'mediaItemId' => $img_id_1, + 'sourceUrl' => wp_get_attachment_image_src( $img_id_1, 'full' )[0] ] - ], $actual['data']['postBy']['postFields']['galleryField'] ); + ], $actual['data']['postBy']['postFields']['galleryField']['nodes'] ); } @@ -911,12 +917,14 @@ public function testQueryPostObjectField() { title postFields { postObjectField { - __typename - ...on Post { - postId - } - ...on Page { - pageId + node { + __typename + ...on Post { + postId + } + ...on Page { + pageId + } } } } @@ -936,7 +944,7 @@ public function testQueryPostObjectField() { $this->assertSame( [ '__typename' => 'Post', 'postId' => $post_id, - ], $actual['data']['postBy']['postFields']['postObjectField'] ); + ], $actual['data']['postBy']['postFields']['postObjectField']['node'] ); } @@ -962,12 +970,14 @@ public function testQueryPostObjectFieldWithPage() { title postFields { postObjectField { - __typename - ...on Post { - postId - } - ...on Page { - pageId + node { + __typename + ...on Post { + postId + } + ...on Page { + pageId + } } } } @@ -987,7 +997,7 @@ public function testQueryPostObjectFieldWithPage() { $this->assertSame( [ '__typename' => 'Page', 'pageId' => $page_id, - ], $actual['data']['postBy']['postFields']['postObjectField'] ); + ], $actual['data']['postBy']['postFields']['postObjectField']['node'] ); } @@ -1017,9 +1027,11 @@ public function testQueryPageLinkField() { title postFields { pageLinkField { - __typename - ...on Post { - postId + node { + __typename + ...on Post { + postId + } } } } @@ -1039,7 +1051,7 @@ public function testQueryPageLinkField() { $this->assertSame( [ '__typename' => 'Post', 'postId' => $id, - ], $actual['data']['postBy']['postFields']['pageLinkField'] ); + ], $actual['data']['postBy']['postFields']['pageLinkField']['node'] ); } @@ -1253,7 +1265,7 @@ public function testQueryFieldOnCustomPostType() { $query = ' { - __type( name: "AcfTest_Acftestfields" ) { + __type( name: "AcfTestFields" ) { name description fields { @@ -1331,11 +1343,7 @@ public function testQueryRelationshipField() { 'post_title' => 'Test Page', ]); - $filename = ( $this->test_image ); - $img_id = $this->factory()->attachment->create_upload_object( $filename ); - - - update_field( 'relationship_field', [ $post_id, $page_id, $img_id ], $this->post_id ); + update_field( 'relationship_field', [ $post_id, $page_id ], $this->post_id ); $query = ' query GET_POST_WITH_ACF_FIELD( $postId: Int! ) { @@ -1344,15 +1352,14 @@ public function testQueryRelationshipField() { title postFields { relationshipField { - __typename - ...on Post { - postId - } - ...on Page { - pageId - } - ...on MediaItem { - mediaItemId + nodes { + __typename + ...on Post { + postId + } + ...on Page { + pageId + } } } } @@ -1378,11 +1385,7 @@ public function testQueryRelationshipField() { '__typename' => 'Page', 'pageId' => $page_id, ], - [ - '__typename' => 'MediaItem', - 'mediaItemId' => $img_id, - ] - ], $actual['data']['postBy']['postFields']['relationshipField'] ); + ], $actual['data']['postBy']['postFields']['relationshipField']['nodes'] ); } diff --git a/vendor/autoload.php b/vendor/autoload.php index 2829c15..19ed6d9 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit22bfbd1509018e9010b8ca40b9b6700b::getLoader(); +return ComposerAutoloaderInit25bca2cfacbb71bc0509958a96747af9::getLoader(); diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 1c7c828..70ff569 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -30,7 +30,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'e35e895bf7d35b7bcc5f4eacc287e9f22de91a02', + 'reference' => 'd0ca31f6391c80cd370d4da71aa185658ee3c5b9', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -42,7 +42,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'e35e895bf7d35b7bcc5f4eacc287e9f22de91a02', + 'reference' => 'd0ca31f6391c80cd370d4da71aa185658ee3c5b9', ), ), ); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 7d3d8b2..447a269 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -10,5 +10,22 @@ 'WPGraphQL\\ACF\\ACF' => $baseDir . '/src/class-acf.php', 'WPGraphQL\\ACF\\ACF_Settings' => $baseDir . '/src/class-acfsettings.php', 'WPGraphQL\\ACF\\Config' => $baseDir . '/src/class-config.php', + 'WPGraphQL\\ACF\\Fields\\AcfField' => $baseDir . '/src/Fields/AcfField.php', + 'WPGraphQL\\ACF\\Fields\\File' => $baseDir . '/src/Fields/File.php', + 'WPGraphQL\\ACF\\Fields\\FlexibleContent' => $baseDir . '/src/Fields/FlexibleContent.php', + 'WPGraphQL\\ACF\\Fields\\Gallery' => $baseDir . '/src/Fields/Gallery.php', + 'WPGraphQL\\ACF\\Fields\\Group' => $baseDir . '/src/Fields/Group.php', + 'WPGraphQL\\ACF\\Fields\\Image' => $baseDir . '/src/Fields/Image.php', + 'WPGraphQL\\ACF\\Fields\\PageLink' => $baseDir . '/src/Fields/PageLink.php', + 'WPGraphQL\\ACF\\Fields\\PostObject' => $baseDir . '/src/Fields/PostObject.php', + 'WPGraphQL\\ACF\\Fields\\Relationship' => $baseDir . '/src/Fields/Relationship.php', + 'WPGraphQL\\ACF\\Fields\\Repeater' => $baseDir . '/src/Fields/Repeater.php', + 'WPGraphQL\\ACF\\Fields\\Select' => $baseDir . '/src/Fields/Select.php', + 'WPGraphQL\\ACF\\Fields\\Taxonomy' => $baseDir . '/src/Fields/Taxonomy.php', + 'WPGraphQL\\ACF\\Fields\\User' => $baseDir . '/src/Fields/User.php', 'WPGraphQL\\ACF\\LocationRules' => $baseDir . '/src/location-rules.php', + 'WPGraphQL\\ACF\\Registry' => $baseDir . '/src/Registry.php', + 'WPGraphQL\\ACF\\Types\\InterfaceType\\AcfFieldGroupInterface' => $baseDir . '/src/Types/InterfaceType/AcfFieldGroupInterface.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfGoogleMap' => $baseDir . '/src/Types/ObjectType/AcfGoogleMap.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfLink' => $baseDir . '/src/Types/ObjectType/AcfLink.php', ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 0d2a066..07d8626 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit22bfbd1509018e9010b8ca40b9b6700b +class ComposerAutoloaderInit25bca2cfacbb71bc0509958a96747af9 { private static $loader; @@ -24,15 +24,15 @@ public static function getLoader() require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit22bfbd1509018e9010b8ca40b9b6700b', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit25bca2cfacbb71bc0509958a96747af9', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); - spl_autoload_unregister(array('ComposerAutoloaderInit22bfbd1509018e9010b8ca40b9b6700b', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit25bca2cfacbb71bc0509958a96747af9', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit25bca2cfacbb71bc0509958a96747af9::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index e082bc9..b2774a3 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b +class ComposerStaticInit25bca2cfacbb71bc0509958a96747af9 { public static $prefixLengthsPsr4 = array ( 'W' => @@ -25,15 +25,32 @@ class ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b 'WPGraphQL\\ACF\\ACF' => __DIR__ . '/../..' . '/src/class-acf.php', 'WPGraphQL\\ACF\\ACF_Settings' => __DIR__ . '/../..' . '/src/class-acfsettings.php', 'WPGraphQL\\ACF\\Config' => __DIR__ . '/../..' . '/src/class-config.php', + 'WPGraphQL\\ACF\\Fields\\AcfField' => __DIR__ . '/../..' . '/src/Fields/AcfField.php', + 'WPGraphQL\\ACF\\Fields\\File' => __DIR__ . '/../..' . '/src/Fields/File.php', + 'WPGraphQL\\ACF\\Fields\\FlexibleContent' => __DIR__ . '/../..' . '/src/Fields/FlexibleContent.php', + 'WPGraphQL\\ACF\\Fields\\Gallery' => __DIR__ . '/../..' . '/src/Fields/Gallery.php', + 'WPGraphQL\\ACF\\Fields\\Group' => __DIR__ . '/../..' . '/src/Fields/Group.php', + 'WPGraphQL\\ACF\\Fields\\Image' => __DIR__ . '/../..' . '/src/Fields/Image.php', + 'WPGraphQL\\ACF\\Fields\\PageLink' => __DIR__ . '/../..' . '/src/Fields/PageLink.php', + 'WPGraphQL\\ACF\\Fields\\PostObject' => __DIR__ . '/../..' . '/src/Fields/PostObject.php', + 'WPGraphQL\\ACF\\Fields\\Relationship' => __DIR__ . '/../..' . '/src/Fields/Relationship.php', + 'WPGraphQL\\ACF\\Fields\\Repeater' => __DIR__ . '/../..' . '/src/Fields/Repeater.php', + 'WPGraphQL\\ACF\\Fields\\Select' => __DIR__ . '/../..' . '/src/Fields/Select.php', + 'WPGraphQL\\ACF\\Fields\\Taxonomy' => __DIR__ . '/../..' . '/src/Fields/Taxonomy.php', + 'WPGraphQL\\ACF\\Fields\\User' => __DIR__ . '/../..' . '/src/Fields/User.php', 'WPGraphQL\\ACF\\LocationRules' => __DIR__ . '/../..' . '/src/location-rules.php', + 'WPGraphQL\\ACF\\Registry' => __DIR__ . '/../..' . '/src/Registry.php', + 'WPGraphQL\\ACF\\Types\\InterfaceType\\AcfFieldGroupInterface' => __DIR__ . '/../..' . '/src/Types/InterfaceType/AcfFieldGroupInterface.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfGoogleMap' => __DIR__ . '/../..' . '/src/Types/ObjectType/AcfGoogleMap.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfLink' => __DIR__ . '/../..' . '/src/Types/ObjectType/AcfLink.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit22bfbd1509018e9010b8ca40b9b6700b::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit25bca2cfacbb71bc0509958a96747af9::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit25bca2cfacbb71bc0509958a96747af9::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit25bca2cfacbb71bc0509958a96747af9::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index b1327f4..94f4332 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => 'e35e895bf7d35b7bcc5f4eacc287e9f22de91a02', + 'reference' => 'd0ca31f6391c80cd370d4da71aa185658ee3c5b9', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -18,7 +18,7 @@ 'aliases' => array ( ), - 'reference' => 'e35e895bf7d35b7bcc5f4eacc287e9f22de91a02', + 'reference' => 'd0ca31f6391c80cd370d4da71aa185658ee3c5b9', ), ), ); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..f79e574 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 70000)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/wp-graphql-acf.php b/wp-graphql-acf.php index 76daa7b..4278ecd 100644 --- a/wp-graphql-acf.php +++ b/wp-graphql-acf.php @@ -113,4 +113,3 @@ function can_load_plugin() { return true; } - From 29fa7c49339350fa8db480090d4764af63f9de48 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Tue, 20 Apr 2021 12:37:23 -0600 Subject: [PATCH 02/12] no message --- src/class-config.php | 1500 ------------------------------------------ 1 file changed, 1500 deletions(-) delete mode 100644 src/class-config.php diff --git a/src/class-config.php b/src/class-config.php deleted file mode 100644 index 60a7e6b..0000000 --- a/src/class-config.php +++ /dev/null @@ -1,1500 +0,0 @@ - List of field names registered to the Schema - */ - protected $registered_field_names; - - /** - * @var array List of options page slugs registered to the Schema - */ - protected $registered_options_pages = []; - - /** - * Initialize WPGraphQL to ACF - * - * @param TypeRegistry $type_registry Instance of the WPGraphQL TypeRegistry - * - * @throws Exception - */ - public function init( TypeRegistry $type_registry ) { - - /** - * Set the TypeRegistry - */ - $this->type_registry = $type_registry; - $this->register_initial_types(); - - /** - * Gets the location rules for backward compatibility. - * - * This allows for ACF Field Groups that were registered before the "graphql_types" - * field was respected can still work with the old GraphQL Schema rules that mapped - * from the ACF Location rules. - */ - $this->location_rules = $this->get_location_rules(); - - /** - * Add ACF Fields to GraphQL Types - */ - $this->add_options_pages_to_schema(); - $this->add_acf_fields_to_graphql_types(); - - // This filter tells WPGraphQL to resolve revision meta for ACF fields from the revision's meta, instead - // of the parent (published post) meta. - add_filter( 'graphql_resolve_revision_meta_from_parent', function( $should, $object_id, $meta_key, $single ) { - - // Loop through all registered ACF fields that show in GraphQL. - if ( is_array( $this->registered_field_names ) && ! empty( $this->registered_field_names ) ) { - - $matches = null; - - // Iterate over all field names - foreach ( $this->registered_field_names as $field_name ) { - - // If the field name is an exact match with the $meta_key, the ACF field should - // resolve from the revision meta, so we can return false here, so that meta can - // resolve from the revision instead of the parent - if ( $field_name === $meta_key ) { - return false; - } - - // For flex fields/repeaters, the meta keys are structured a bit funky. - // This checks to see if the $meta_key starts with the same string as one of the - // acf fields (a flex/repeater field) and then checks if it's preceeded by an underscore and a number. - if ( $field_name === substr( $meta_key, 0, strlen( $field_name ) ) ) { - // match any string that starts with the field name, followed by an underscore, followed by a number, followed by another string - // ex my_flex_field_0_text_field or some_repeater_field_12_25MostPopularDogToys - $pattern = '/' . $field_name . '_\d+_\w+/m'; - preg_match( $pattern, $meta_key, $matches ); - } - - // If the meta key matches the pattern, treat it as a sub-field of an ACF Field Group - if ( null !== $matches ) { - return false; - } - - } - - } - - return $should; - }, 10, 4 ); - } - - /** - * Registers initial Types for use with ACF Fields - * - * @throws Exception - */ - public function register_initial_types() { - - $this->type_registry->register_interface_type( - 'AcfFieldGroup', - [ - 'description' => __( 'A Field Group registered by ACF', 'wp-graphql-acf' ), - 'fields' => [ - 'fieldGroupName' => [ - 'description' => __( 'The name of the ACF Field Group', 'wp-graphql-acf' ), - 'type' => 'String', - ], - ] - ] - ); - - $this->type_registry->register_object_type( - 'AcfLink', - [ - 'description' => __( 'ACF Link field', 'wp-graphql-acf' ), - 'fields' => [ - 'url' => [ - 'type' => 'String', - 'description' => __( 'The url of the link', 'wp-graphql-acf' ), - ], - 'title' => [ - 'type' => 'String', - 'description' => __( 'The title of the link', 'wp-graphql-acf' ), - ], - 'target' => [ - 'type' => 'String', - 'description' => __( 'The target of the link (_blank, etc)', 'wp-graphql-acf' ), - ], - ], - ] - ); - - } - - - - /** - * Gets the location rules - * @return array - */ - protected function get_location_rules() { - - $field_groups = acf_get_field_groups(); - if ( empty( $field_groups ) || ! is_array( $field_groups ) ) { - return []; - } - - $rules = []; - - // Each field group that doesn't have GraphQL Types explicitly set should get the location - // rules interpreted. - foreach ( $field_groups as $field_group ) { - if ( ! isset( $field_group['graphql_types'] ) || ! is_array( $field_group['graphql_types'] ) ) { - $rules[] = $field_group; - } - } - - if ( empty( $rules ) ) { - return []; - } - - // If there are field groups with no graphql_types field set, inherit the rules from - // ACF Location Rules - $rules = new LocationRules(); - $rules->determine_location_rules(); - return $rules->get_rules(); - } - - protected function add_options_pages_to_schema() { - - global $acf_options_page; - - if ( ! isset( $acf_options_page ) ) { - return ; - } - - /** - * Get a list of post types that have been registered to show in graphql - */ - $graphql_options_pages = acf_get_options_pages(); - - /** - * If there are no post types exposed to GraphQL, bail - */ - if ( empty( $graphql_options_pages ) || ! is_array( $graphql_options_pages ) ) { - return; - } - - $options_pages_to_register = []; - - /** - * Loop over the post types exposed to GraphQL - */ - foreach ( $graphql_options_pages as $options_page_key => $options_page ) { - if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { - continue; - } - - /** - * Get options page properties. - */ - $page_title = $options_page['page_title']; - $page_slug = $options_page['menu_slug']; - $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - - $options_pages_to_register[ $type_name ] = [ - 'title' => $page_title, - 'slug' => $page_slug, - 'type_name' => $type_name, - 'options_page' => $options_page, - ]; - - } - - if ( is_array( $options_pages_to_register ) && ! empty( $options_pages_to_register ) ) { - - foreach ( $options_pages_to_register as $page_to_register ) { - - $page_title = $page_to_register['title']; - $page_slug = $page_to_register['slug']; - $type_name = isset( $page_to_register['type_name'] ) ? Utils::format_type_name( $page_to_register['type_name'] ) : Utils::format_type_name( $page_to_register['slug'] ); - $options_page = $page_to_register['options_page']; - - $this->type_registry->register_object_type( $type_name, [ - 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ), - 'fields' => [ - 'pageTitle' => [ - 'type' => 'String', - 'resolve' => function( $source ) use ( $page_title ) { - return ! empty( $page_title ) ? $page_title : null; - }, - ], - 'pageSlug' => [ - 'type' => 'String', - 'resolve' => function( $source ) use ( $page_slug ) { - return ! empty( $page_slug ) ? $page_slug : null; - }, - ], - ], - ] ); - - $field_name = Utils::format_field_name( $type_name ); - - $this->type_registry->register_field( - 'RootQuery', - $field_name, - [ - 'type' => $type_name, - 'description' => sprintf( __( '%s options.', 'wp-graphql-acf' ), $page_title ), - 'resolve' => function() use ( $options_page ) { - return ! empty( $options_page ) ? $options_page : null; - } - ] - ); - - } - } - - } - - /** - * Determines whether a field group should be exposed to the GraphQL Schema. By default, field - * groups will not be exposed to GraphQL. - * - * @param array $field_group Undocumented. - * - * @return bool - */ - protected function should_field_group_show_in_graphql( $field_group ) { - - /** - * By default, field groups will not be exposed to GraphQL. - */ - $show = false; - - /** - * If - */ - if ( isset( $field_group['show_in_graphql'] ) && true === (bool) $field_group['show_in_graphql'] ) { - $show = true; - } - - /** - * Determine conditions where the GraphQL Schema should NOT be shown in GraphQL for - * root groups, not nested groups with parent. - */ - if ( ! isset( $field_group['parent'] ) ) { - if ( - ( isset( $field_group['active'] ) && true != $field_group['active'] ) || - ( empty( $field_group['location'] ) || ! is_array( $field_group['location'] ) ) - ) { - $show = false; - } - } - - /** - * Whether a field group should show in GraphQL. - * - * @var boolean $show Whether the field group should show in the GraphQL Schema - * @var array $field_group The ACF Field Group - * @var Config $this The Config for the ACF Plugin - */ - return apply_filters( 'wpgraphql_acf_should_field_group_show_in_graphql', $show, $field_group, $this ); - - } - - /** - * Undocumented function - * - * @todo: This may be a good utility to add to WPGraphQL Core? May even have something already? - * - * @param string $str Unknown. - * @param array $no_strip Unknown. - * - * @return mixed|null|string|string[] - */ - public static function camel_case( $str, array $no_strip = [] ) { - // non-alpha and non-numeric characters become spaces. - $str = preg_replace( '/[^a-z0-9' . implode( '', $no_strip ) . ']+/i', ' ', $str ); - $str = trim( $str ); - // Lowercase the string - $str = strtolower( $str ); - // uppercase the first character of each word. - $str = ucwords( $str ); - // Replace spaces - $str = str_replace( ' ', '', $str ); - // Lowecase first letter - $str = lcfirst( $str ); - - return $str; - } - - /** - * Undocumented function - * - * @param [type] $root Undocumented. - * @param [type] $acf_field Undocumented. - * @param boolean $format Whether ACF should apply formatting to the field. Default false. - * - * @return mixed - */ - protected function get_acf_field_value( $root, $acf_field, $format = false ) { - - $value = null; - $id = null; - - if ( is_array( $root ) && isset( $root['node'] ) ) { - $id = $root['node']->ID; - } - - if ( is_array( $root ) && ! ( ! empty( $root['type'] ) && 'options_page' === $root['type'] ) ) { - - if ( isset( $root[ $acf_field['key'] ] ) ) { - $value = $root[ $acf_field['key'] ]; - - if ( 'wysiwyg' === $acf_field['type'] ) { - $value = apply_filters( 'the_content', $value ); - } - - } - } else { - - switch ( true ) { - - case $root instanceof Term: - $id = 'term_' . $root->term_id; - break; - case $root instanceof Post: - $id = absint( $root->databaseId ); - break; - case $root instanceof MenuItem: - $id = absint( $root->menuItemId ); - break; - case $root instanceof Menu: - $id = 'term_' . $root->menuId; - break; - case $root instanceof User: - $id = 'user_' . absint( $root->userId ); - break; - case $root instanceof Comment: - $id = 'comment_' . absint( $root->databaseId ); - break; - case is_array( $root ) && ! empty( $root['type'] ) && 'options_page' === $root['type']: - $id = $root['post_id']; - break; - default: - $id = null; - break; - } - } - - if ( empty( $value ) ) { - - /** - * Filters the root ID, allowing additional Models the ability to provide a way to resolve their ID - * - * @param int $id The ID of the object. Default null - * @param mixed $root The Root object being resolved. The ID is typically a property of this object. - */ - $id = apply_filters( 'graphql_acf_get_root_id', $id, $root ); - - if ( empty( $id ) ) { - return null; - } - - $format = false; - - if ( 'wysiwyg' === $acf_field['type'] ) { - $format = true; - } - - if ( 'select' === $acf_field['type'] ) { - $format = true; - } - - /** - * Check if cloned field and retrieve the key accordingly. - */ - if ( ! empty( $acf_field['_clone'] ) ) { - $key = $acf_field['__key']; - } else { - $key = $acf_field['key']; - } - - $field_value = get_field( $key, $id, $format ); - - $value = ! empty( $field_value ) ? $field_value : null; - - } - - /** - * Filters the returned ACF field value - * - * @param mixed $value The resolved ACF field value - * @param array $acf_field The ACF field config - * @param mixed $root The Root object being resolved. The ID is typically a property of this object. - * @param int $id The ID of the object - */ - return apply_filters( 'graphql_acf_field_value', $value, $acf_field, $root, $id ); - - } - - /** - * Get a list of supported fields that WPGraphQL for ACF supports. - * - * This is helpful for determining whether UI should be output for the field, and whether - * the field should be added to the Schema. - * - * Some fields, such as "Accordion" are not supported currently. - * - * @return array - */ - public static function get_supported_fields() { - $supported_fields = [ - 'text', - 'textarea', - 'number', - 'range', - 'email', - 'url', - 'password', - 'image', - 'file', - 'wysiwyg', - 'oembed', - 'gallery', - 'select', - 'checkbox', - 'radio', - 'button_group', - 'true_false', - 'link', - 'post_object', - 'page_link', - 'relationship', - 'taxonomy', - 'user', - 'google_map', - 'date_picker', - 'date_time_picker', - 'time_picker', - 'color_picker', - 'group', - 'repeater', - 'flexible_content' - ]; - - /** - * filter the supported fields - * - * @param array $supported_fields - */ - return apply_filters( 'wpgraphql_acf_supported_fields', $supported_fields ); - } - - /** - * Undocumented function - * - * @param string $type_name The name of the GraphQL Type to add the field to. - * @param string $field_name The name of the field to add to the GraphQL Type. - * @param array $config The GraphQL configuration of the field. - * - * @return mixed - */ - protected function register_graphql_field( string $type_name, string $field_name, array $config ) { - $acf_field = isset( $config['acf_field'] ) ? $config['acf_field'] : null; - $acf_type = isset( $acf_field['type'] ) ? $acf_field['type'] : null; - - if ( empty( $acf_type ) ) { - return false; - } - - /** - * filter the field config for custom field types - * - * @param array $field_config - */ - $field_config = apply_filters( 'wpgraphql_acf_register_graphql_field', [ - 'type' => null, - 'resolve' => isset( $config['resolve'] ) && is_callable( $config['resolve'] ) ? $config['resolve'] : function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - return ! empty( $value ) ? $value : null; - }, - ], $type_name, $field_name, $config ); - - switch ( $acf_type ) { - case 'button_group': - case 'color_picker': - case 'email': - case 'text': - case 'message': - case 'oembed': - case 'password': - case 'wysiwyg': - case 'url': - // Even though Selects and Radios in ACF can _technically_ be an integer - // we're choosing to always cast as a string because with - // GraphQL we can't return different types - $field_config['type'] = 'String'; - break; - case 'textarea': - $field_config['type'] = 'String'; - $field_config['resolve'] = function( $root ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - if ( ! empty( $acf_field['new_lines'] ) ) { - if ( 'wpautop' === $acf_field['new_lines'] ) { - $value = wpautop( $value ); - } - if ( 'br' === $acf_field['new_lines'] ) { - $value = nl2br( $value ); - } - } - return $value; - - - }; - break; - case 'select': - - /** - * If the select field is configured to not allow multiple values - * the field will return a string, but if it is configured to allow - * multiple values it will return a list of strings, and an empty array - * if no values are set. - * - * @see: https://github.com/wp-graphql/wp-graphql-acf/issues/25 - */ - if ( empty( $acf_field['multiple'] ) ) { - if('array' === $acf_field['return_format'] ){ - $field_config['type'] = [ 'list_of' => 'String' ]; - $field_config['resolve'] = function( $root ) use ( $acf_field) { - $value = $this->get_acf_field_value( $root, $acf_field, true); - - return ! empty( $value ) && is_array( $value ) ? $value : []; - }; - }else{ - $field_config['type'] = 'String'; - } - } else { - $field_config['type'] = [ 'list_of' => 'String' ]; - $field_config['resolve'] = function( $root ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - return ! empty( $value ) && is_array( $value ) ? $value : []; - }; - } - break; - case 'radio': - $field_config['type'] = 'String'; - break; - case 'number': - case 'range': - $field_config['type'] = 'Float'; - break; - case 'true_false': - $field_config['type'] = 'Boolean'; - break; - case 'date_picker': - case 'time_picker': - case 'date_time_picker': - $field_config = [ - 'type' => 'String', - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - - $value = $this->get_acf_field_value( $root, $acf_field, true ); - - if ( ! empty( $value ) && ! empty( $acf_field['return_format'] ) ) { - $value = date( $acf_field['return_format'], strtotime( $value ) ); - } - return ! empty( $value ) ? $value : null; - }, - ]; - break; - case 'relationship': - - if ( isset( $acf_field['post_type'] ) && is_array( $acf_field['post_type'] ) ) { - - $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); - - if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) { - $type = $field_type_name; - } else { - $type_names = []; - foreach ( $acf_field['post_type'] as $post_type ) { - if ( in_array( $post_type, get_post_types([ 'show_in_graphql' => true ]), true ) ) { - $type_names[ $post_type ] = get_post_type_object( $post_type )->graphql_single_name; - } - } - - if ( empty( $type_names ) ) { - $type = 'PostObjectUnion'; - } else { - register_graphql_union_type( $field_type_name, [ - 'typeNames' => $type_names, - 'resolveType' => function( $value ) use ( $type_names ) { - $post_type_object = get_post_type_object( $value->post_type ); - return ! empty( $post_type_object->graphql_single_name ) ? $this->type_registry->get_type( $post_type_object->graphql_single_name ) : null; - } - ] ); - - $type = $field_type_name; - } - - - } - } else { - $type = 'PostObjectUnion'; - } - - $field_config = [ - 'type' => [ 'list_of' => $type ], - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $relationship = []; - $value = $this->get_acf_field_value( $root, $acf_field ); - - if ( ! empty( $value ) && is_array( $value ) ) { - foreach ( $value as $post_id ) { - $post_object = get_post( $post_id ); - if ( $post_object instanceof \WP_Post ) { - $post_model = new Post( $post_object ); - $relationship[] = $post_model; - } - } - } - - return isset( $value ) ? $relationship : null; - - }, - ]; - break; - case 'page_link': - case 'post_object': - - if ( isset( $acf_field['post_type'] ) && is_array( $acf_field['post_type'] ) ) { - $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); - if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) { - $type = $field_type_name; - } else { - $type_names = []; - foreach ( $acf_field['post_type'] as $post_type ) { - if ( in_array( $post_type, \get_post_types( [ 'show_in_graphql' => true ] ), true ) ) { - $type_names[ $post_type ] = get_post_type_object( $post_type )->graphql_single_name; - } - } - - if ( empty( $type_names ) ) { - $field_config['type'] = null; - break; - } - - register_graphql_union_type( $field_type_name, [ - 'typeNames' => $type_names, - 'resolveType' => function( $value ) use ( $type_names ) { - $post_type_object = get_post_type_object( $value->post_type ); - return ! empty( $post_type_object->graphql_single_name ) ? $this->type_registry->get_type( $post_type_object->graphql_single_name ) : null; - } - ] ); - - $type = $field_type_name; - } - } else { - $type = 'PostObjectUnion'; - } - - // If the field is allowed to be a multi select - if ( 0 !== $acf_field['multiple'] ) { - $type = [ 'list_of' => $type ]; - } - - $field_config = [ - 'type' => $type, - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - $return = []; - if ( ! empty( $value ) ) { - if ( is_array( $value ) ) { - foreach ( $value as $id ) { - $post = get_post( $id ); - if ( ! empty( $post ) ) { - $return[] = new Post( $post ); - } - } - } else { - $post = get_post( absint( $value ) ); - if ( ! empty( $post ) ) { - $return[] = new Post( $post ); - } - } - } - - // If the field is allowed to be a multi select - if ( 0 !== $acf_field['multiple'] ) { - $return = ! empty( $return ) ? $return : null; - } else { - $return = ! empty( $return[0] ) ? $return[0] : null; - } - - /** - * This hooks allows for filtering of the post object source. In case an non-core defined - * post-type is being targeted. - * - * @param mixed|null $source GraphQL Type source. - * @param mixed|null $value Root ACF field value. - * @param AppContext $context AppContext instance. - * @param ResolveInfo $info ResolveInfo instance. - */ - return apply_filters( - 'graphql_acf_post_object_source', - $return, - $value, - $context, - $info - ); - - }, - ]; - break; - case 'link': - $field_config['type'] = 'AcfLink'; - break; - case 'image': - case 'file': - $field_config = [ - 'type' => 'MediaItem', - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - return DataSource::resolve_post_object( (int) $value, $context ); - }, - ]; - break; - case 'checkbox': - $field_config = [ - 'type' => [ 'list_of' => 'String' ], - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - return is_array( $value ) ? $value : null; - }, - ]; - break; - case 'gallery': - $field_config = [ - 'type' => [ 'list_of' => 'MediaItem' ], - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - $gallery = []; - if ( ! empty( $value ) && is_array( $value ) ) { - foreach ( $value as $image ) { - $post_object = get_post( (int) $image ); - if ( $post_object instanceof \WP_Post ) { - $post_model = new Post( $post_object ); - $gallery[] = $post_model; - } - } - } - - return isset( $value ) ? $gallery : null; - }, - ]; - break; - case 'user': - - $type = 'User'; - - if ( isset( $acf_field['multiple'] ) && 1 === $acf_field['multiple'] ) { - $type = [ 'list_of' => $type ]; - } - - $field_config = [ - 'type' => $type, - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - $return = []; - if ( ! empty( $value ) ) { - if ( is_array( $value ) ) { - foreach ( $value as $id ) { - $user = get_user_by( 'id', $id ); - if ( ! empty( $user ) ) { - $user = new User( $user ); - if ( 'private' !== $user->get_visibility() ) { - $return[] = $user; - } - } - } - } else { - $user = get_user_by( 'id', absint( $value ) ); - if ( ! empty( $user ) ) { - $user = new User( $user ); - if ( 'private' !== $user->get_visibility() ) { - $return[] = $user; - } - } - } - } - - // If the field is allowed to be a multi select - if ( 0 !== $acf_field['multiple'] ) { - $return = ! empty( $return ) ? $return : null; - } else { - $return = ! empty( $return[0] ) ? $return[0] : null; - } - - return $return; - }, - ]; - break; - case 'taxonomy': - - $type = 'TermObjectUnion'; - - if ( isset( $acf_field['taxonomy'] ) ) { - $tax_object = get_taxonomy( $acf_field['taxonomy'] ); - if ( isset( $tax_object->graphql_single_name ) ) { - $type = $tax_object->graphql_single_name; - } - } - - $is_multiple = isset( $acf_field['field_type'] ) && in_array( $acf_field['field_type'], array( 'checkbox', 'multi_select' ) ); - - $field_config = [ - 'type' => $is_multiple ? ['list_of' => $type ] : $type, - 'resolve' => function( $root, $args, $context, $info ) use ( $acf_field, $is_multiple ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - /** - * If this is multiple, the value will most likely always be an array. - * If it isn't, we want to return a single term id. - */ - if ( ! empty( $value ) && is_array( $value ) ) { - foreach ( $value as $term ) { - $terms[] = DataSource::resolve_term_object( (int) $term, $context ); - } - return $terms; - } else { - return DataSource::resolve_term_object( (int) $value, $context ); - } - }, - ]; - break; - - // Accordions are not represented in the GraphQL Schema. - case 'accordion': - $field_config = null; - break; - case 'group': - - $field_type_name = ucfirst( Utils::format_type_name( $acf_field['name'] ) ); - - if ( isset( $acf_field['parent'] ) && ! empty( $acf_field['parent'] ) ) { - $field_type_name = $type_name . '_' . Utils::format_type_name( $acf_field['name'] ); - } - - if ( null !== $this->type_registry->get_type( strtolower( $field_type_name ) ) ) { - $field_config['type'] = $field_type_name; - break; - } - - $this->type_registry->register_object_type( - $field_type_name, - [ - 'description' => __( 'Field Group', 'wp-graphql-acf' ), - 'interfaces' => [ 'AcfFieldGroup' ], - 'fields' => [ - 'fieldGroupName' => [ - 'resolve' => function( $source ) use ( $acf_field ) { - return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null; - }, - ], - ], - ] - ); - - $this->add_field_group_fields( $acf_field, $field_type_name ); - - $field_config['type'] = $field_type_name; - - break; - - case 'google_map': - $field_type_name = 'ACF_GoogleMap'; - if ( $this->type_registry->get_type( $field_type_name ) == $field_type_name ) { - $field_config['type'] = $field_type_name; - break; - } - - $fields = [ - 'streetAddress' => [ - 'type' => 'String', - 'description' => __( 'The street address associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['address'] ) ? $root['address'] : null; - }, - ], - 'latitude' => [ - 'type' => 'Float', - 'description' => __( 'The latitude associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['lat'] ) ? $root['lat'] : null; - }, - ], - 'longitude' => [ - 'type' => 'Float', - 'description' => __( 'The longitude associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['lng'] ) ? $root['lng'] : null; - }, - ], - ]; - - // ACF 5.8.6 added more data to Google Maps field value - // https://www.advancedcustomfields.com/changelog/ - if ( \acf_version_compare(acf_get_db_version(), '>=', '5.8.6' ) ) { - $fields += [ - 'streetName' => [ - 'type' => 'String', - 'description' => __( 'The street name associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['street_name'] ) ? $root['street_name'] : null; - }, - ], - 'streetNumber' => [ - 'type' => 'String', - 'description' => __( 'The street number associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['street_number'] ) ? $root['street_number'] : null; - }, - ], - 'city' => [ - 'type' => 'String', - 'description' => __( 'The city associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['city'] ) ? $root['city'] : null; - }, - ], - 'state' => [ - 'type' => 'String', - 'description' => __( 'The state associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['state'] ) ? $root['state'] : null; - }, - ], - 'stateShort' => [ - 'type' => 'String', - 'description' => __( 'The state abbreviation associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['state_short'] ) ? $root['state_short'] : null; - }, - ], - 'postCode' => [ - 'type' => 'String', - 'description' => __( 'The post code associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['post_code'] ) ? $root['post_code'] : null; - }, - ], - 'country' => [ - 'type' => 'String', - 'description' => __( 'The country associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['country'] ) ? $root['country'] : null; - }, - ], - 'countryShort' => [ - 'type' => 'String', - 'description' => __( 'The country abbreviation associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['country_short'] ) ? $root['country_short'] : null; - }, - ], - 'placeId' => [ - 'type' => 'String', - 'description' => __( 'The country associated with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['place_id'] ) ? $root['place_id'] : null; - }, - ], - 'zoom' => [ - 'type' => 'String', - 'description' => __( 'The zoom defined with the map', 'wp-graphql-acf' ), - 'resolve' => function( $root ) { - return isset( $root['zoom'] ) ? $root['zoom'] : null; - }, - ], - ]; - } - - $this->type_registry->register_object_type( - $field_type_name, - [ - 'description' => __( 'Google Map field', 'wp-graphql-acf' ), - 'fields' => $fields, - ] - ); - $field_config['type'] = $field_type_name; - break; - case 'repeater': - $field_type_name = $type_name . '_' . self::camel_case( $acf_field['name'] ); - - if ( $this->type_registry->get_type( $field_type_name ) ) { - $field_config['type'] = $field_type_name; - break; - } - - $this->type_registry->register_object_type( - $field_type_name, - [ - 'description' => __( 'Field Group', 'wp-graphql-acf' ), - 'interfaces' => [ 'AcfFieldGroup' ], - 'fields' => [ - 'fieldGroupName' => [ - 'resolve' => function( $source ) use ( $acf_field ) { - return ! empty( $acf_field['name'] ) ? $acf_field['name'] : null; - }, - ], - ], - 'resolve' => function( $source ) use ( $acf_field ) { - $repeater = $this->get_acf_field_value( $source, $acf_field ); - - return ! empty( $repeater ) ? $repeater : []; - }, - ] - ); - - $this->add_field_group_fields( $acf_field, $field_type_name ); - - $field_config['type'] = [ 'list_of' => $field_type_name ]; - break; - - /** - * Flexible content fields should return a Union of the Layouts that can be configured. - * - * - * Example Query of a flex field with the name "flex_field" and 2 groups - * - * { - * post { - * flexField { - * ...on GroupOne { - * textField - * textAreaField - * } - * ...on GroupTwo { - * imageField { - * id - * title - * } - * } - * } - * } - * } - * - */ - case 'flexible_content': - - $field_config = null; - $field_type_name = $type_name . '_' . ucfirst( self::camel_case( $acf_field['name'] ) ); - if ( $this->type_registry->get_type( $field_type_name ) ) { - $field_config['type'] = $field_type_name; - break; - } - - if ( ! empty( $acf_field['layouts'] ) && is_array( $acf_field['layouts'] ) ) { - - $union_types = []; - foreach ( $acf_field['layouts'] as $layout ) { - - $flex_field_layout_name = ! empty( $layout['name'] ) ? ucfirst( self::camel_case( $layout['name'] ) ) : null; - $flex_field_layout_name = ! empty( $flex_field_layout_name ) ? $field_type_name . '_' . $flex_field_layout_name : null; - - /** - * If there are no layouts defined for the Flex Field - */ - if ( empty( $flex_field_layout_name ) ) { - continue; - } - - $layout_type = $this->type_registry->get_type( $flex_field_layout_name ); - - if ( $layout_type ) { - $union_types[ $layout['name'] ] = $layout_type; - } else { - - - $this->type_registry->register_object_type( $flex_field_layout_name, [ - 'description' => __( 'Group within the flex field', 'wp-graphql-acf' ), - 'interfaces' => [ 'AcfFieldGroup' ], - 'fields' => [ - 'fieldGroupName' => [ - 'resolve' => function( $source ) use ( $flex_field_layout_name ) { - return ! empty( $flex_field_layout_name ) ? $flex_field_layout_name : null; - }, - ], - ], - ] ); - - $union_types[ $layout['name'] ] = $flex_field_layout_name; - - - $layout['parent'] = $acf_field; - $layout['show_in_graphql'] = isset( $acf_field['show_in_graphql'] ) ? (bool) $acf_field['show_in_graphql'] : true; - $this->add_field_group_fields( $layout, $flex_field_layout_name, true ); - } - } - - $this->type_registry->register_union_type( $field_type_name, [ - 'typeNames' => $union_types, - 'resolveType' => function( $value ) use ( $union_types ) { - return isset( $union_types[ $value['acf_fc_layout'] ] ) ? $this->type_registry->get_type( $union_types[ $value['acf_fc_layout'] ] ) : null; - } - ] ); - - $field_config['type'] = [ 'list_of' => $field_type_name ]; - $field_config['resolve'] = function( $root, $args, $context, $info ) use ( $acf_field ) { - $value = $this->get_acf_field_value( $root, $acf_field ); - - return ! empty( $value ) ? $value : []; - }; - } - break; - default: - break; - } - - if ( empty( $field_config ) || empty( $field_config['type'] ) ) { - return null; - } - - $config = array_merge( $config, $field_config ); - $this->registered_field_names[] = $acf_field['name']; - return $this->type_registry->register_field( $type_name, $field_name, $config ); - } - - /** - * Given a field group array, this adds the fields to the specified Type in the Schema - * - * @param array $field_group The group to add to the Schema. - * @param string $type_name The Type name in the GraphQL Schema to add fields to. - * @param bool $layout Whether or not these fields are part of a Flex Content layout. - */ - protected function add_field_group_fields( array $field_group, string $type_name, $layout = false ) { - - /** - * If the field group has the show_in_graphql setting configured, respect it's setting - * otherwise default to true (for nested fields) - */ - $field_group['show_in_graphql'] = isset( $field_group['show_in_graphql'] ) ? (boolean) $field_group['show_in_graphql'] : true; - - /** - * Determine if the field group should be exposed - * to graphql - */ - if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { - return; - } - - /** - * Get the fields in the group. - */ - $acf_fields = ! empty( $field_group['sub_fields'] ) || $layout ? $field_group['sub_fields'] : acf_get_fields( $field_group ); - - /** - * If there are no fields, bail - */ - if ( empty( $acf_fields ) || ! is_array( $acf_fields ) ) { - return; - } - - /** - * Stores field keys to prevent duplicate field registration for cloned fields - */ - $processed_keys = []; - - /** - * Loop over the fields and register them to the Schema - */ - foreach ( $acf_fields as $acf_field ) { - if ( in_array( $acf_field['key'], $processed_keys, true ) ) { - continue; - } else { - $processed_keys[] = $acf_field['key']; - } - - /** - * Setup data for register_graphql_field - */ - $explicit_name = ! empty( $acf_field['graphql_field_name'] ) ? $acf_field['graphql_field_name'] : null; - $name = empty( $explicit_name ) && ! empty( $acf_field['name'] ) ? self::camel_case( $acf_field['name'] ) : $explicit_name; - $show_in_graphql = isset( $acf_field['show_in_graphql'] ) ? (bool) $acf_field['show_in_graphql'] : true; - $description = isset( $acf_field['instructions'] ) ? $acf_field['instructions'] : __( 'ACF Field added to the Schema by WPGraphQL ACF' ); - - /** - * If the field is missing a name or a type, - * we can't add it to the Schema. - */ - if ( - empty( $name ) || - true != $show_in_graphql - ) { - - /** - * Uncomment line below to determine what fields are not going to be output - * in the Schema. - */ - continue; - } - - $config = [ - 'name' => $name, - 'description' => $description, - 'acf_field' => $acf_field, - 'acf_field_group' => $field_group, - ]; - - $this->register_graphql_field( $type_name, $name, $config ); - - } - - } - - /** - * Returns all available GraphQL Types - * - * @return array - */ - public static function get_all_graphql_types() { - $graphql_types = array(); - - // Use GraphQL to get the Interface and the Types that implement them - $query = ' - query GetPossibleTypes($name:String!){ - __type(name:$name){ - name - description - possibleTypes { - name - description - } - } - } - '; - - $interfaces = [ - 'ContentNode' => [ - 'label' => __( 'Post Type', 'wp-graphql-acf' ), - 'plural_label' => __( 'All Post Types', 'wp-graphql-acf' ), - ], - 'TermNode' => [ - 'label' => __( 'Taxonomy', 'wp-graphql-acf' ), - 'plural_label' => __( 'All Taxonomies', 'wp-graphql-acf' ), - ], - 'ContentTemplate' => [ - 'label' => __( 'Page Template', 'wp-graphql-acf' ), - 'plural_label' => __( 'All Templates Assignable to Content', 'wp-graphql-acf' ), - ] - ]; - - foreach ( $interfaces as $interface_name => $config ) { - - $interface_query = graphql([ - 'query' => $query, - 'variables' => [ - 'name' => $interface_name - ] - ]); - - $possible_types = $interface_query['data']['__type']['possibleTypes']; - asort( $possible_types ); - - if ( ! empty( $possible_types ) && is_array( $possible_types ) ) { - - // Intentionally not translating "ContentNode Interface" as this is part of the GraphQL Schema and should not be translated. - $graphql_types[ $interface_name ] = '' . $interface_name . ' Interface (' . $config['plural_label'] . ')'; - $label = ' (' . $config['label'] . ')'; - foreach ( $possible_types as $type ) { - $type_label = $type['name'] . $label; - $type_key = $type['name']; - - $graphql_types[ $type_key ] = $type_label; - } - } - - } - - /** - * Add comment to GraphQL types - */ - $graphql_types['Comment'] = __( 'Comment', 'wp-graphql-acf' ); - - /** - * Add menu to GraphQL types - */ - $graphql_types['Menu'] = __( 'Menu', 'wp-graphql-acf' ); - - /** - * Add menu items to GraphQL types - */ - $graphql_types['MenuItem'] = __( 'Menu Item', 'wp-graphql-acf' ); - - /** - * Add users to GraphQL types - */ - $graphql_types['User'] = __( 'User', 'wp-graphql-acf' ); - - - /** - * Add options pages to GraphQL types - */ - global $acf_options_page; - - if ( isset( $acf_options_page ) ) { - /** - * Get a list of post types that have been registered to show in graphql - */ - $graphql_options_pages = acf_get_options_pages(); - - /** - * If there are no post types exposed to GraphQL, bail - */ - if ( ! empty( $graphql_options_pages ) && is_array( $graphql_options_pages ) ) { - - /** - * Prepare type key prefix and label surfix - */ - $label = ' (' . __( 'ACF Options Page', 'wp-graphql-acf' ) . ')'; - - /** - * Loop over the post types exposed to GraphQL - */ - foreach ( $graphql_options_pages as $options_page_key => $options_page ) { - if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { - continue; - } - - /** - * Get options page properties. - */ - $page_title = $options_page['page_title']; - $type_label = $page_title . $label; - $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - - $graphql_types[ $type_name ] = $type_label; - } - } - } - - return $graphql_types; - } - - /** - * Adds acf field groups to GraphQL types. - */ - protected function add_acf_fields_to_graphql_types() { - /** - * Get all the field groups - */ - $field_groups = acf_get_field_groups(); - - /** - * If there are no acf field groups, bail - */ - if ( empty( $field_groups ) || ! is_array( $field_groups ) ) { - return; - } - - /** - * Loop over all the field groups - */ - foreach ( $field_groups as $field_group ) { - - $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; - $field_group_name = Utils::format_field_name( $field_group_name ); - - $manually_set_graphql_types = isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false; - - if ( false === $manually_set_graphql_types ) { - if ( ! isset( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) { - $field_group['graphql_types'] = []; - $location_rules = $this->get_location_rules(); - if ( isset( $location_rules[ $field_group_name ] ) ) { - $field_group['graphql_types'] = $location_rules[ $field_group_name ]; - } - } - } - - if ( ! is_array( $field_group['graphql_types'] ) || empty( $field_group['graphql_types'] ) ) { - continue; - } - - /** - * Determine if the field group should be exposed - * to graphql - */ - if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { - return; - } - - $graphql_types = array_unique( $field_group['graphql_types'] ); - $graphql_types = array_filter( $graphql_types ); - - /** - * Prepare default info - */ - $field_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : Config::camel_case( $field_group['title'] ); - $field_group['type'] = 'group'; - $field_group['name'] = $field_name; - $config = [ - 'name' => $field_name, - 'acf_field' => $field_group, - 'acf_field_group' => null, - 'resolve' => function ( $root ) use ( $field_group ) { - return isset( $root ) ? $root : null; - } - ]; - - $qualifier = sprintf( __( 'Added to the GraphQL Schema because the ACF Field Group "%1$s" was set to Show in GraphQL.', 'wp-graphql-acf' ), $field_group['title'] ); - $config['description'] = $field_group['description'] ? $field_group['description'] . ' | ' . $qualifier : $qualifier; - - /** - * Loop over the GraphQL types for this field group on - */ - foreach ( $graphql_types as $graphql_type ) { - $this->register_graphql_field( $graphql_type, $field_name, $config ); - } - } - - } - -} From 2cda3df3dcb5ddfe39adff36545f57595ed3d512 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Wed, 21 Apr 2021 16:14:06 -0600 Subject: [PATCH 03/12] - Adjust the JS on the GraphQL Settings to only execute the AJAX request if one is not already pending. --- src/js/main.js | 144 +++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 59 deletions(-) diff --git a/src/js/main.js b/src/js/main.js index 326bbd3..dc2cad8 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -2,6 +2,59 @@ $j = jQuery.noConflict(); $j(document).ready(function () { + var GraphqlLocationManager = new acf.Model({ + id: 'graphqlLocationManager', + wait: 'ready', + events: { + 'click .add-location-rule': 'onClickAddRule', + 'click .add-location-group': 'onClickAddGroup', + 'click .remove-location-rule': 'onClickRemoveRule', + 'change .refresh-location-rule': 'onChangeRemoveRule', + 'change .rule-groups .operator select': 'onChangeRemoveRule', + 'change .rule-groups .value select': 'onChangeRemoveRule', + }, + requestPending: false, + initialize: function () { + this.$el = $j('#acf-field-group-locations'); + this.getGraphqlTypes(); + }, + + onClickAddRule: function (e, $el) { + this.getGraphqlTypes(); + }, + + onClickRemoveRule: function (e, $el) { + this.getGraphqlTypes(); + }, + + onChangeRemoveRule: function (e, $el) { + setTimeout(function () { + GraphqlLocationManager.getGraphqlTypes(); + }, 500); + + }, + + onClickAddGroup: function (e, $el) { + this.getGraphqlTypes(); + }, + + isRequestPending: function() { + return this.requestPending; + }, + + startRequest: function () { + this.requestPending = true; + }, + + finishRequest: function() { + this.requestPending = false; + }, + + getGraphqlTypes: function () { + getGraphqlTypesFromLocationRules(); + }, + }); + /** * Set the visibility of the GraphQL Fields based on the `show_in_graphql` * field state. @@ -183,8 +236,9 @@ $j(document).ready(function () { function getGraphqlTypesFromLocationRules() { - var form = $j('#post :input'); - var serialized = form.serialize(); + var form = $j('#post'); + var formInputs = $j('#post :input'); + var serialized = formInputs.serialize(); var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]'); var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules'); @@ -194,25 +248,37 @@ $j(document).ready(function () { return; } - $j.post(ajaxurl, { - action: 'get_acf_field_group_graphql_types', - data: serialized - }, function (res) { - var types = res && res['graphql_types'] ? res['graphql_types'] : []; - - checkboxes.each(function (i, el) { - var checkbox = $j(this); - var value = $j(this).val(); - checkbox.prop('checked', false); - if (types && types.length) { - if (-1 !== $j.inArray(value, types)) { - checkbox.prop('checked', true); - checkbox.trigger("change"); + console.log( form.attr('data-request-pending') ); + + if ( 'pending' !== form.attr('data-request-pending') ) { + + // Start the request + form.attr('data-request-pending', 'pending' ); + + // Make the request + $j.post(ajaxurl, { + action: 'get_acf_field_group_graphql_types', + data: serialized + }, function (res) { + var types = res && res['graphql_types'] ? res['graphql_types'] : []; + + checkboxes.each(function (i, el) { + var checkbox = $j(this); + var value = $j(this).val(); + checkbox.prop('checked', false); + if (types && types.length) { + if (-1 !== $j.inArray(value, types)) { + checkbox.prop('checked', true); + checkbox.trigger("change"); + } } - } - }) + }) - }); + // Signal that the request is finished + form.removeAttr('data-request-pending'); + + }); + } }; @@ -223,44 +289,4 @@ $j(document).ready(function () { setGraphqlFieldName(); graphqlMapTypesFromLocations(); - var GraphqlLocationManager = new acf.Model({ - id: 'graphqlLocationManager', - wait: 'ready', - events: { - 'click .add-location-rule': 'onClickAddRule', - 'click .add-location-group': 'onClickAddGroup', - 'click .remove-location-rule': 'onClickRemoveRule', - 'change .refresh-location-rule': 'onChangeRemoveRule', - 'change .rule-groups .operator select': 'onChangeRemoveRule', - 'change .rule-groups .value select': 'onChangeRemoveRule', - }, - initialize: function () { - this.$el = $j('#acf-field-group-locations'); - this.getGraphqlTypes(); - }, - - onClickAddRule: function (e, $el) { - this.getGraphqlTypes(); - }, - - onClickRemoveRule: function (e, $el) { - this.getGraphqlTypes(); - }, - - onChangeRemoveRule: function (e, $el) { - setTimeout(function () { - GraphqlLocationManager.getGraphqlTypes(); - }, 500); - - }, - - onClickAddGroup: function (e, $el) { - this.getGraphqlTypes(); - }, - - getGraphqlTypes: function () { - getGraphqlTypesFromLocationRules(); - }, - }) - }); From 2a8d47fec14183b76bedbb6af213d464eeff708a Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Thu, 22 Apr 2021 12:40:00 -0600 Subject: [PATCH 04/12] - Add PHPStan checks - Start adding PHPStan suggested fixes - BREAKING: Changed some class names --- .gitignore | 1 + composer.json | 27 +- phpstan.neon.dist | 22 + phpstan/class-wp-post-type.stub | 9 + phpstan/class-wp-taxonomy.stub | 9 + phpstan/constants.php | 8 + src/Acf.php | 166 +++ src/AcfSettings.php | 262 +++++ src/Fields/AcfField.php | 2 + src/LocationRules.php | 983 ++++++++++++++++++ src/Registry.php | 48 +- .../InterfaceType/AcfFieldGroupInterface.php | 25 +- src/Types/ObjectType/AcfFieldGroupConfig.php | 36 + src/Types/ObjectType/AcfGoogleMap.php | 11 + src/Types/ObjectType/AcfLink.php | 10 +- src/admin/js/main.js | 296 ++++++ vendor/composer/InstalledVersions.php | 4 +- vendor/composer/autoload_classmap.php | 7 +- vendor/composer/autoload_static.php | 7 +- vendor/composer/installed.php | 4 +- wp-graphql-acf.php | 22 +- 21 files changed, 1913 insertions(+), 46 deletions(-) create mode 100644 phpstan.neon.dist create mode 100644 phpstan/class-wp-post-type.stub create mode 100644 phpstan/class-wp-taxonomy.stub create mode 100644 phpstan/constants.php create mode 100644 src/Acf.php create mode 100644 src/AcfSettings.php create mode 100644 src/LocationRules.php create mode 100644 src/Types/ObjectType/AcfFieldGroupConfig.php create mode 100644 src/admin/js/main.js diff --git a/.gitignore b/.gitignore index e6c29ad..c24e35e 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ vendor/composer/installed.json vendor/composer/*/ composer.lock .log +local diff --git a/composer.json b/composer.json index a5fcb29..315d167 100644 --- a/composer.json +++ b/composer.json @@ -19,6 +19,21 @@ "src/" ] }, + "repositories": [ + { + "type": "package", + "package": { + "name": "wp-premium/advanced-custom-fields-pro", + "version": "5.8.7", + "type": "wordpress-plugin", + "source": { + "url": "https://github.com/wp-premium/advanced-custom-fields-pro.git", + "type": "git", + "reference": "master" + } + } + } + ], "scripts": { "install-test-env": "bash bin/install-test-env.sh", "docker-build": "bash bin/run-docker.sh build", @@ -66,6 +81,16 @@ "wp-graphql/wp-graphql-testcase": "^1.0", "phpunit/phpunit": "9.4.1", "simpod/php-coveralls-mirror": "^3.0", - "phpstan/extension-installer": "^1.1" + "phpstan/extension-installer": "^1.1", + "wp-graphql/wp-graphql": "^v1.3.5", + "wp-premium/advanced-custom-fields-pro": "^5" + }, + "extra": { + "wordpress-install-dir": "local/public", + "installer-paths": { + "local/public/wp-content/plugins/{$name}/": ["type:wordpress-plugin"], + "local/public/wp-content/mu-plugins/{$name}/": ["type:wordpress-muplugin"], + "local/public/wp-content/themes/{$name}/": ["type:wordpress-theme"] + } } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..6030bb5 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,22 @@ +parameters: + level: 8 + inferPrivatePropertyTypeFromConstructor: true + checkMissingIterableValueType: false + stubFiles: + # Simulate added properties + - phpstan/class-wp-post-type.stub + - phpstan/class-wp-taxonomy.stub + scanDirectories: + - local/public/wp-content/plugins/advanced-custom-fields-pro + - local/public/wp-content/plugins/wp-graphql + bootstrapFiles: + - phpstan/constants.php + - wp-graphql-acf.php + paths: + - wp-graphql-acf.php + - src/ + excludePaths: + - src/deprecated-class-config.php + ignoreErrors: + # Ignore any filters that are applied with more than 2 paramaters + - '#^Function apply_filters(_ref_array)? invoked with ([1-9]|1[0-2]) parameters, 2 required\.$#' diff --git a/phpstan/class-wp-post-type.stub b/phpstan/class-wp-post-type.stub new file mode 100644 index 0000000..33a7209 --- /dev/null +++ b/phpstan/class-wp-post-type.stub @@ -0,0 +1,9 @@ +setup_constants(); + self::$instance->includes(); + self::$instance->actions(); + self::$instance->filters(); + self::$instance->init(); + } + + /** + * Fire off init action + * + * @param Acf $instance The instance of the WPGraphQL\Acf class + */ + do_action( 'graphql_acf_init', self::$instance ); + + /** + * Return the WPGraphQL Instance + */ + return self::$instance; + } + + /** + * Throw error on object clone. + * The whole idea of the singleton design pattern is that there is a single object + * therefore, we don't want the object to be cloned. + * + * @access public + * @return void + */ + public function __clone() { + + // Cloning instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'The \WPGraphQL\ACF class should not be cloned.', 'wp-graphql-acf' ), '0.0.1' ); + + } + + /** + * Disable unserializing of the class. + * + * @access protected + * @return void + */ + public function __wakeup() { + + // De-serializing instances of the class is forbidden. + _doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the \WPGraphQL\ACF class is not allowed', 'wp-graphql-acf' ), '0.0.1' ); + + } + + /** + * Setup plugin constants. + * + * @access private + * @return void + */ + private function setup_constants() { + + // Plugin Folder Path. + if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_DIR' ) ) { + define( 'WPGRAPHQL_ACF_PLUGIN_DIR', plugin_dir_path( __FILE__ . '/..' ) ); + } + + // Plugin Folder URL. + if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_URL' ) ) { + define( 'WPGRAPHQL_ACF_PLUGIN_URL', plugin_dir_url( __FILE__ . '/..' ) ); + } + + // Plugin Root File. + if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_FILE' ) ) { + define( 'WPGRAPHQL_ACF_PLUGIN_FILE', __FILE__ . '/..' ); + } + + } + + /** + * Include required files. + * Uses composer's autoload + * + * @access private + * @return void + */ + private function includes() { + + // Autoload Required Classes. + } + + /** + * Sets up actions to run at certain spots throughout WordPress and the WPGraphQL execution + * cycle + * + * @return void + */ + private function actions() { + + } + + /** + * Setup filters + * + * @return void + */ + private function filters() { + + /** + * This filters any field that returns the `ContentTemplate` type + * to pass the source node down to the template for added context + */ + add_filter( 'graphql_resolve_field', function( $result, $source, $args, $context, ResolveInfo $info, $type_name, $field_key, $field, $field_resolver ) { + if ( isset( $info->returnType ) && strtolower( 'ContentTemplate' ) === strtolower( $info->returnType ) ) { + if ( is_array( $result ) && ! isset( $result['node'] ) && ! empty( $source ) ) { + $result['node'] = $source; + } + } + return $result; + }, 10, 9 ); + + } + + /** + * Initialize + * + * @return void + */ + private function init() { + + $registry = new Registry(); + add_action( 'graphql_register_types', [ $registry, 'init' ], 10, 1 ); + + $acf_settings = new AcfSettings(); + $acf_settings->init(); + + } + +} diff --git a/src/AcfSettings.php b/src/AcfSettings.php new file mode 100644 index 0000000..c538f3c --- /dev/null +++ b/src/AcfSettings.php @@ -0,0 +1,262 @@ +determine_location_rules(); + $rules = $location_rules->get_rules(); + + $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $group_name = $location_rules->format_field_name( $group_name ); + + switch( $column_name ) { + case 'graphql_types': + echo isset( $rules[ $group_name ] ) ? implode( ', ', $rules[ $group_name ] ) : ''; + break; + case 'graphql_field_name': + echo isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : ''; + break; + default: + break; + } + + } + + public function admin_table_columns( $columns ) { + $columns['graphql_types'] = __( 'GraphQL Schema Location', 'wp-graphql-acf' ); + $columns['graphql_field_name'] = __( 'GraphQL Field Name', 'wp-graphql-acf' ); + return $columns; + + } + + /** + * Handle the AJAX callback for converting ACF Location settings to GraphQL Types + * + * @return void + */ + public function ajax_callback() { + + if ( isset( $_POST['data'] ) ) { + + $form_data = []; + + parse_str( $_POST['data'], $form_data ); + + if ( empty( $form_data ) || ! isset( $form_data['acf_field_group'] ) ) { + wp_send_json( __( 'No form data.', 'wp-graphql-acf' ) ); + } + + $field_group = isset( $form_data['acf_field_group'] ) ? $form_data['acf_field_group'] : []; + $rules = new LocationRules( [ $field_group ] ); + $rules->determine_location_rules(); + + $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $group_name = $rules->format_field_name( $group_name ); + + $all_rules = $rules->get_rules(); + if ( isset( $all_rules[ $group_name ] ) ) { + wp_send_json( [ 'graphql_types' => $all_rules[ $group_name ] ] ); + } + wp_send_json( [ 'graphql_types' => null ] ); + } + + echo __( 'No location rules were found', 'wp-graphql-acf' ); + wp_die(); + } + + /** + * Register the GraphQL Settings metabox for the ACF Field Group post type + * + * @return void + */ + public function register_meta_boxes() { + add_meta_box( 'wpgraphql-acf-meta-box', __( 'GraphQL', 'wp-graphql-acf' ), [ + $this, + 'display_metabox' + ], [ 'acf-field-group' ] ); + } + + /** + * Display the GraphQL Settings Metabox on the Field Group admin page + * + * @param $field_group_post_object + */ + public function display_metabox( $field_group_post_object ) { + + global $field_group; + + // Render a field in the Field Group settings to allow for a Field Group to be shown in GraphQL. + acf_render_field_wrap( + [ + 'label' => __( 'Show in GraphQL', 'acf' ), + 'instructions' => __( 'If the field group is active, and this is set to show, the fields in this group will be available in the WPGraphQL Schema based on the respective Location rules.' ), + 'type' => 'true_false', + 'name' => 'show_in_graphql', + 'prefix' => 'acf_field_group', + 'value' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : false, + 'ui' => 1, + ] + ); + + /** + * Render a field in the Field Group settings to set the GraphQL field name for the field group. + */ + acf_render_field_wrap( + [ + 'label' => __( 'GraphQL Field Name', 'acf' ), + 'instructions' => __( 'The name of the field group in the GraphQL Schema. Names should not include spaces or special characters. Best practice is to use "camelCase".', 'wp-graphql-acf' ), + 'type' => 'text', + 'prefix' => 'acf_field_group', + 'name' => 'graphql_field_name', + 'required' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : false, + 'placeholder' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null, + 'value' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null, + ] + ); + + acf_render_field_wrap( + [ + 'label' => __( 'Manually Set GraphQL Types for Field Group', 'acf' ), + 'instructions' => __( 'By default, ACF Field groups are added to the GraphQL Schema based on the field group\'s location rules. Checking this box will let you manually control the GraphQL Types the field group should be shown on in the GraphQL Schema using the checkboxes below, and the Location Rules will no longer effect the GraphQL Types.', 'wp-graphql-acf' ), + 'type' => 'true_false', + 'name' => 'map_graphql_types_from_location_rules', + 'prefix' => 'acf_field_group', + 'value' => isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false, + 'ui' => 1, + ] + ); + + $choices = Config::get_all_graphql_types(); + acf_render_field_wrap( + [ + 'label' => __( 'GraphQL Types to Show the Field Group On', 'wp-graphql-acf' ), + 'instructions' => __( 'Select the Types in the WPGraphQL Schema to show the fields in this field group on', 'wp-graphql-acf' ), + 'type' => 'checkbox', + 'prefix' => 'acf_field_group', + 'name' => 'graphql_types', + 'value' => ! empty( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : [], + 'toggle' => true, + 'choices' => $choices, + ] + ); + + ?> +
+ +
+ + __( 'Show in GraphQL', 'wp-graphql-acf' ), + 'instructions' => __( 'Whether the field should be queryable via GraphQL', 'wp-graphql-acf' ), + 'name' => 'show_in_graphql', + 'type' => 'true_false', + 'ui' => 1, + 'default_value' => 1, + 'value' => isset( $field['show_in_graphql'] ) ? (bool) $field['show_in_graphql'] : true, + ], + true + ); + + } + + /** + * This enqueues admin script. + * + * @param string $screen The screen that scripts are being enqueued to + * + * @return void + */ + public function enqueue_graphql_acf_scripts( string $screen ) { + global $post; + + if ( $screen == 'post-new.php' || $screen == 'post.php' ) { + if ( 'acf-field-group' === $post->post_type ) { + wp_enqueue_script( 'graphql-acf', plugins_url( 'src/admin/js/main.js', dirname( __FILE__ ) ), array( + 'jquery', + 'acf-input', + 'acf-field-group' + ) ); + } + } + } + +} diff --git a/src/Fields/AcfField.php b/src/Fields/AcfField.php index 9ef0d95..e6d571e 100644 --- a/src/Fields/AcfField.php +++ b/src/Fields/AcfField.php @@ -322,6 +322,8 @@ public function prepare_acf_field_value( $value, $node, $node_id ) { /** * Registers a field to the WPGraphQL Schema + * + * @return void */ public function register_field() { diff --git a/src/LocationRules.php b/src/LocationRules.php new file mode 100644 index 0000000..760b273 --- /dev/null +++ b/src/LocationRules.php @@ -0,0 +1,983 @@ +acf_field_groups = isset( $acf_field_groups ) && ! empty( $acf_field_groups ) ? $acf_field_groups : acf_get_field_groups(); + } + + /** + * Given a field name, formats it for GraphQL + * + * @param string $field_name The field name to format + * + * @return string + */ + public function format_field_name( string $field_name ) { + + $replaced = preg_replace( '[^a-zA-Z0-9 -]', '_', $field_name ); + + // If any values were replaced, use the replaced string as the new field name + if ( ! empty( $replaced ) ) { + $field_name = $replaced; + } + + $field_name = lcfirst( $field_name ); + $field_name = lcfirst( str_replace( '-', ' ', ucwords( $field_name, '_' ) ) ); + $field_name = lcfirst( str_replace( ' ', '', ucwords( $field_name, ' ' ) ) ); + + return $field_name; + } + + /** + * Given a type name, formats it for GraphQL + * + * @param string $type_name The type name to format + * + * @return string + */ + public function format_type_name( string $type_name ) { + return ucfirst( $this->format_field_name( $type_name ) ); + } + + /** + * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this sets the + * field group to show in that Type + * + * @param string $field_group_name The name of the ACF Field Group + * @param string $graphql_type_name The name of the GraphQL Type + */ + public function set_graphql_type( string $field_group_name, string $graphql_type_name ) { + $this->mapped_field_groups[ Utils::format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name ); + } + + /** + * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this unsets the + * GraphQL Type for the field group + * + * @param string $field_group_name The name of the ACF Field Group + * @param string $graphql_type_name The name of the GraphQL Type + */ + public function unset_graphql_type( string $field_group_name, string $graphql_type_name ) { + $this->unset_types[ $this->format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name ); + } + + /** + * Get the rules + * + * @return array + */ + public function get_rules() { + + $mapped_field_groups = isset( $this->mapped_field_groups ) && ! empty( $this->mapped_field_groups ) ? $this->mapped_field_groups : []; + + if ( empty( $mapped_field_groups ) ) { + return []; + } + + + if ( empty( $this->unset_types ) ) { + return $mapped_field_groups; + } + + /** + * Remove any Types that were flagged to unset + */ + foreach ( $this->unset_types as $field_group => $types ) { + if ( ! empty( $types ) ) { + foreach ( $types as $type ) { + if ( isset( $this->mapped_field_groups[ $field_group ] ) ) { + if ( ( $key = array_search( $type, $mapped_field_groups[ $field_group ] ) ) !== false ) { + unset( $mapped_field_groups[ $field_group ][ $key ] ); + } + } + } + } + } + + return $mapped_field_groups; + + } + + /** + * Checks for conflicting rule types to avoid impossible states. + * + * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag" + * this would be an impossible state, as an object can't be a Post and a Tag. + * + * If we detect conflicting rules, the rule set is not applied at all. + * + * @param array $and_params The parameters of the rule group + * @param mixed $param The current param being evaluated + * @param array $allowed_params The allowed params that shouldn't conflict + * + * @return bool + */ + public function check_for_conflicts( array $and_params, $param, $allowed_params = [] ) { + + if ( empty( $and_params ) ) { + return false; + } + + $has_conflict = false; + $keys = array_keys( $and_params, $param ); + + if ( isset( $keys[0] ) ) { + unset( $and_params[ $keys[0] ] ); + } + + if ( ! empty( $and_params ) ) { + foreach ( $and_params as $key => $and_param ) { + if ( false === array_search( $and_param, $allowed_params, true ) ) { + $has_conflict = true; + } + } + } + + return $has_conflict; + + } + + /** + * Checks for conflicting rule types to avoid impossible states. + * + * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag" + * this would be an impossible state, as an object can't be a Post and a Tag. + * + * If we detect conflicting rules, the rule set is not applied at all. + * + * @param array $and_params The parameters of the rule group + * @param mixed $param The current param being evaluated + * + * @return bool + */ + public function check_params_for_conflicts( array $and_params = [], $param ) { + switch ( $param ) { + case 'post_type': + $allowed_and_params = [ + 'post_status', + 'post_format', + 'post_category', + 'post_taxonomy', + 'post', + ]; + break; + case 'post_template': + case 'page_template': + $allowed_and_params = [ + 'page_type', + 'page_parent', + 'page', + ]; + break; + case 'post_status': + $allowed_and_params = [ + 'post_type', + 'post_format', + 'post_category', + 'post_taxonomy', + ]; + break; + case 'post_format': + case 'post_category': + case 'post': + case 'post_taxonomy': + $allowed_and_params = [ + 'post_status', + 'post_type', + 'post_format', + 'post_category', + 'post_taxonomy', + 'post', + ]; + break; + case 'page': + case 'page_parent': + case 'page_type': + $allowed_and_params = [ + 'page_template', + 'page_type', + 'page_parent', + 'page', + ]; + break; + case 'current_user': + case 'current_user_role': + // @todo: + // Right now, if you set current_user or current_user_role as the only rule, + // ACF adds the field group to every possible location in the Admin. + // This seems a bit heavy handed. 🤔 + // We need to think through this a bit more, and how this rule + // Can be composed with other rules, etc. + $allowed_and_params = []; + break; + case 'user_form': + case 'user_role': + $allowed_and_params = [ + 'user_form', + 'user_role', + ]; + break; + case 'taxonomy': + case 'attachment': + case 'comment': + case 'widget': + case 'nav_menu': + case 'nav_menu_item': + case 'options_page': + default: + $allowed_and_params = []; + break; + + } + + return $this->check_for_conflicts( $and_params, $param, $allowed_and_params ); + + } + + /** + * Determine how an ACF Location Rule should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_rules( string $field_group_name, string $param, string $operator, string $value ) { + + // Depending on the param of the rule, there's different logic to + // map to the Schema + switch ( $param ) { + case 'post_type': + $this->determine_post_type_rules( $field_group_name, $param, $operator, $value ); + break; + case 'post_template': + case 'page_template': + $this->determine_post_template_rules( $field_group_name, $param, $operator, $value ); + break; + case 'post_status': + $this->determine_post_status_rules( $field_group_name, $param, $operator, $value ); + break; + case 'post_format': + case 'post_category': + case 'post_taxonomy': + $this->determine_post_taxonomy_rules( $field_group_name, $param, $operator, $value ); + break; + case 'post': + $this->determine_post_rules( $field_group_name, $param, $operator, $value ); + break; + case 'page_type': + $this->determine_page_type_rules( $field_group_name, $param, $operator, $value ); + break; + case 'page_parent': + case 'page': + // If page or page_parent is set, regardless of operator and value, + // we can add the field group to the Page type + $this->set_graphql_type( $field_group_name, 'Page' ); + break; + case 'current_user': + case 'current_user_role': + // @todo: + // Right now, if you set current_user or current_user_role as the only rule, + // ACF adds the field group to every possible location in the Admin. + // This seems a bit heavy handed. 🤔 + // We need to think through this a bit more, and how this rule + // Can be composed with other rules, etc. + break; + case 'user_form': + case 'user_role': + // If user_role or user_form params are set, we need to expose the field group + // to the User type + $this->set_graphql_type( $field_group_name, 'User' ); + break; + case 'taxonomy': + $this->determine_taxonomy_rules( $field_group_name, $param, $operator, $value ); + break; + case 'attachment': + $this->determine_attachment_rules( $field_group_name, $param, $operator, $value ); + break; + case 'comment': + $this->determine_comment_rules( $field_group_name, $param, $operator, $value ); + break; + case 'widget': + // @todo: Widgets are not currently supported in WPGraphQL + break; + case 'nav_menu': + $this->determine_nav_menu_rules( $field_group_name, $param, $operator, $value ); + break; + case 'nav_menu_item': + $this->determine_nav_menu_item_item_rules( $field_group_name, $param, $operator, $value ); + break; + case 'options_page': + $this->determine_options_rules( $field_group_name, $param, $operator, $value ); + break; + default: + // If a built-in location rule could not be matched, + // Custom rules (from extensions, etc) can hook in here and apply their + // rules to the WPGraphQL Schema + do_action( 'graphql_acf_match_location_rule', $field_group_name, $param, $operator, $value, $this ); + break; + + } + + } + + /** + * Determine GraphQL Schema location rules based on ACF Location rules for field groups + * that are configured with no `graphql_types` field. + * + * @return void + */ + public function determine_location_rules() { + + if ( ! empty( $this->acf_field_groups ) ) { + foreach ( $this->acf_field_groups as $field_group ) { + + $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + + if ( ! isset( $field_group['active'] ) || false === (bool) $field_group['active'] ) { + continue; + } + + if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) { + + foreach ( $field_group['location'] as $location_rule_group ) { + if ( ! empty( $location_rule_group ) ) { + + foreach ( $location_rule_group as $group => $rule ) { + + // Determine the and params for the rule group + $and_params = wp_list_pluck( $location_rule_group, 'param' ); + $and_params = ! empty( $and_params ) ? array_values( $and_params ) : []; + + $operator = isset( $rule['operator'] ) ? $rule['operator'] : '=='; + $param = isset( $rule['param'] ) ? $rule['param'] : null; + $value = isset( $rule['value'] ) ? $rule['value'] : null; + + if ( empty( $param ) || empty( $value ) ) { + continue; + } + + if ( true === $this->check_params_for_conflicts( $and_params, $param ) ) { + continue; + } + + $this->determine_rules( $field_group_name, $param, $operator, $value ); + + } + } + } + } + } + } + + } + + /** + * Returns an array of Post Templates + * + * @return array + */ + public function get_graphql_post_template_types() { + + $registered_page_templates = wp_get_theme()->get_post_templates(); + + $page_templates['default'] = 'DefaultTemplate'; + + if ( ! empty( $registered_page_templates ) && is_array( $registered_page_templates ) ) { + + foreach ( $registered_page_templates as $post_type_templates ) { + // Post templates are returned as an array of arrays. PHPStan believes they're returned as + // an array of strings and believes this will always evaluate to false. + // We should ignore the phpstan check here. + // @phpstan-ignore-next-line + if ( ! empty( $post_type_templates ) && is_array( $post_type_templates ) ) { + foreach ( $post_type_templates as $file => $name ) { + + $name = ucwords( $name ); + $replaced_name = preg_replace( '/[^\w]/', '', $name ); + + if ( ! empty( $replaced_name ) ) { + $name = $replaced_name; + } + + if ( preg_match( '/^\d/', $name ) || false === strpos( strtolower( $name ), 'template' ) ) { + $name = 'Template_' . $name; + } + + $page_templates[ $file ] = $name; + } + } + } + } + + return $page_templates; + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_type_rules( string $field_group_name, string $param, string $operator, string $value ) { + $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); + + if ( empty( $allowed_post_types ) ) { + return; + } + + if ( '==' === $operator ) { + + // If all post types + if ( 'all' === $value ) { + + // loop over and set all post types + foreach ( $allowed_post_types as $allowed_post_type ) { + + $post_type_object = get_post_type_object( $allowed_post_type ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } else { + if ( in_array( $value, $allowed_post_types, true ) ) { + $post_type_object = get_post_type_object( $value ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + + } + + if ( '!=' === $operator ) { + + if ( 'all' !== $value ) { + // loop over and set all post types + foreach ( $allowed_post_types as $allowed_post_type ) { + $post_type_object = get_post_type_object( $allowed_post_type ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + $post_type_object = get_post_type_object( $value ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->unset_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_template_rules( string $field_group_name, string $param, string $operator, string $value ) { + + $templates = $this->get_graphql_post_template_types(); + + if ( ! is_array( $templates ) || empty( $templates ) ) { + return; + } + + if ( '==' === $operator ) { + + // If the template is available in GraphQL, set it + if ( isset( $templates[ $value ] ) ) { + $this->set_graphql_type( $field_group_name, $templates[ $value ] ); + } + } + + if ( '!=' === $operator ) { + + foreach ( $templates as $name => $template_type ) { + $this->set_graphql_type( $field_group_name, $template_type ); + } + + // If the Template is available in GraphQL, unset it + if ( isset( $templates[ $value ] ) ) { + $this->unset_graphql_type( $field_group_name, $templates[ $value ] ); + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_status_rules( string $field_group_name, string $param, string $operator, string $value ) { + // @todo: Should post status affect the GraphQL Schema at all? + // If a field group is set to show on "post_status == publish" as the only rule, what post type does that apply to? All? 🤔 + // If a field group is set to show on "post_status != draft" does that mean the field group should be available on all post types in the Schema by default? + // This seems like a very difficult rule to translate to the Schema. + // Like, lets say I add a field group called: "Editor Notes" that I want to show for any status that is not "publish". In theory, if that's my only rule, that seems like it should apply to all post types across the board, and show in the Admin in any state of the post, other than publish. 🤔 + + // ACF Admin behavior seems to add it to the Admin on all post types, so WPGraphQL + // should respect this rule and also add it to all post types. The resolver should + // then determine whether to resolve the data or not, based on this rule. + + // If Post Status is used to qualify a field group location, + // It will be added to the Schema for any Post Type that is set to show in GraphQL + $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); + foreach ( $allowed_post_types as $post_type ) { + + $post_type_object = get_post_type_object( $post_type ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_format_rules( string $field_group_name, string $param, string $operator, string $value ) { + + $post_format_taxonomy = get_taxonomy( 'post_format' ); + $post_format_post_types = $post_format_taxonomy->object_type; + + if ( ! is_array( $post_format_post_types ) || empty( $post_format_post_types ) ) { + return; + } + + // If Post Format is used to qualify a field group location, + // It will be added to the Schema for any Post Type that supports post formats + // And shows in GraphQL + $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); + foreach ( $allowed_post_types as $post_type ) { + if ( in_array( $post_type, $post_format_post_types, true ) ) { + $post_type_object = get_post_type_object( $value ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) { + + // If Post Taxonomy is used to qualify a field group location, + // It will be added to the Schema for the Post post type + $this->set_graphql_type( $field_group_name, 'Post' ); + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_post_rules( string $field_group_name, string $param, string $operator, string $value ) { + + // If a Single post is used to qualify a field group location, + // It will be added to the Schema for the GraphQL Type for the post_type of the Post + // it is assigned to + + if ( '==' === $operator ) { + + if ( absint( $value ) ) { + $post = get_post( absint( $value ) ); + if ( $post instanceof \WP_Post ) { + $post_type_object = get_post_type_object( $post->post_type ); + if ( isset( $post_type_object->show_in_graphql ) && true === $post_type_object->show_in_graphql ) { + if ( isset( $post_type_object->graphql_single_name ) ) { + $this->set_graphql_type( $field_group_name, $post_type_object->graphql_single_name ); + } + } + } + } + + } + + // If a single post is used as not equal, + // the field group should be added to ALL post types in the Schema + if ( '!=' === $operator ) { + + $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); + + if ( empty( $allowed_post_types ) ) { + return; + } + + // loop over and set all post types + foreach ( $allowed_post_types as $allowed_post_type ) { + + $post_type_object = get_post_type_object( $allowed_post_type ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_page_type_rules( string $field_group_name, string $param, string $operator, string $value ) { + + // If front_page or posts_page is set to equal_to or not_equal_to + // then the field group should be shown on the Post type + if ( in_array( $value, [ 'front_page', 'posts_page' ], true ) ) { + $this->set_graphql_type( $field_group_name, 'Page' ); + } + + // If top_level, parent, or child is set as equal_to or not_equal_to + // then the field group should be shown on all hierarchical post types + if ( in_array( $value, [ 'top_level', 'parent', 'child' ], true ) ) { + + $hierarchical_post_types = get_post_types( [ + 'show_in_graphql' => true, + 'hierarchical' => true + ] ); + + if ( empty( $hierarchical_post_types ) ) { + return; + } + + // loop over and set all post types + foreach ( $hierarchical_post_types as $allowed_post_type ) { + + $post_type_object = get_post_type_object( $allowed_post_type ); + $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) { + + $allowed_taxonomies = get_taxonomies( [ 'show_in_graphql' => true ] ); + + if ( empty( $allowed_taxonomies ) ) { + return; + } + + if ( '==' === $operator ) { + + // If all post types + if ( 'all' === $value ) { + + // loop over and set all post types + foreach ( $allowed_taxonomies as $allowed_taxonomy ) { + + $tax_object = get_taxonomy( $allowed_taxonomy ); + $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + + } else { + if ( in_array( $value, $allowed_taxonomies, true ) ) { + $tax_object = get_taxonomy( $value ); + $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + } + + + } + + if ( '!=' === $operator ) { + + if ( 'all' !== $value ) { + + // loop over and set all post types + foreach ( $allowed_taxonomies as $allowed_taxonomy ) { + + $tax_object = get_taxonomy( $allowed_taxonomy ); + $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->set_graphql_type( $field_group_name, $graphql_name ); + } + } + + $tax_object = get_taxonomy( $value ); + $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; + if ( ! empty( $graphql_name ) ) { + $this->unset_graphql_type( $field_group_name, $graphql_name ); + } + + } + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_attachment_rules( string $field_group_name, string $param, string $operator, string $value ) { + + if ( '==' === $operator ) { + $this->set_graphql_type( $field_group_name, 'MediaItem' ); + } + + if ( '!=' === $operator && 'all' === $value ) { + $this->unset_graphql_type( $field_group_name, 'MediaItem' ); + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_comment_rules( string $field_group_name, string $param, string $operator, string $value ) { + + if ( '==' === $operator ) { + $this->set_graphql_type( $field_group_name, 'Comment' ); + } + + if ( '!=' === $operator ) { + + // If not equal to all, unset from all comments + if ( 'all' === $value ) { + $this->unset_graphql_type( $field_group_name, 'Comment' ); + + // If not equal to just a specific post type/comment relationship, + // show the field group on the Comment Type + } else { + $this->set_graphql_type( $field_group_name, 'Comment' ); + } + + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_nav_menu_rules( string $field_group_name, string $param, string $operator, string $value ) { + + if ( '==' === $operator ) { + $this->set_graphql_type( $field_group_name, 'Menu' ); + } + + if ( '!=' === $operator ) { + + // If not equal to all, unset from all Menu + if ( 'all' === $value ) { + $this->unset_graphql_type( $field_group_name, 'Menu' ); + + // If not equal to just a Menu, + // show the field group on all Menus + } else { + $this->set_graphql_type( $field_group_name, 'Menu' ); + } + + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_nav_menu_item_item_rules( string $field_group_name, string $param, string $operator, string $value ) { + + if ( '==' === $operator ) { + $this->set_graphql_type( $field_group_name, 'MenuItem' ); + } + + if ( '!=' === $operator ) { + + // If not equal to all, unset from all MenuItem + if ( 'all' === $value ) { + $this->unset_graphql_type( $field_group_name, 'MenuItem' ); + + // If not equal to one Menu / location, + // show the field group on all MenuItems + } else { + $this->set_graphql_type( $field_group_name, 'MenuItem' ); + } + + } + + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_block_rules( string $field_group_name, string $param, string $operator, string $value ) { + // @todo: ACF Blocks are not formally supported by WPGraphQL / WPGraphQL for ACF. More to come in the future! + } + + /** + * Determines how the ACF Rules should apply to the WPGraphQL Schema + * + * @param string $field_group_name The name of the ACF Field Group the rule applies to + * @param string $param The parameter of the rule + * @param string $operator The operator of the rule + * @param string $value The value of the rule + */ + public function determine_options_rules( string $field_group_name, string $param, string $operator, string $value ) { + + if ( '==' === $operator ) { + $options_page = acf_get_options_page( $value ); + + if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + return; + } + + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + + $this->set_graphql_type( $field_group_name, $type_name ); + } + + if ( '!=' === $operator ) { + + $options_pages = acf_get_options_pages(); + + if ( empty( $options_pages ) || ! is_array( $options_pages ) ) { + return; + } + + // Show all options pages + foreach ( $options_pages as $options_page ) { + if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + continue; + } + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + $this->set_graphql_type( $field_group_name, $type_name ); + } + + // Get the options page to unset + $options_page = acf_get_options_page( $value ); + if ( ! isset( $options_page['show_in_graphql'] ) || false === $options_page['show_in_graphql'] ) { + return; + } + $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); + $this->unset_graphql_type( $field_group_name, $type_name ); + } + + } + +} diff --git a/src/Registry.php b/src/Registry.php index 9ece1b8..a34f689 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -6,6 +6,7 @@ use GraphQLRelay\Relay; use WPGraphQL\ACF\Fields\AcfField; use WPGraphQL\ACF\Types\InterfaceType\AcfFieldGroupInterface; +use WPGraphQL\ACF\Types\ObjectType\AcfFieldGroupConfig; use WPGraphQL\ACF\Types\ObjectType\AcfGoogleMap; use WPGraphQL\ACF\Types\ObjectType\AcfLink; use WPGraphQL\Registry\TypeRegistry; @@ -142,7 +143,10 @@ protected function map_acf_to_graphql() { } /** + * Register ACF Options pages to the GraphQL Schema. * + * @return void + * @throws Exception */ public function register_options_pages() { @@ -205,6 +209,8 @@ public function register_options_pages() { /** * Register initial types to the Schema * + * @return void + * * @throws Exception */ public function register_initial_types() { @@ -215,7 +221,12 @@ public function register_initial_types() { // Object Types AcfLink::register_type(); AcfGoogleMap::register_type(); + AcfFieldGroupConfig::register_type(); + /** + * Registers a RootQuery entry for fetching + * an individual FieldGroup by ID + */ $this->type_registry->register_field( 'RootQuery', 'acfFieldGroup', [ 'description' => __( 'ACF Field Group', 'wp-graphql-acf' ), 'type' => 'AcfFieldGroup', @@ -229,9 +240,13 @@ public function register_initial_types() { $id_parts = Relay::fromGlobalId( $args['id'] ); $field_group = isset( $id_parts['id'] ) ? acf_get_field_group( $id_parts['id'] ) : null; + if ( empty( $field_group ) ) { + return null; + } + return [ 'fieldGroupName' => isset( $field_group['title'] ) ? $field_group['title'] : null, - '_fieldGroupConfig' => ! empty( $field_group ) ? $field_group : null, + '_fieldGroupConfig' => $field_group, ]; } @@ -240,24 +255,28 @@ public function register_initial_types() { } /** - * Map user generated field groups to the Schema + * Map ACF field groups to the Schema + * + * @return void + * @throws Exception */ public function map_acf_field_groups_to_types() { - foreach ( $this->acf_field_groups as $field_group ) { $this->add_acf_field_group_to_graphql( $field_group ); } - } /** - * @param $field_group + * Adds an ACF Field Group to the GraphQL Schema by determining the GraphQL Types the + * field group should show on. + * + * @param array $field_group The ACF Field Group config to add to the Schema * * @return mixed|string|null * * @throws Exception */ - public function add_acf_field_group_to_graphql( $field_group ) { + public function add_acf_field_group_to_graphql( array $field_group ) { if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { return null; @@ -266,6 +285,7 @@ public function add_acf_field_group_to_graphql( $field_group ) { $type_name = $this->get_field_group_type_name( $field_group ); $interface_name = 'With' . $type_name; + // Check if a GraphQL Type already exists for the Type if ( null === $this->type_registry->get_type( $type_name ) ) { $this->type_registry->register_object_type( $type_name, [ @@ -284,7 +304,7 @@ public function add_acf_field_group_to_graphql( $field_group ) { ], 'fieldGroupName' => [ 'resolve' => function() use ( $type_name ) { - return isset( $type_name ) ? lcfirst( $type_name ) : null; + return lcfirst( $type_name ); } ] ], @@ -340,11 +360,15 @@ public function add_acf_field_group_to_graphql( $field_group ) { } /** - * @param $field_group + * Get the GraphQL Types a Field Group should be registered to show on + * + * @param array $field_group The ACF Field Group config to determine the Types for + * + * @return array * - * @return array|mixed + * @return array */ - public function get_graphql_types_for_field_group( $field_group ) { + public function get_graphql_types_for_field_group( array $field_group ) { $graphql_types = isset( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : []; @@ -445,6 +469,8 @@ public function get_field_group_type_name( array $field_group ) { * Map Fields to the Field Groups in the Schema * * @param array $field_group + * + * @return void */ public function map_acf_fields_to_field_group( array $field_group ) { @@ -492,6 +518,8 @@ public function map_acf_fields_to_field_group( array $field_group ) { /** * @param array $field The ACF Field config * @param array $field_group The ACF Field Group Config + * + * @return void */ public function register_graphql_field( array $field, array $field_group ) { diff --git a/src/Types/InterfaceType/AcfFieldGroupInterface.php b/src/Types/InterfaceType/AcfFieldGroupInterface.php index 55f2390..3005cf0 100644 --- a/src/Types/InterfaceType/AcfFieldGroupInterface.php +++ b/src/Types/InterfaceType/AcfFieldGroupInterface.php @@ -4,30 +4,25 @@ use Exception; use WPGraphQL\ACF\Registry; +/** + * Class AcfFieldGroupInterface + * + * @package WPGraphQL\ACF\Types\InterfaceType + */ class AcfFieldGroupInterface { /** + * Register the AcfFieldGroup Interface Type which is shared by all + * Field Groups registered by ACF. + * * @param Registry $registry * + * @return void + * * @throws Exception */ public static function register_type( Registry $registry ) { - register_graphql_object_type( 'AcfFieldGroupConfig', [ - 'description' => __( 'Configuration settings of an ACF Field Group.', 'wp-graphql' ), - 'fields' => [ - 'databaseId' => [ - 'type' => 'Int', - 'resolve' => function( $field_group ) { - return $field_group['ID']; - } - ], - 'key' => [ - 'type' => 'String', - ], - ], - ] ); - register_graphql_interface_type('AcfFieldGroup', [ 'description' => __( 'A Field Group registered by ACF', 'wp-graphql-acf' ), diff --git a/src/Types/ObjectType/AcfFieldGroupConfig.php b/src/Types/ObjectType/AcfFieldGroupConfig.php new file mode 100644 index 0000000..18571ab --- /dev/null +++ b/src/Types/ObjectType/AcfFieldGroupConfig.php @@ -0,0 +1,36 @@ + __( 'Configuration settings of an ACF Field Group.', 'wp-graphql' ), + 'fields' => [ + 'databaseId' => [ + 'type' => 'Int', + 'resolve' => function( $field_group ) { + return $field_group['ID']; + } + ], + 'key' => [ + 'type' => 'String', + ], + ], + ] ); + + } + +} diff --git a/src/Types/ObjectType/AcfGoogleMap.php b/src/Types/ObjectType/AcfGoogleMap.php index f348332..d622e11 100644 --- a/src/Types/ObjectType/AcfGoogleMap.php +++ b/src/Types/ObjectType/AcfGoogleMap.php @@ -1,8 +1,19 @@ *'); + } + + // Toggle required attributes and visual features. + graphqlFieldNameWrap.addClass('is-required'); + graphqlLabel.find('.acf-required').show(); + graphqlInput.attr('required', true); + } else { + graphqlFieldNameWrap.removeClass('is-required'); + graphqlLabel.find('.acf-required').hide(); + graphqlInput.attr('required', false); + } + + }); + + } + + /** + * Listen to state changes for checkboxes for Interfaces and the checkboxes for + * Possible Types of the interfaces + */ + function initInterfaceCheckboxes() { + + // Find all the checkboxes for Interface types + $j('span[data-interface]').each(function (i, el) { + + // Get the interface name + let interfaceName = $j(el).data('interface'); + + // Get the checkbox for the interface + let interfaceCheckbox = $j('input[value="' + interfaceName + '"]'); + + // Find all checkboxes that implement the interface + let possibleTypesCheckboxes = $j('span[data-implements="' + interfaceName + '"]').siblings('input[type="checkbox"]'); + + // Prepend some space before to nest the Types beneath the Interface + possibleTypesCheckboxes.before("  "); + + // Listen for changes on the Interface checkbox + interfaceCheckbox.change(function () { + possibleTypesCheckboxes.prop('checked', $j(this).is(":checked")); + }) + + // Listen for changes to the checkboxes that implement the Interface + possibleTypesCheckboxes.change(function () { + + // Set the checked state of the Interface checkbox + if (!$j(this).is(":checked") && interfaceCheckbox.is(":checked")) { + interfaceCheckbox.prop("checked", false); + } + + // Set the state of the Implementing checkboxes + if ($j(possibleTypesCheckboxes).not(":checked").length === 0) { + interfaceCheckbox.prop("checked", true); + } + + }) + + }); + + } + + /** + * JavaScript version of the PHP lcfirst + * + * @param str + * @returns {string} + */ + function lcfirst(str) { + str += '' + const f = str.charAt(0) + .toLowerCase() + return f + str.substr(1) + } + + /** + * JavaScript version of the PHP ucwords + * + * @param str + * @returns {string} + */ + function ucwords(str) { + return str.toLowerCase().replace(/\b[a-z]/g, function (letter) { + return letter.toUpperCase(); + }) + } + + /** + * Based on the WPGraphQL format_field_name function + * + * See: https://github.com/wp-graphql/wp-graphql/blob/cc0b383259383898c3a1bebe65adf1140290b37e/src/Utils/Utils.php#L85-L100 + */ + function formatFieldName(fieldName) { + + fieldName.replace('[^a-zA-Z0-9 -]', '-'); + fieldName = lcfirst(fieldName); + fieldName = lcfirst(fieldName.split('-').join(' ')); + fieldName = ucwords(fieldName); + fieldName = lcfirst(fieldName.split(' ').join('')); + return fieldName; + + } + + /** + * Set the GraphQL Field Name value based on the Field Group Title + * if the graphql_field_name has not already been set. + */ + function setGraphqlFieldName() { + var graphqlFieldNameField = $j('#acf_field_group-graphql_field_name'); + var fieldGroupTitle = $j('#titlediv #title'); + if ('' === graphqlFieldNameField.val()) { + graphqlFieldNameField.val(formatFieldName(fieldGroupTitle.val())); + } + fieldGroupTitle.on('change', function () { + setGraphqlFieldName(); + }); + } + + + /** + * Determine whether users should be able to interact with the checkboxes + * to manually set the GraphQL Types for the ACF Field Group + */ + function graphqlMapTypesFromLocations() { + var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]'); + var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules'); + + if (manualMapTypes.not(':checked')) { + getGraphqlTypesFromLocationRules(); + } + + checkboxes.each(function (i, el) { + if (manualMapTypes.is(':checked')) { + $j(this).removeAttr("disabled"); + } else { + $j(this).attr("disabled", true); + } + }); + manualMapTypes.on('change', function () { + graphqlMapTypesFromLocations(); + }); + } + + function getGraphqlTypesFromLocationRules() { + + var showInGraphQLCheckbox = $j('#acf_field_group-show_in_graphql'); + var form = $j('#post'); + var formInputs = $j('#post :input'); + var serialized = formInputs.serialize(); + var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]'); + var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules'); + + // If Manual Type selection is checked, + // Don't attempt to get GraphQL Types from the location rules + if (manualMapTypes.is(':checked')) { + return; + } + + if ( ! showInGraphQLCheckbox.is(':checked') ) { + return; + } + + if ( 'pending' !== form.attr('data-request-pending') ) { + + // Start the request + form.attr('data-request-pending', 'pending' ); + + // Make the request + $j.post(ajaxurl, { + action: 'get_acf_field_group_graphql_types', + data: serialized + }, function (res) { + var types = res && res['graphql_types'] ? res['graphql_types'] : []; + + checkboxes.each(function (i, el) { + var checkbox = $j(this); + var value = $j(this).val(); + checkbox.prop('checked', false); + if (types && types.length) { + if (-1 !== $j.inArray(value, types)) { + checkbox.prop('checked', true); + checkbox.trigger("change"); + } + } + }) + + // Signal that the request is finished + form.removeAttr('data-request-pending'); + + }); + } + + }; + + // Initialize the functionality to track the state of the Interface checkboxes. + initInterfaceCheckboxes(); + toggleFieldRequirement(); + setGraphqlFieldVisibility(); + setGraphqlFieldName(); + graphqlMapTypesFromLocations(); + +}); diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index b798812..4ea28e5 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -30,7 +30,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '29fa7c49339350fa8db480090d4764af63f9de48', + 'reference' => '4b39845d52a773d528e147363e55710b62594db3', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -42,7 +42,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '29fa7c49339350fa8db480090d4764af63f9de48', + 'reference' => '4b39845d52a773d528e147363e55710b62594db3', ), ), ); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index 0d32888..3894f1e 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -7,8 +7,8 @@ return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', - 'WPGraphQL\\ACF\\ACF' => $baseDir . '/src/class-acf.php', - 'WPGraphQL\\ACF\\ACF_Settings' => $baseDir . '/src/class-acfsettings.php', + 'WPGraphQL\\ACF\\Acf' => $baseDir . '/src/Acf.php', + 'WPGraphQL\\ACF\\AcfSettings' => $baseDir . '/src/AcfSettings.php', 'WPGraphQL\\ACF\\Config' => $baseDir . '/src/deprecated-class-config.php', 'WPGraphQL\\ACF\\Fields\\AcfField' => $baseDir . '/src/Fields/AcfField.php', 'WPGraphQL\\ACF\\Fields\\File' => $baseDir . '/src/Fields/File.php', @@ -23,9 +23,10 @@ 'WPGraphQL\\ACF\\Fields\\Select' => $baseDir . '/src/Fields/Select.php', 'WPGraphQL\\ACF\\Fields\\Taxonomy' => $baseDir . '/src/Fields/Taxonomy.php', 'WPGraphQL\\ACF\\Fields\\User' => $baseDir . '/src/Fields/User.php', - 'WPGraphQL\\ACF\\LocationRules' => $baseDir . '/src/location-rules.php', + 'WPGraphQL\\ACF\\LocationRules' => $baseDir . '/src/LocationRules.php', 'WPGraphQL\\ACF\\Registry' => $baseDir . '/src/Registry.php', 'WPGraphQL\\ACF\\Types\\InterfaceType\\AcfFieldGroupInterface' => $baseDir . '/src/Types/InterfaceType/AcfFieldGroupInterface.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfFieldGroupConfig' => $baseDir . '/src/Types/ObjectType/AcfFieldGroupConfig.php', 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfGoogleMap' => $baseDir . '/src/Types/ObjectType/AcfGoogleMap.php', 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfLink' => $baseDir . '/src/Types/ObjectType/AcfLink.php', ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index a63d332..8bf4c0b 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -22,8 +22,8 @@ class ComposerStaticInit25bca2cfacbb71bc0509958a96747af9 public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', - 'WPGraphQL\\ACF\\ACF' => __DIR__ . '/../..' . '/src/class-acf.php', - 'WPGraphQL\\ACF\\ACF_Settings' => __DIR__ . '/../..' . '/src/class-acfsettings.php', + 'WPGraphQL\\ACF\\Acf' => __DIR__ . '/../..' . '/src/Acf.php', + 'WPGraphQL\\ACF\\AcfSettings' => __DIR__ . '/../..' . '/src/AcfSettings.php', 'WPGraphQL\\ACF\\Config' => __DIR__ . '/../..' . '/src/deprecated-class-config.php', 'WPGraphQL\\ACF\\Fields\\AcfField' => __DIR__ . '/../..' . '/src/Fields/AcfField.php', 'WPGraphQL\\ACF\\Fields\\File' => __DIR__ . '/../..' . '/src/Fields/File.php', @@ -38,9 +38,10 @@ class ComposerStaticInit25bca2cfacbb71bc0509958a96747af9 'WPGraphQL\\ACF\\Fields\\Select' => __DIR__ . '/../..' . '/src/Fields/Select.php', 'WPGraphQL\\ACF\\Fields\\Taxonomy' => __DIR__ . '/../..' . '/src/Fields/Taxonomy.php', 'WPGraphQL\\ACF\\Fields\\User' => __DIR__ . '/../..' . '/src/Fields/User.php', - 'WPGraphQL\\ACF\\LocationRules' => __DIR__ . '/../..' . '/src/location-rules.php', + 'WPGraphQL\\ACF\\LocationRules' => __DIR__ . '/../..' . '/src/LocationRules.php', 'WPGraphQL\\ACF\\Registry' => __DIR__ . '/../..' . '/src/Registry.php', 'WPGraphQL\\ACF\\Types\\InterfaceType\\AcfFieldGroupInterface' => __DIR__ . '/../..' . '/src/Types/InterfaceType/AcfFieldGroupInterface.php', + 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfFieldGroupConfig' => __DIR__ . '/../..' . '/src/Types/ObjectType/AcfFieldGroupConfig.php', 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfGoogleMap' => __DIR__ . '/../..' . '/src/Types/ObjectType/AcfGoogleMap.php', 'WPGraphQL\\ACF\\Types\\ObjectType\\AcfLink' => __DIR__ . '/../..' . '/src/Types/ObjectType/AcfLink.php', ); diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 875b07d..3c6a793 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => '29fa7c49339350fa8db480090d4764af63f9de48', + 'reference' => '4b39845d52a773d528e147363e55710b62594db3', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -18,7 +18,7 @@ 'aliases' => array ( ), - 'reference' => '29fa7c49339350fa8db480090d4764af63f9de48', + 'reference' => '4b39845d52a773d528e147363e55710b62594db3', ), ), ); diff --git a/wp-graphql-acf.php b/wp-graphql-acf.php index 4278ecd..6ee67e9 100644 --- a/wp-graphql-acf.php +++ b/wp-graphql-acf.php @@ -31,12 +31,12 @@ /** * Initialize the plugin * - * @return ACF|void + * @return mixed|Acf|void */ function init() { /** - * If either ACF or WPGraphQL are not active, show the admin notice and bail + * If either Acf or WPGraphQL are not active, show the admin notice and bail */ if ( false === can_load_plugin() ) { // Show the admin notice @@ -49,16 +49,17 @@ function init() { /** * Return the instance of WPGraphQL\ACF */ - return ACF::instance(); + return Acf::instance(); } -add_action( 'init', '\WPGraphQL\ACF\init' ); +add_action( 'init', '\WPGraphQL\Acf\init' ); + /** * Show admin notice to admins if this plugin is active but either ACF and/or WPGraphQL * are not active * - * @return bool + * @return void */ function show_admin_notice() { @@ -66,7 +67,7 @@ function show_admin_notice() { * For users with lower capabilities, don't show the notice */ if ( ! current_user_can( 'manage_options' ) ) { - return false; + return; } add_action( @@ -79,19 +80,21 @@ function() { Date: Thu, 22 Apr 2021 12:40:21 -0600 Subject: [PATCH 05/12] - remove old (renamed) class names --- src/class-acf.php | 160 ------- src/class-acfsettings.php | 264 ---------- src/js/main.js | 296 ------------ src/location-rules.php | 983 -------------------------------------- 4 files changed, 1703 deletions(-) delete mode 100644 src/class-acf.php delete mode 100644 src/class-acfsettings.php delete mode 100644 src/js/main.js delete mode 100644 src/location-rules.php diff --git a/src/class-acf.php b/src/class-acf.php deleted file mode 100644 index 2526a77..0000000 --- a/src/class-acf.php +++ /dev/null @@ -1,160 +0,0 @@ -setup_constants(); - self::$instance->includes(); - self::$instance->actions(); - self::$instance->filters(); - self::$instance->init(); - } - - /** - * Fire off init action - * - * @param ACF $instance The instance of the WPGraphQL\ACF class - */ - do_action( 'graphql_acf_init', self::$instance ); - - /** - * Return the WPGraphQL Instance - */ - return self::$instance; - } - - /** - * Throw error on object clone. - * The whole idea of the singleton design pattern is that there is a single object - * therefore, we don't want the object to be cloned. - * - * @access public - * @return void - */ - public function __clone() { - - // Cloning instances of the class is forbidden. - _doing_it_wrong( __FUNCTION__, esc_html__( 'The \WPGraphQL\ACF class should not be cloned.', 'wp-graphql-acf' ), '0.0.1' ); - - } - - /** - * Disable unserializing of the class. - * - * @access protected - * @return void - */ - public function __wakeup() { - - // De-serializing instances of the class is forbidden. - _doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the \WPGraphQL\ACF class is not allowed', 'wp-graphql-acf' ), '0.0.1' ); - - } - - /** - * Setup plugin constants. - * - * @access private - * @return void - */ - private function setup_constants() { - - // Plugin Folder Path. - if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_DIR' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_DIR', plugin_dir_path( __FILE__ . '/..' ) ); - } - - // Plugin Folder URL. - if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_URL' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_URL', plugin_dir_url( __FILE__ . '/..' ) ); - } - - // Plugin Root File. - if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_FILE' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_FILE', __FILE__ . '/..' ); - } - - } - - /** - * Include required files. - * Uses composer's autoload - * - * @access private - * @return void - */ - private function includes() { - - // Autoload Required Classes. - } - - /** - * Sets up actions to run at certain spots throughout WordPress and the WPGraphQL execution - * cycle - */ - private function actions() { - - } - - /** - * Setup filters - */ - private function filters() { - - /** - * This filters any field that returns the `ContentTemplate` type - * to pass the source node down to the template for added context - */ - add_filter( 'graphql_resolve_field', function( $result, $source, $args, $context, ResolveInfo $info, $type_name, $field_key, $field, $field_resolver ) { - if ( isset( $info->returnType ) && strtolower( 'ContentTemplate' ) === strtolower( $info->returnType ) ) { - if ( is_array( $result ) && ! isset( $result['node'] ) && ! empty( $source ) ) { - $result['node'] = $source; - } - } - return $result; - }, 10, 9 ); - - } - - /** - * Initialize - */ - private function init() { - - $registry = new Registry(); - add_action( 'graphql_register_types', [ $registry, 'init' ], 10, 1 ); - - $acf_settings = new ACF_Settings(); - $acf_settings->init(); - - } - -} diff --git a/src/class-acfsettings.php b/src/class-acfsettings.php deleted file mode 100644 index 7611ee4..0000000 --- a/src/class-acfsettings.php +++ /dev/null @@ -1,264 +0,0 @@ -determine_location_rules(); - $rules = $location_rules->get_rules(); - - $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; - $group_name = $location_rules->format_field_name( $group_name ); - - switch( $column_name ) { - case 'graphql_types': - echo isset( $rules[ $group_name ] ) ? implode( ', ', $rules[ $group_name ] ) : ''; - break; - case 'graphql_field_name': - echo isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : ''; - break; - default: - break; - } - - } - - public function admin_table_columns( $columns ) { - $columns['graphql_types'] = __( 'GraphQL Schema Location', 'wp-graphql-acf' ); - $columns['graphql_field_name'] = __( 'GraphQL Field Name', 'wp-graphql-acf' ); - return $columns; - - } - - /** - * Handle the AJAX callback for converting ACF Location settings to GraphQL Types - * - * @return void - */ - public function ajax_callback() { - - if ( isset( $_POST['data'] ) ) { - - $form_data = []; - - parse_str( $_POST['data'], $form_data ); - - if ( empty( $form_data ) || ! isset( $form_data['acf_field_group'] ) ) { - wp_send_json( __( 'No form data.', 'wp-graphql-acf' ) ); - } - - $field_group = isset( $form_data['acf_field_group'] ) ? $form_data['acf_field_group'] : []; - $rules = new LocationRules( [ $field_group ] ); - $rules->determine_location_rules(); - - $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; - $group_name = $rules->format_field_name( $group_name ); - - $all_rules = $rules->get_rules(); - if ( isset( $all_rules[ $group_name ] ) ) { - wp_send_json( [ 'graphql_types' => $all_rules[ $group_name ] ] ); - } - wp_send_json( [ 'graphql_types' => null ] ); - } - - echo __( 'No location rules were found', 'wp-graphql-acf' ); - wp_die(); - } - - /** - * Register the GraphQL Settings metabox for the ACF Field Group post type - * - * @return void - */ - public function register_meta_boxes() { - add_meta_box( 'wpgraphql-acf-meta-box', __( 'GraphQL', 'wp-graphql-acf' ), [ - $this, - 'display_metabox' - ], [ 'acf-field-group' ] ); - } - - /** - * Display the GraphQL Settings Metabox on the Field Group admin page - * - * @param $field_group_post_object - */ - public function display_metabox( $field_group_post_object ) { - - global $field_group; - - /** - * Render a field in the Field Group settings to allow for a Field Group to be shown in GraphQL. - */ - acf_render_field_wrap( - [ - 'label' => __( 'Show in GraphQL', 'acf' ), - 'instructions' => __( 'If the field group is active, and this is set to show, the fields in this group will be available in the WPGraphQL Schema based on the respective Location rules.' ), - 'type' => 'true_false', - 'name' => 'show_in_graphql', - 'prefix' => 'acf_field_group', - 'value' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : false, - 'ui' => 1, - ] - ); - - /** - * Render a field in the Field Group settings to set the GraphQL field name for the field group. - */ - acf_render_field_wrap( - [ - 'label' => __( 'GraphQL Field Name', 'acf' ), - 'instructions' => __( 'The name of the field group in the GraphQL Schema. Names should not include spaces or special characters. Best practice is to use "camelCase".', 'wp-graphql-acf' ), - 'type' => 'text', - 'prefix' => 'acf_field_group', - 'name' => 'graphql_field_name', - 'required' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : false, - 'placeholder' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null, - 'value' => ! empty( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : null, - ] - ); - - acf_render_field_wrap( - [ - 'label' => __( 'Manually Set GraphQL Types for Field Group', 'acf' ), - 'instructions' => __( 'By default, ACF Field groups are added to the GraphQL Schema based on the field group\'s location rules. Checking this box will let you manually control the GraphQL Types the field group should be shown on in the GraphQL Schema using the checkboxes below, and the Location Rules will no longer effect the GraphQL Types.', 'wp-graphql-acf' ), - 'type' => 'true_false', - 'name' => 'map_graphql_types_from_location_rules', - 'prefix' => 'acf_field_group', - 'value' => isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false, - 'ui' => 1, - ] - ); - - $choices = Config::get_all_graphql_types(); - acf_render_field_wrap( - [ - 'label' => __( 'GraphQL Types to Show the Field Group On', 'wp-graphql-acf' ), - 'instructions' => __( 'Select the Types in the WPGraphQL Schema to show the fields in this field group on', 'wp-graphql-acf' ), - 'type' => 'checkbox', - 'prefix' => 'acf_field_group', - 'name' => 'graphql_types', - 'value' => ! empty( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : [], - 'toggle' => true, - 'choices' => $choices, - ] - ); - - ?> -
- -
- - __( 'Show in GraphQL', 'wp-graphql-acf' ), - 'instructions' => __( 'Whether the field should be queryable via GraphQL', 'wp-graphql-acf' ), - 'name' => 'show_in_graphql', - 'type' => 'true_false', - 'ui' => 1, - 'default_value' => 1, - 'value' => isset( $field['show_in_graphql'] ) ? (bool) $field['show_in_graphql'] : true, - ], - true - ); - - } - - /** - * This enqueues admin script. - * - * @param string $screen The screen that scripts are being enqueued to - * - * @return void - */ - public function enqueue_graphql_acf_scripts( string $screen ) { - global $post; - - if ( $screen == 'post-new.php' || $screen == 'post.php' ) { - if ( 'acf-field-group' === $post->post_type ) { - wp_enqueue_script( 'graphql-acf', plugins_url( 'src/js/main.js', dirname( __FILE__ ) ), array( - 'jquery', - 'acf-input', - 'acf-field-group' - ) ); - } - } - } - -} diff --git a/src/js/main.js b/src/js/main.js deleted file mode 100644 index 50c14be..0000000 --- a/src/js/main.js +++ /dev/null @@ -1,296 +0,0 @@ -$j = jQuery.noConflict(); - -$j(document).ready(function () { - - var GraphqlLocationManager = new acf.Model({ - id: 'graphqlLocationManager', - wait: 'ready', - events: { - 'click .add-location-rule': 'onClickAddRule', - 'click .add-location-group': 'onClickAddGroup', - 'click .remove-location-rule': 'onClickRemoveRule', - 'change .refresh-location-rule': 'onChangeRemoveRule', - 'change .rule-groups .operator select': 'onChangeRemoveRule', - 'change .rule-groups .value select': 'onChangeRemoveRule', - }, - requestPending: false, - initialize: function () { - this.$el = $j('#acf-field-group-locations'); - this.getGraphqlTypes(); - }, - - onClickAddRule: function (e, $el) { - this.getGraphqlTypes(); - }, - - onClickRemoveRule: function (e, $el) { - this.getGraphqlTypes(); - }, - - onChangeRemoveRule: function (e, $el) { - setTimeout(function () { - GraphqlLocationManager.getGraphqlTypes(); - }, 500); - - }, - - onClickAddGroup: function (e, $el) { - this.getGraphqlTypes(); - }, - - isRequestPending: function() { - return this.requestPending; - }, - - startRequest: function () { - this.requestPending = true; - }, - - finishRequest: function() { - this.requestPending = false; - }, - - getGraphqlTypes: function () { - getGraphqlTypesFromLocationRules(); - }, - }); - - /** - * Set the visibility of the GraphQL Fields based on the `show_in_graphql` - * field state. - */ - function setGraphqlFieldVisibility() { - - var showInGraphQLCheckbox = $j('#acf_field_group-show_in_graphql'); - var graphqlFields = $j('#wpgraphql-acf-meta-box .acf-field'); - - graphqlFields.each(function (i, el) { - if ($j(this).attr('data-name') !== 'show_in_graphql') { - if (!showInGraphQLCheckbox.is(':checked')) { - $j(this).hide(); - } else { - $j(this).show(); - } - } - }); - - showInGraphQLCheckbox.on('change', function () { - setGraphqlFieldVisibility(); - getGraphqlTypesFromLocationRules(); - }) - - } - - function toggleFieldRequirement() { - - $j('#acf_field_group-show_in_graphql').on('change', function () { - var graphqlFieldNameWrap = $j('.acf-field[data-name="graphql_field_name"]'), - graphqlLabel = graphqlFieldNameWrap.find('label'), - graphqlInput = $j('#acf_field_group-graphql_field_name'); - - if ($j(this).is(':checked')) { - - // Add span.acf-required if necessary. - if (graphqlFieldNameWrap.find('.acf-required').length === 0) { - graphqlLabel.append('*'); - } - - // Toggle required attributes and visual features. - graphqlFieldNameWrap.addClass('is-required'); - graphqlLabel.find('.acf-required').show(); - graphqlInput.attr('required', true); - } else { - graphqlFieldNameWrap.removeClass('is-required'); - graphqlLabel.find('.acf-required').hide(); - graphqlInput.attr('required', false); - } - - }); - - } - - /** - * Listen to state changes for checkboxes for Interfaces and the checkboxes for - * Possible Types of the interfaces - */ - function initInterfaceCheckboxes() { - - // Find all the checkboxes for Interface types - $j('span[data-interface]').each(function (i, el) { - - // Get the interface name - let interfaceName = $j(el).data('interface'); - - // Get the checkbox for the interface - let interfaceCheckbox = $j('input[value="' + interfaceName + '"]'); - - // Find all checkboxes that implement the interface - let possibleTypesCheckboxes = $j('span[data-implements="' + interfaceName + '"]').siblings('input[type="checkbox"]'); - - // Prepend some space before to nest the Types beneath the Interface - possibleTypesCheckboxes.before("  "); - - // Listen for changes on the Interface checkbox - interfaceCheckbox.change(function () { - possibleTypesCheckboxes.prop('checked', $j(this).is(":checked")); - }) - - // Listen for changes to the checkboxes that implement the Interface - possibleTypesCheckboxes.change(function () { - - // Set the checked state of the Interface checkbox - if (!$j(this).is(":checked") && interfaceCheckbox.is(":checked")) { - interfaceCheckbox.prop("checked", false); - } - - // Set the state of the Implementing checkboxes - if ($j(possibleTypesCheckboxes).not(":checked").length === 0) { - interfaceCheckbox.prop("checked", true); - } - - }) - - }); - - } - - /** - * JavaScript version of the PHP lcfirst - * - * @param str - * @returns {string} - */ - function lcfirst(str) { - str += '' - const f = str.charAt(0) - .toLowerCase() - return f + str.substr(1) - } - - /** - * JavaScript version of the PHP ucwords - * - * @param str - * @returns {string} - */ - function ucwords(str) { - return str.toLowerCase().replace(/\b[a-z]/g, function (letter) { - return letter.toUpperCase(); - }) - } - - /** - * Based on the WPGraphQL format_field_name function - * - * See: https://github.com/wp-graphql/wp-graphql/blob/cc0b383259383898c3a1bebe65adf1140290b37e/src/Utils/Utils.php#L85-L100 - */ - function formatFieldName(fieldName) { - - fieldName.replace('[^a-zA-Z0-9 -]', '-'); - fieldName = lcfirst(fieldName); - fieldName = lcfirst(fieldName.split('-').join(' ')); - fieldName = ucwords(fieldName); - fieldName = lcfirst(fieldName.split(' ').join('')); - return fieldName; - - } - - /** - * Set the GraphQL Field Name value based on the Field Group Title - * if the graphql_field_name has not already been set. - */ - function setGraphqlFieldName() { - var graphqlFieldNameField = $j('#acf_field_group-graphql_field_name'); - var fieldGroupTitle = $j('#titlediv #title'); - if ('' === graphqlFieldNameField.val()) { - graphqlFieldNameField.val(formatFieldName(fieldGroupTitle.val())); - } - fieldGroupTitle.on('change', function () { - setGraphqlFieldName(); - }); - } - - - /** - * Determine whether users should be able to interact with the checkboxes - * to manually set the GraphQL Types for the ACF Field Group - */ - function graphqlMapTypesFromLocations() { - var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]'); - var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules'); - - if (manualMapTypes.not(':checked')) { - getGraphqlTypesFromLocationRules(); - } - - checkboxes.each(function (i, el) { - if (manualMapTypes.is(':checked')) { - $j(this).removeAttr("disabled"); - } else { - $j(this).attr("disabled", true); - } - }); - manualMapTypes.on('change', function () { - graphqlMapTypesFromLocations(); - }); - } - - function getGraphqlTypesFromLocationRules() { - - var showInGraphQLCheckbox = $j('#acf_field_group-show_in_graphql'); - var form = $j('#post'); - var formInputs = $j('#post :input'); - var serialized = formInputs.serialize(); - var checkboxes = $j('.acf-field[data-name="graphql_types"] .acf-checkbox-list input[type="checkbox"]'); - var manualMapTypes = $j('#acf_field_group-map_graphql_types_from_location_rules'); - - // If Manual Type selection is checked, - // Don't attempt to get GraphQL Types from the location rules - if (manualMapTypes.is(':checked')) { - return; - } - - if ( ! showInGraphQLCheckbox.is(':checked') ) { - return; - } - - if ( 'pending' !== form.attr('data-request-pending') ) { - - // Start the request - form.attr('data-request-pending', 'pending' ); - - // Make the request - $j.post(ajaxurl, { - action: 'get_acf_field_group_graphql_types', - data: serialized - }, function (res) { - var types = res && res['graphql_types'] ? res['graphql_types'] : []; - - checkboxes.each(function (i, el) { - var checkbox = $j(this); - var value = $j(this).val(); - checkbox.prop('checked', false); - if (types && types.length) { - if (-1 !== $j.inArray(value, types)) { - checkbox.prop('checked', true); - checkbox.trigger("change"); - } - } - }) - - // Signal that the request is finished - form.removeAttr('data-request-pending'); - - }); - } - - }; - - // Initialize the functionality to track the state of the Interface checkboxes. - initInterfaceCheckboxes(); - toggleFieldRequirement(); - setGraphqlFieldVisibility(); - setGraphqlFieldName(); - graphqlMapTypesFromLocations(); - -}); diff --git a/src/location-rules.php b/src/location-rules.php deleted file mode 100644 index 760b273..0000000 --- a/src/location-rules.php +++ /dev/null @@ -1,983 +0,0 @@ -acf_field_groups = isset( $acf_field_groups ) && ! empty( $acf_field_groups ) ? $acf_field_groups : acf_get_field_groups(); - } - - /** - * Given a field name, formats it for GraphQL - * - * @param string $field_name The field name to format - * - * @return string - */ - public function format_field_name( string $field_name ) { - - $replaced = preg_replace( '[^a-zA-Z0-9 -]', '_', $field_name ); - - // If any values were replaced, use the replaced string as the new field name - if ( ! empty( $replaced ) ) { - $field_name = $replaced; - } - - $field_name = lcfirst( $field_name ); - $field_name = lcfirst( str_replace( '-', ' ', ucwords( $field_name, '_' ) ) ); - $field_name = lcfirst( str_replace( ' ', '', ucwords( $field_name, ' ' ) ) ); - - return $field_name; - } - - /** - * Given a type name, formats it for GraphQL - * - * @param string $type_name The type name to format - * - * @return string - */ - public function format_type_name( string $type_name ) { - return ucfirst( $this->format_field_name( $type_name ) ); - } - - /** - * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this sets the - * field group to show in that Type - * - * @param string $field_group_name The name of the ACF Field Group - * @param string $graphql_type_name The name of the GraphQL Type - */ - public function set_graphql_type( string $field_group_name, string $graphql_type_name ) { - $this->mapped_field_groups[ Utils::format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name ); - } - - /** - * Given the name of a GraphqL Field Group and the name of a GraphQL Type, this unsets the - * GraphQL Type for the field group - * - * @param string $field_group_name The name of the ACF Field Group - * @param string $graphql_type_name The name of the GraphQL Type - */ - public function unset_graphql_type( string $field_group_name, string $graphql_type_name ) { - $this->unset_types[ $this->format_field_name( $field_group_name ) ][] = $this->format_type_name( $graphql_type_name ); - } - - /** - * Get the rules - * - * @return array - */ - public function get_rules() { - - $mapped_field_groups = isset( $this->mapped_field_groups ) && ! empty( $this->mapped_field_groups ) ? $this->mapped_field_groups : []; - - if ( empty( $mapped_field_groups ) ) { - return []; - } - - - if ( empty( $this->unset_types ) ) { - return $mapped_field_groups; - } - - /** - * Remove any Types that were flagged to unset - */ - foreach ( $this->unset_types as $field_group => $types ) { - if ( ! empty( $types ) ) { - foreach ( $types as $type ) { - if ( isset( $this->mapped_field_groups[ $field_group ] ) ) { - if ( ( $key = array_search( $type, $mapped_field_groups[ $field_group ] ) ) !== false ) { - unset( $mapped_field_groups[ $field_group ][ $key ] ); - } - } - } - } - } - - return $mapped_field_groups; - - } - - /** - * Checks for conflicting rule types to avoid impossible states. - * - * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag" - * this would be an impossible state, as an object can't be a Post and a Tag. - * - * If we detect conflicting rules, the rule set is not applied at all. - * - * @param array $and_params The parameters of the rule group - * @param mixed $param The current param being evaluated - * @param array $allowed_params The allowed params that shouldn't conflict - * - * @return bool - */ - public function check_for_conflicts( array $and_params, $param, $allowed_params = [] ) { - - if ( empty( $and_params ) ) { - return false; - } - - $has_conflict = false; - $keys = array_keys( $and_params, $param ); - - if ( isset( $keys[0] ) ) { - unset( $and_params[ $keys[0] ] ); - } - - if ( ! empty( $and_params ) ) { - foreach ( $and_params as $key => $and_param ) { - if ( false === array_search( $and_param, $allowed_params, true ) ) { - $has_conflict = true; - } - } - } - - return $has_conflict; - - } - - /** - * Checks for conflicting rule types to avoid impossible states. - * - * If a field group is assigned to a rule such as "post_type == post" AND "taxonomy == Tag" - * this would be an impossible state, as an object can't be a Post and a Tag. - * - * If we detect conflicting rules, the rule set is not applied at all. - * - * @param array $and_params The parameters of the rule group - * @param mixed $param The current param being evaluated - * - * @return bool - */ - public function check_params_for_conflicts( array $and_params = [], $param ) { - switch ( $param ) { - case 'post_type': - $allowed_and_params = [ - 'post_status', - 'post_format', - 'post_category', - 'post_taxonomy', - 'post', - ]; - break; - case 'post_template': - case 'page_template': - $allowed_and_params = [ - 'page_type', - 'page_parent', - 'page', - ]; - break; - case 'post_status': - $allowed_and_params = [ - 'post_type', - 'post_format', - 'post_category', - 'post_taxonomy', - ]; - break; - case 'post_format': - case 'post_category': - case 'post': - case 'post_taxonomy': - $allowed_and_params = [ - 'post_status', - 'post_type', - 'post_format', - 'post_category', - 'post_taxonomy', - 'post', - ]; - break; - case 'page': - case 'page_parent': - case 'page_type': - $allowed_and_params = [ - 'page_template', - 'page_type', - 'page_parent', - 'page', - ]; - break; - case 'current_user': - case 'current_user_role': - // @todo: - // Right now, if you set current_user or current_user_role as the only rule, - // ACF adds the field group to every possible location in the Admin. - // This seems a bit heavy handed. 🤔 - // We need to think through this a bit more, and how this rule - // Can be composed with other rules, etc. - $allowed_and_params = []; - break; - case 'user_form': - case 'user_role': - $allowed_and_params = [ - 'user_form', - 'user_role', - ]; - break; - case 'taxonomy': - case 'attachment': - case 'comment': - case 'widget': - case 'nav_menu': - case 'nav_menu_item': - case 'options_page': - default: - $allowed_and_params = []; - break; - - } - - return $this->check_for_conflicts( $and_params, $param, $allowed_and_params ); - - } - - /** - * Determine how an ACF Location Rule should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_rules( string $field_group_name, string $param, string $operator, string $value ) { - - // Depending on the param of the rule, there's different logic to - // map to the Schema - switch ( $param ) { - case 'post_type': - $this->determine_post_type_rules( $field_group_name, $param, $operator, $value ); - break; - case 'post_template': - case 'page_template': - $this->determine_post_template_rules( $field_group_name, $param, $operator, $value ); - break; - case 'post_status': - $this->determine_post_status_rules( $field_group_name, $param, $operator, $value ); - break; - case 'post_format': - case 'post_category': - case 'post_taxonomy': - $this->determine_post_taxonomy_rules( $field_group_name, $param, $operator, $value ); - break; - case 'post': - $this->determine_post_rules( $field_group_name, $param, $operator, $value ); - break; - case 'page_type': - $this->determine_page_type_rules( $field_group_name, $param, $operator, $value ); - break; - case 'page_parent': - case 'page': - // If page or page_parent is set, regardless of operator and value, - // we can add the field group to the Page type - $this->set_graphql_type( $field_group_name, 'Page' ); - break; - case 'current_user': - case 'current_user_role': - // @todo: - // Right now, if you set current_user or current_user_role as the only rule, - // ACF adds the field group to every possible location in the Admin. - // This seems a bit heavy handed. 🤔 - // We need to think through this a bit more, and how this rule - // Can be composed with other rules, etc. - break; - case 'user_form': - case 'user_role': - // If user_role or user_form params are set, we need to expose the field group - // to the User type - $this->set_graphql_type( $field_group_name, 'User' ); - break; - case 'taxonomy': - $this->determine_taxonomy_rules( $field_group_name, $param, $operator, $value ); - break; - case 'attachment': - $this->determine_attachment_rules( $field_group_name, $param, $operator, $value ); - break; - case 'comment': - $this->determine_comment_rules( $field_group_name, $param, $operator, $value ); - break; - case 'widget': - // @todo: Widgets are not currently supported in WPGraphQL - break; - case 'nav_menu': - $this->determine_nav_menu_rules( $field_group_name, $param, $operator, $value ); - break; - case 'nav_menu_item': - $this->determine_nav_menu_item_item_rules( $field_group_name, $param, $operator, $value ); - break; - case 'options_page': - $this->determine_options_rules( $field_group_name, $param, $operator, $value ); - break; - default: - // If a built-in location rule could not be matched, - // Custom rules (from extensions, etc) can hook in here and apply their - // rules to the WPGraphQL Schema - do_action( 'graphql_acf_match_location_rule', $field_group_name, $param, $operator, $value, $this ); - break; - - } - - } - - /** - * Determine GraphQL Schema location rules based on ACF Location rules for field groups - * that are configured with no `graphql_types` field. - * - * @return void - */ - public function determine_location_rules() { - - if ( ! empty( $this->acf_field_groups ) ) { - foreach ( $this->acf_field_groups as $field_group ) { - - $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; - - if ( ! isset( $field_group['active'] ) || false === (bool) $field_group['active'] ) { - continue; - } - - if ( ! empty( $field_group['location'] ) && is_array( $field_group['location'] ) ) { - - foreach ( $field_group['location'] as $location_rule_group ) { - if ( ! empty( $location_rule_group ) ) { - - foreach ( $location_rule_group as $group => $rule ) { - - // Determine the and params for the rule group - $and_params = wp_list_pluck( $location_rule_group, 'param' ); - $and_params = ! empty( $and_params ) ? array_values( $and_params ) : []; - - $operator = isset( $rule['operator'] ) ? $rule['operator'] : '=='; - $param = isset( $rule['param'] ) ? $rule['param'] : null; - $value = isset( $rule['value'] ) ? $rule['value'] : null; - - if ( empty( $param ) || empty( $value ) ) { - continue; - } - - if ( true === $this->check_params_for_conflicts( $and_params, $param ) ) { - continue; - } - - $this->determine_rules( $field_group_name, $param, $operator, $value ); - - } - } - } - } - } - } - - } - - /** - * Returns an array of Post Templates - * - * @return array - */ - public function get_graphql_post_template_types() { - - $registered_page_templates = wp_get_theme()->get_post_templates(); - - $page_templates['default'] = 'DefaultTemplate'; - - if ( ! empty( $registered_page_templates ) && is_array( $registered_page_templates ) ) { - - foreach ( $registered_page_templates as $post_type_templates ) { - // Post templates are returned as an array of arrays. PHPStan believes they're returned as - // an array of strings and believes this will always evaluate to false. - // We should ignore the phpstan check here. - // @phpstan-ignore-next-line - if ( ! empty( $post_type_templates ) && is_array( $post_type_templates ) ) { - foreach ( $post_type_templates as $file => $name ) { - - $name = ucwords( $name ); - $replaced_name = preg_replace( '/[^\w]/', '', $name ); - - if ( ! empty( $replaced_name ) ) { - $name = $replaced_name; - } - - if ( preg_match( '/^\d/', $name ) || false === strpos( strtolower( $name ), 'template' ) ) { - $name = 'Template_' . $name; - } - - $page_templates[ $file ] = $name; - } - } - } - } - - return $page_templates; - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_type_rules( string $field_group_name, string $param, string $operator, string $value ) { - $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); - - if ( empty( $allowed_post_types ) ) { - return; - } - - if ( '==' === $operator ) { - - // If all post types - if ( 'all' === $value ) { - - // loop over and set all post types - foreach ( $allowed_post_types as $allowed_post_type ) { - - $post_type_object = get_post_type_object( $allowed_post_type ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } else { - if ( in_array( $value, $allowed_post_types, true ) ) { - $post_type_object = get_post_type_object( $value ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - - } - - if ( '!=' === $operator ) { - - if ( 'all' !== $value ) { - // loop over and set all post types - foreach ( $allowed_post_types as $allowed_post_type ) { - $post_type_object = get_post_type_object( $allowed_post_type ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - $post_type_object = get_post_type_object( $value ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->unset_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_template_rules( string $field_group_name, string $param, string $operator, string $value ) { - - $templates = $this->get_graphql_post_template_types(); - - if ( ! is_array( $templates ) || empty( $templates ) ) { - return; - } - - if ( '==' === $operator ) { - - // If the template is available in GraphQL, set it - if ( isset( $templates[ $value ] ) ) { - $this->set_graphql_type( $field_group_name, $templates[ $value ] ); - } - } - - if ( '!=' === $operator ) { - - foreach ( $templates as $name => $template_type ) { - $this->set_graphql_type( $field_group_name, $template_type ); - } - - // If the Template is available in GraphQL, unset it - if ( isset( $templates[ $value ] ) ) { - $this->unset_graphql_type( $field_group_name, $templates[ $value ] ); - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_status_rules( string $field_group_name, string $param, string $operator, string $value ) { - // @todo: Should post status affect the GraphQL Schema at all? - // If a field group is set to show on "post_status == publish" as the only rule, what post type does that apply to? All? 🤔 - // If a field group is set to show on "post_status != draft" does that mean the field group should be available on all post types in the Schema by default? - // This seems like a very difficult rule to translate to the Schema. - // Like, lets say I add a field group called: "Editor Notes" that I want to show for any status that is not "publish". In theory, if that's my only rule, that seems like it should apply to all post types across the board, and show in the Admin in any state of the post, other than publish. 🤔 - - // ACF Admin behavior seems to add it to the Admin on all post types, so WPGraphQL - // should respect this rule and also add it to all post types. The resolver should - // then determine whether to resolve the data or not, based on this rule. - - // If Post Status is used to qualify a field group location, - // It will be added to the Schema for any Post Type that is set to show in GraphQL - $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); - foreach ( $allowed_post_types as $post_type ) { - - $post_type_object = get_post_type_object( $post_type ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_format_rules( string $field_group_name, string $param, string $operator, string $value ) { - - $post_format_taxonomy = get_taxonomy( 'post_format' ); - $post_format_post_types = $post_format_taxonomy->object_type; - - if ( ! is_array( $post_format_post_types ) || empty( $post_format_post_types ) ) { - return; - } - - // If Post Format is used to qualify a field group location, - // It will be added to the Schema for any Post Type that supports post formats - // And shows in GraphQL - $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); - foreach ( $allowed_post_types as $post_type ) { - if ( in_array( $post_type, $post_format_post_types, true ) ) { - $post_type_object = get_post_type_object( $value ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) { - - // If Post Taxonomy is used to qualify a field group location, - // It will be added to the Schema for the Post post type - $this->set_graphql_type( $field_group_name, 'Post' ); - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_post_rules( string $field_group_name, string $param, string $operator, string $value ) { - - // If a Single post is used to qualify a field group location, - // It will be added to the Schema for the GraphQL Type for the post_type of the Post - // it is assigned to - - if ( '==' === $operator ) { - - if ( absint( $value ) ) { - $post = get_post( absint( $value ) ); - if ( $post instanceof \WP_Post ) { - $post_type_object = get_post_type_object( $post->post_type ); - if ( isset( $post_type_object->show_in_graphql ) && true === $post_type_object->show_in_graphql ) { - if ( isset( $post_type_object->graphql_single_name ) ) { - $this->set_graphql_type( $field_group_name, $post_type_object->graphql_single_name ); - } - } - } - } - - } - - // If a single post is used as not equal, - // the field group should be added to ALL post types in the Schema - if ( '!=' === $operator ) { - - $allowed_post_types = get_post_types( [ 'show_in_graphql' => true ] ); - - if ( empty( $allowed_post_types ) ) { - return; - } - - // loop over and set all post types - foreach ( $allowed_post_types as $allowed_post_type ) { - - $post_type_object = get_post_type_object( $allowed_post_type ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_page_type_rules( string $field_group_name, string $param, string $operator, string $value ) { - - // If front_page or posts_page is set to equal_to or not_equal_to - // then the field group should be shown on the Post type - if ( in_array( $value, [ 'front_page', 'posts_page' ], true ) ) { - $this->set_graphql_type( $field_group_name, 'Page' ); - } - - // If top_level, parent, or child is set as equal_to or not_equal_to - // then the field group should be shown on all hierarchical post types - if ( in_array( $value, [ 'top_level', 'parent', 'child' ], true ) ) { - - $hierarchical_post_types = get_post_types( [ - 'show_in_graphql' => true, - 'hierarchical' => true - ] ); - - if ( empty( $hierarchical_post_types ) ) { - return; - } - - // loop over and set all post types - foreach ( $hierarchical_post_types as $allowed_post_type ) { - - $post_type_object = get_post_type_object( $allowed_post_type ); - $graphql_name = isset( $post_type_object->graphql_single_name ) ? $post_type_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_taxonomy_rules( string $field_group_name, string $param, string $operator, string $value ) { - - $allowed_taxonomies = get_taxonomies( [ 'show_in_graphql' => true ] ); - - if ( empty( $allowed_taxonomies ) ) { - return; - } - - if ( '==' === $operator ) { - - // If all post types - if ( 'all' === $value ) { - - // loop over and set all post types - foreach ( $allowed_taxonomies as $allowed_taxonomy ) { - - $tax_object = get_taxonomy( $allowed_taxonomy ); - $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - - } else { - if ( in_array( $value, $allowed_taxonomies, true ) ) { - $tax_object = get_taxonomy( $value ); - $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - } - - - } - - if ( '!=' === $operator ) { - - if ( 'all' !== $value ) { - - // loop over and set all post types - foreach ( $allowed_taxonomies as $allowed_taxonomy ) { - - $tax_object = get_taxonomy( $allowed_taxonomy ); - $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->set_graphql_type( $field_group_name, $graphql_name ); - } - } - - $tax_object = get_taxonomy( $value ); - $graphql_name = isset( $tax_object->graphql_single_name ) ? $tax_object->graphql_single_name : null; - if ( ! empty( $graphql_name ) ) { - $this->unset_graphql_type( $field_group_name, $graphql_name ); - } - - } - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_attachment_rules( string $field_group_name, string $param, string $operator, string $value ) { - - if ( '==' === $operator ) { - $this->set_graphql_type( $field_group_name, 'MediaItem' ); - } - - if ( '!=' === $operator && 'all' === $value ) { - $this->unset_graphql_type( $field_group_name, 'MediaItem' ); - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_comment_rules( string $field_group_name, string $param, string $operator, string $value ) { - - if ( '==' === $operator ) { - $this->set_graphql_type( $field_group_name, 'Comment' ); - } - - if ( '!=' === $operator ) { - - // If not equal to all, unset from all comments - if ( 'all' === $value ) { - $this->unset_graphql_type( $field_group_name, 'Comment' ); - - // If not equal to just a specific post type/comment relationship, - // show the field group on the Comment Type - } else { - $this->set_graphql_type( $field_group_name, 'Comment' ); - } - - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_nav_menu_rules( string $field_group_name, string $param, string $operator, string $value ) { - - if ( '==' === $operator ) { - $this->set_graphql_type( $field_group_name, 'Menu' ); - } - - if ( '!=' === $operator ) { - - // If not equal to all, unset from all Menu - if ( 'all' === $value ) { - $this->unset_graphql_type( $field_group_name, 'Menu' ); - - // If not equal to just a Menu, - // show the field group on all Menus - } else { - $this->set_graphql_type( $field_group_name, 'Menu' ); - } - - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_nav_menu_item_item_rules( string $field_group_name, string $param, string $operator, string $value ) { - - if ( '==' === $operator ) { - $this->set_graphql_type( $field_group_name, 'MenuItem' ); - } - - if ( '!=' === $operator ) { - - // If not equal to all, unset from all MenuItem - if ( 'all' === $value ) { - $this->unset_graphql_type( $field_group_name, 'MenuItem' ); - - // If not equal to one Menu / location, - // show the field group on all MenuItems - } else { - $this->set_graphql_type( $field_group_name, 'MenuItem' ); - } - - } - - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_block_rules( string $field_group_name, string $param, string $operator, string $value ) { - // @todo: ACF Blocks are not formally supported by WPGraphQL / WPGraphQL for ACF. More to come in the future! - } - - /** - * Determines how the ACF Rules should apply to the WPGraphQL Schema - * - * @param string $field_group_name The name of the ACF Field Group the rule applies to - * @param string $param The parameter of the rule - * @param string $operator The operator of the rule - * @param string $value The value of the rule - */ - public function determine_options_rules( string $field_group_name, string $param, string $operator, string $value ) { - - if ( '==' === $operator ) { - $options_page = acf_get_options_page( $value ); - - if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { - return; - } - - $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - - $this->set_graphql_type( $field_group_name, $type_name ); - } - - if ( '!=' === $operator ) { - - $options_pages = acf_get_options_pages(); - - if ( empty( $options_pages ) || ! is_array( $options_pages ) ) { - return; - } - - // Show all options pages - foreach ( $options_pages as $options_page ) { - if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { - continue; - } - $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - $this->set_graphql_type( $field_group_name, $type_name ); - } - - // Get the options page to unset - $options_page = acf_get_options_page( $value ); - if ( ! isset( $options_page['show_in_graphql'] ) || false === $options_page['show_in_graphql'] ) { - return; - } - $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - $this->unset_graphql_type( $field_group_name, $type_name ); - } - - } - -} From 4624f78162750c6cb45882662f0c9eb733eb7105 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Fri, 14 May 2021 15:08:25 -0600 Subject: [PATCH 06/12] - Update test for gallery to reflect proper order - add test for taxonomy field in repeaters - add test for repeater field with no values saved --- src/Fields/Gallery.php | 1 + src/Fields/PostObject.php | 1 + src/Fields/Repeater.php | 29 +++- src/Fields/Taxonomy.php | 1 + src/Fields/User.php | 1 + tests/wpunit/PostObjectFieldsTest.php | 241 +++++++++++++++++++++++++- vendor/composer/InstalledVersions.php | 12 +- vendor/composer/installed.php | 12 +- 8 files changed, 284 insertions(+), 14 deletions(-) diff --git a/src/Fields/Gallery.php b/src/Fields/Gallery.php index cb0b15e..9d64f06 100644 --- a/src/Fields/Gallery.php +++ b/src/Fields/Gallery.php @@ -31,6 +31,7 @@ public function get_graphql_type() { $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'attachment' ); return $resolver ->set_query_arg( 'post__in', $value ) + ->set_query_arg( 'orderby', 'post__in' ) ->get_connection(); } ]); diff --git a/src/Fields/PostObject.php b/src/Fields/PostObject.php index a8354db..49ab6d3 100644 --- a/src/Fields/PostObject.php +++ b/src/Fields/PostObject.php @@ -35,6 +35,7 @@ public function get_graphql_type() { $resolver = new PostObjectConnectionResolver( $root, $args, $context, $info, 'any' ); return $resolver ->set_query_arg( 'post__in', $value ) + ->set_query_arg( 'orderby', 'post__in' ) ->get_connection(); } ]; diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php index 9ace07c..f544e8b 100644 --- a/src/Fields/Repeater.php +++ b/src/Fields/Repeater.php @@ -1,16 +1,43 @@ get_parent_type(); $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); - return ! empty( $type_name ) ? [ 'list_of' => $type_name ] : null; + return ! empty( $type_name ) ? [ 'non_null' => [ 'list_of' => $type_name ] ] : null; + } + + /** + * @param mixed $node The parent node the repeater field belongs to + * @param array $args The args passed to the field + * @param AppContext $context The AppContext passed down the resolve tree + * @param ResolveInfo $info The ResolveInfo for the field + * + * @return array + */ + public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { + $value = parent::resolve( $node, $args, $context, $info ); + return ! empty( $value ) && is_array( $value ) ? $value : []; } } diff --git a/src/Fields/Taxonomy.php b/src/Fields/Taxonomy.php index e1ef95b..98934b3 100644 --- a/src/Fields/Taxonomy.php +++ b/src/Fields/Taxonomy.php @@ -42,6 +42,7 @@ public function get_graphql_type() { $resolver = new TermObjectConnectionResolver( $root, $args, $context, $info ); return $resolver ->set_query_arg( 'include', $value ) + ->set_query_arg( 'orderby', 'include' ) ->get_connection(); } ]; diff --git a/src/Fields/User.php b/src/Fields/User.php index 80edebe..5111505 100644 --- a/src/Fields/User.php +++ b/src/Fields/User.php @@ -40,6 +40,7 @@ public function get_graphql_type() { $resolver = new UserConnectionResolver( $root, $args, $context, $info ); return $resolver ->set_query_arg( 'include', $value ) + ->set_query_arg( 'orderby', 'include' ) ->get_connection(); } ]; diff --git a/tests/wpunit/PostObjectFieldsTest.php b/tests/wpunit/PostObjectFieldsTest.php index db2a520..8208583 100644 --- a/tests/wpunit/PostObjectFieldsTest.php +++ b/tests/wpunit/PostObjectFieldsTest.php @@ -625,6 +625,30 @@ public function testQueryGalleryField() { codecept_debug( $actual ); + $this->assertArrayNotHasKey( 'errors', $actual ); + $this->assertSame( [ + [ + 'mediaItemId' => $img_id_1, + 'sourceUrl' => wp_get_attachment_image_src( $img_id_1, 'full' )[0] + ], + [ + 'mediaItemId' => $img_id_2, + 'sourceUrl' => wp_get_attachment_image_src( $img_id_2, 'full' )[0] + ], + ], $actual['data']['postBy']['postFields']['galleryField']['nodes'] ); + + $img_ids = [ $img_id_2, $img_id_1 ]; + update_field( 'gallery_field', $img_ids, $this->post_id ); + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'postId' => $this->post_id, + ], + ]); + + codecept_debug( $actual ); + $this->assertArrayNotHasKey( 'errors', $actual ); $this->assertSame( [ [ @@ -634,7 +658,7 @@ public function testQueryGalleryField() { [ 'mediaItemId' => $img_id_1, 'sourceUrl' => wp_get_attachment_image_src( $img_id_1, 'full' )[0] - ] + ], ], $actual['data']['postBy']['postFields']['galleryField']['nodes'] ); @@ -1471,6 +1495,221 @@ public function test_relationship_field_with_draft_post_doesnt_cause_error() { } + public function test_taxonomy_field_in_repeater_returns_terms() { + + $this->register_acf_field([ + 'type' => 'repeater', + 'name' => 'repeater_field', + 'post_type' => [ + 'post', + ], + 'sub_fields' => [ + [ + 'key' => 'field_609d76ed7dc3e', + 'label' => 'category', + 'name' => 'category', + 'type' => 'taxonomy', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'taxonomy' => 'category', + 'field_type' => 'checkbox', + 'add_term' => 1, + 'save_terms' => 0, + 'load_terms' => 0, + 'return_format' => 'id', + 'multiple' => 0, + 'allow_null' => 0, + ], + ], + ]); + + $category_1 = $this->factory()->category->create([ + 'name' => 'test one' + ]); + $category_2 = $this->factory()->category->create([ + 'name' => 'test two', + 'parent' => $category_1, + ]); + + update_field( 'repeater_field', [ + [ + 'field_609d76ed7dc3e' => [ $category_1 ] + ], + [ + 'field_609d76ed7dc3e' => [ $category_2, $category_1 ] + ], + [ + 'field_609d76ed7dc3e' => [ $category_2 ] + ] + ], $this->post_id ); + + codecept_debug( get_post_custom( $this->post_id ) ); + + $query = ' + query GET_POST_WITH_ACF_FIELD( $postId: Int! ) { + postBy( postId: $postId ) { + id + title + postFields { + repeaterField { + category { + nodes { + __typename + databaseId + } + } + } + } + } + }'; + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'postId' => $this->post_id, + ] + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + + // First repeater has just the parent category + $this->assertSame( [ + [ + '__typename' => 'Category', + 'databaseId' => $category_1, + ], + ], $actual['data']['postBy']['postFields']['repeaterField'][0]['category']['nodes'] ); + + // Next repeater has parent and child category, ordered with the child first, parent 2nd + $this->assertSame( [ + [ + '__typename' => 'Category', + 'databaseId' => $category_2, + ], + [ + '__typename' => 'Category', + 'databaseId' => $category_1, + ], + ], $actual['data']['postBy']['postFields']['repeaterField'][1]['category']['nodes'] ); + + // Next repeater has just child category + $this->assertSame( [ + [ + '__typename' => 'Category', + 'databaseId' => $category_2, + ], + ], $actual['data']['postBy']['postFields']['repeaterField'][2]['category']['nodes'] ); + + + } + + public function test_repeater_field_with_no_values_returns_empty_array() { + + $this->register_acf_field([ + 'type' => 'repeater', + 'name' => 'repeater_field', + 'post_type' => [ + 'post', + ], + 'sub_fields' => [ + [ + 'key' => 'field_609d76ed7dc3e', + 'label' => 'text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'add_term' => 1, + 'save_terms' => 0, + 'load_terms' => 0, + 'multiple' => 0, + 'allow_null' => 0, + ], + ], + ]); + + $query = ' + query GET_POST_WITH_ACF_FIELD( $postId: Int! ) { + postBy( postId: $postId ) { + id + title + postFields { + repeaterField { + text + } + } + } + }'; + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'postId' => $this->post_id, + ] + ]); + + $this->assertArrayNotHasKey( 'errors', $actual ); + + $this->assertEmpty( $actual['data']['postBy']['postFields']['repeaterField'] ); + + update_field( 'repeater_field', [ + [ + 'field_609d76ed7dc3e' => 'text one' + ], + [ + 'field_609d76ed7dc3e' => 'text two' + ], + [ + 'field_609d76ed7dc3e' => 'text three' + ] + ], $this->post_id ); + + $query = ' + query GET_POST_WITH_ACF_FIELD( $postId: Int! ) { + postBy( postId: $postId ) { + id + title + postFields { + repeaterField { + text + } + } + } + }'; + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'postId' => $this->post_id, + ] + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + + $this->assertSame( 'text one', $actual['data']['postBy']['postFields']['repeaterField'][0]['text'] ); + $this->assertSame( 'text two', $actual['data']['postBy']['postFields']['repeaterField'][1]['text'] ); + $this->assertSame( 'text three', $actual['data']['postBy']['postFields']['repeaterField'][2]['text'] ); + + } + public function test_flex_field_preview() { // @todo: test that previewing flex fields work } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 48ee62a..bc12c14 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -25,24 +25,24 @@ class InstalledVersions private static $installed = array ( 'root' => array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', 'aliases' => array ( ), - 'reference' => '2542f2e4eeed7c527d374050c37ccced5ceb16b8', + 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => array ( 'wp-graphql/wp-graphql-acf' => array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', 'aliases' => array ( ), - 'reference' => '2542f2e4eeed7c527d374050c37ccced5ceb16b8', + 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', ), ), ); diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 0c0194f..f0c58ce 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -1,24 +1,24 @@ array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', 'aliases' => array ( ), - 'reference' => '2542f2e4eeed7c527d374050c37ccced5ceb16b8', + 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => array ( 'wp-graphql/wp-graphql-acf' => array ( - 'pretty_version' => 'dev-master', - 'version' => 'dev-master', + 'pretty_version' => 'dev-develop', + 'version' => 'dev-develop', 'aliases' => array ( ), - 'reference' => '2542f2e4eeed7c527d374050c37ccced5ceb16b8', + 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', ), ), ); From b68f991196c1e9b6519c0e0319f2909821920a53 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Mon, 17 May 2021 16:24:12 -0600 Subject: [PATCH 07/12] - Fixes bug where querying a flex field with no data throws errors --- src/Fields/FlexibleContent.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Fields/FlexibleContent.php b/src/Fields/FlexibleContent.php index 18fcf12..cad0372 100644 --- a/src/Fields/FlexibleContent.php +++ b/src/Fields/FlexibleContent.php @@ -2,6 +2,8 @@ namespace WPGraphQL\ACF\Fields; use Exception; +use GraphQL\Type\Definition\ResolveInfo; +use WPGraphQL\AppContext; use WPGraphQL\Utils\Utils; /** @@ -122,4 +124,9 @@ public function get_graphql_type() { return [ 'list_of' => $layout_interface_name ]; } + public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { + $value = parent::resolve( $node, $args, $context, $info ); + return ! empty( $value ) && is_array( $value ) ? $value : []; + } + } From fcb8eff327217940b33752a4efe12c7b066da7eb Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Thu, 27 May 2021 11:22:09 -0600 Subject: [PATCH 08/12] - refactor registry and fields. - all tests passing. . .still need to work on Clone fields, Flex Fields, and re-usable interfaces/fragments. . . and need to clean up code quite a bit --- src/Fields/AcfField.php | 35 +- src/Fields/File.php | 12 +- src/Fields/FlexibleContent.php | 196 +++++----- src/Fields/Gallery.php | 8 + src/Fields/Group.php | 5 - src/Fields/PostObject.php | 12 +- src/Fields/Relationship.php | 10 +- src/Fields/Repeater.php | 10 +- src/Fields/Select.php | 25 +- src/Fields/Taxonomy.php | 11 +- src/Fields/User.php | 11 +- src/Registry.php | 498 +++++++++++++++++++------- tests/wpunit/ClonedFieldsTest.php | 339 ++++++++++++++++++ tests/wpunit/FieldGroupTest.php | 114 ++++++ tests/wpunit/FlexFieldsTest.php | 118 ++++++ tests/wpunit/PostObjectFieldsTest.php | 111 ++++-- vendor/composer/InstalledVersions.php | 4 +- vendor/composer/installed.php | 4 +- wp-graphql-acf.php | 1 - 19 files changed, 1259 insertions(+), 265 deletions(-) create mode 100644 tests/wpunit/ClonedFieldsTest.php create mode 100644 tests/wpunit/FieldGroupTest.php create mode 100644 tests/wpunit/FlexFieldsTest.php diff --git a/src/Fields/AcfField.php b/src/Fields/AcfField.php index e6d571e..612be15 100644 --- a/src/Fields/AcfField.php +++ b/src/Fields/AcfField.php @@ -72,6 +72,16 @@ public function __construct( array $field, array $field_group, Registry $registr $this->should_format = false; $this->field_name = isset( $this->field_config['graphql_field_name'] ) ? Utils::format_field_name( $this->field_config['graphql_field_name'] ) : Utils::format_field_name( $this->field_config['name'] ); $this->field_type = $this->field_config['type']; + return $this; + } + + /** + * Get the field name for the GraphQL Field mapped to the schema + * + * @return string + */ + public function get_field_name() { + return $this->field_name; } /** @@ -107,8 +117,8 @@ public function get_acf_node_id( $node, array $acf_field ) { case $node instanceof Comment: $id = 'comment_' . absint( $node->databaseId ); break; - case is_array( $node ) && isset ( $node['node']['post_id'] ) && 'options' === $node['node']['post_id']: - $id = $node['node']['post_id']; + case is_array( $node ) && isset ( $node['post_id'] ) && 'options' === $node['post_id']: + $id = $node['post_id']; break; default: $id = 0; @@ -249,7 +259,7 @@ public function resolve( $node, array $args, AppContext $context, ResolveInfo $i /** * Check if cloned field and retrieve the key accordingly. */ - if ( ! empty( $acf_field['_clone'] ) ) { + if ( ! empty( $this->field_config['_clone'] ) ) { $key = $this->field_config['__key']; } else { $key = $this->field_config['key']; @@ -317,28 +327,31 @@ public function prepare_acf_field_value( $value, $node, $node_id ) { } } + if ( in_array( $this->field_type, [ 'number', 'range' ], true ) ) { + return (float) $value ?: null; + } + return $value; } /** * Registers a field to the WPGraphQL Schema * - * @return void + * @return array */ - public function register_field() { - - $type_name = $this->registry->get_field_group_type_name( $this->field_group ); - $graphql_field_name = Utils::format_field_name( $this->field_name ); + public function get_graphql_field_config() { if ( ! empty( $this->get_graphql_type() ) ) { - register_graphql_field( $type_name, $graphql_field_name, [ + return [ 'type' => $this->get_graphql_type(), - 'resolve' => function( $source, $args, $context, $info ) use ( $graphql_field_name ) { + 'resolve' => function( $source, $args, $context, $info ) { return $this->resolve( $source, $args, $context, $info ); } - ] ); + ]; } + + return null; } } diff --git a/src/Fields/File.php b/src/Fields/File.php index 50f1fb2..fe63041 100644 --- a/src/Fields/File.php +++ b/src/Fields/File.php @@ -5,12 +5,17 @@ use WPGraphQL\AppContext; use WPGraphQL\Data\Connection\PostObjectConnectionResolver; +/** + * Class File + * + * @package WPGraphQL\ACF\Fields + */ class File extends AcfField { /** * Registers a GraphQL connection instead of returning a scalar Type * - * @return null + * @return string * @throws Exception */ public function get_graphql_type() { @@ -21,6 +26,11 @@ public function get_graphql_type() { $connection_name = ucfirst( $type_name ) . 'ToSingleMediaItemConnection'; + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return $connection_name; + } + $type_registry->register_connection([ 'fromType' => $type_name, 'toType' => 'MediaItem', diff --git a/src/Fields/FlexibleContent.php b/src/Fields/FlexibleContent.php index cad0372..d0f24d7 100644 --- a/src/Fields/FlexibleContent.php +++ b/src/Fields/FlexibleContent.php @@ -21,7 +21,18 @@ class FlexibleContent extends AcfField { */ public function get_graphql_type() { - $layouts = isset( $this->field_config['layouts'] ) ? $this->field_config['layouts'] : []; + $layouts = []; + + if ( isset( $this->field_config['layouts'] ) && is_array( $this->field_config['layouts'] ) ) { + $layouts = array_map( function( $layout ) { + $layout['parent'] = $this->field_config['key']; + $graphql_field_name = isset( $layout['graphql_field_name'] ) ? $layout['graphql_field_name'] : $layout['name']; + $graphql_field_name = $this->get_parent_type() . '_' . Utils::format_type_name( $this->field_name ) . '_' . Utils::format_type_name( $graphql_field_name ); + $layout['graphql_field_name'] = $graphql_field_name; + $layout['graphql_types'] = [ $this->get_parent_type() ]; + return $layout; + }, $this->field_config['layouts'] ); + } if ( empty( $layouts ) ) { return null; @@ -31,95 +42,114 @@ public function get_graphql_type() { $type_name = $parent_type . '_' . Utils::format_type_name( $this->field_config['name'] ); $layout_interface_name = $type_name . '_Layout'; - if ( null === $this->registry->get_type_registry()->get_type( $layout_interface_name ) ) { - - $this->registry->get_type_registry()->register_interface_type( $layout_interface_name, [ - 'description' => sprintf( __( 'Layouts of the %s Flexible Field Type', 'wp-graphql-acf' ), $layout_interface_name ), - 'fields' => [ - 'layoutName' => [ - 'type' => 'String', - 'description' => __( 'The name of the flexibile field layout', 'wp-graphql-acf' ), - ], + register_graphql_interface_type( $layout_interface_name, [ + 'description' => sprintf( __( 'Layouts of the %s Flexible Field Type', 'wp-graphql-acf' ), $layout_interface_name ), + 'fields' => [ + 'layoutName' => [ + 'type' => 'String', + 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), ], - 'resolveType' => function( $object ) use ( $layouts, $type_name ) { - return $type_name . '_' . Utils::format_type_name( $object['acf_fc_layout'] ); - } - ] ); - - } - - // Get the raw fields for the field group so we can - // determine which fields are clones and which fields are not - $raw_fields = acf_get_raw_fields( $this->field_config['key'] ); - $layout_type_names = []; - - /** - * Iterate over the layouts to determine their GraphQL Type - */ - foreach ( $layouts as $layout ) { - - $cloned = false; - - if ( ! isset( $layout['name'] ) ) { - continue; + ], + 'resolveType' => function( $object ) use ( $layouts, $type_name ) { + return $type_name . '_' . Utils::format_type_name( $object['acf_fc_layout'] ); } + ] ); - foreach ( $raw_fields as $raw_field ) { - if ( $layout['key'] === $raw_field['parent_layout'] ) { - $layout['raw_fields'][] = $raw_field; - if ( isset( $raw_field['clone'] ) && is_array( $raw_field['clone'] ) && 1 === count( $raw_field['clone'] ) ) { - - if ( 'Hero' === $raw_field['label'] ) { - - if ( false !== strpos( $raw_field['clone'][0], 'group_' ) ) { - $cloned_group = acf_get_field_group( $raw_field['clone'][0] ); - if ( is_array( $cloned_group ) && ! empty( $cloned_group ) ) { - $cloned = true; - $layout_type_names[] = $this->registry->get_field_group_type_name( $cloned_group ); - } - } - - } - } - } - } - if ( true !== $cloned ) { +// wp_send_json( [ $this->field_config, $this->field_group, $layouts ]); - $layout_type_name = $type_name . '_' . Utils::format_type_name( $layout['name'] ); - $layout['title'] = $layout['label']; - $layout['graphql_field_name'] = $layout_type_name; - - - if ( null === $this->registry->get_type_registry()->get_type( $layout_type_name ) ) { - - $this->registry->get_type_registry()->register_object_type( $layout_type_name, [ - 'description' => sprintf( __( '%s Flexible Field Layout', 'wp-graphql' ), $layout_type_name ), - 'interfaces' => [ 'AcfFieldGroup', $layout_interface_name ], - 'fields' => [ - 'layoutName' => [ - 'type' => 'String', - 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), - 'resolve' => function() use ( $layout ) { - return isset( $layout['label'] ) ? $layout['label'] : null; - } - ], - ] - ] ); - - } - - $layout_type_names[] = $layout_type_name; - - } - - if ( ! empty( $layout_type_names ) ) { - register_graphql_interfaces_to_types( $layout_interface_name, $layout_type_names ); - } + $registered = []; + foreach ( $layouts as $layout ) { + $registered[] = $this->registry->add_acf_field_group_to_graphql( $layout, [ $parent_type ], [ $layout_interface_name ] ); + } - $this->registry->map_acf_fields_to_field_group( $layout ); - } +// if ( ! empty( $registered ) ) { +// register_graphql_interfaces_to_types( $layout_interface_name, $registered ); +// } + + // wp_send_json( [ $registered, $layout_interface_name, $this->registry->get_type_registry()->get_type( $registered[0] )->config ]); + +// wp_send_json( $registered_types ); + +// +// wp_send_json( [ 'list_of' => $registered[0] ] ); + + return [ 'list_of' => $registered[0] ]; + +// // Get the raw fields for the field group so we can +// // determine which fields are clones and which fields are not +// $raw_fields = acf_get_raw_fields( $this->field_config['key'] ); +// $layout_type_names = []; +// wp_send_json( $layouts ); +// +// /** +// * Iterate over the layouts to determine their GraphQL Type +// */ +// foreach ( $layouts as $layout ) { +// +// $cloned = false; +// +// if ( ! isset( $layout['name'] ) ) { +// continue; +// } +// +// foreach ( $raw_fields as $raw_field ) { +// if ( $layout['key'] === $raw_field['parent_layout'] ) { +// $layout['raw_fields'][] = $raw_field; +// if ( isset( $raw_field['clone'] ) && is_array( $raw_field['clone'] ) && 1 === count( $raw_field['clone'] ) ) { +// +// if ( 'Hero' === $raw_field['label'] ) { +// +// if ( false !== strpos( $raw_field['clone'][0], 'group_' ) ) { +// $cloned_group = acf_get_field_group( $raw_field['clone'][0] ); +// if ( is_array( $cloned_group ) && ! empty( $cloned_group ) ) { +// $cloned = true; +// $layout_type_names[] = $this->registry->get_field_group_type_name( $cloned_group ); +// } +// } +// +// } +// } +// } +// } +// +// if ( true !== $cloned ) { +// +// $layout_type_name = $type_name . '_' . Utils::format_type_name( $layout['name'] ); +// $layout['title'] = $layout['label']; +// $layout['graphql_field_name'] = $layout_type_name; +// +// +// if ( null === $this->registry->get_type_registry()->get_type( $layout_type_name ) ) { +// +// $this->registry->get_type_registry()->register_object_type( $layout_type_name, [ +// 'description' => sprintf( __( '%s Flexible Field Layout', 'wp-graphql' ), $layout_type_name ), +// 'interfaces' => [ 'AcfFieldGroup', $layout_interface_name ], +// 'fields' => [ +// 'layoutName' => [ +// 'type' => 'String', +// 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), +// 'resolve' => function() use ( $layout ) { +// return isset( $layout['label'] ) ? $layout['label'] : null; +// } +// ], +// ] +// ] ); +// +// } +// +// $layout_type_names[] = $layout_type_name; +// +// } +// +// if ( ! empty( $layout_type_names ) ) { +// register_graphql_interfaces_to_types( $layout_interface_name, $layout_type_names ); +// } +// +// $this->registry->map_acf_fields_to_field_group( $layout ); +// +// } return [ 'list_of' => $layout_interface_name ]; } diff --git a/src/Fields/Gallery.php b/src/Fields/Gallery.php index 9d64f06..7211dd3 100644 --- a/src/Fields/Gallery.php +++ b/src/Fields/Gallery.php @@ -12,6 +12,13 @@ public function get_graphql_type() { $type_registry = $this->registry->get_type_registry(); + $connection_name = $this->registry->get_connection_name( $type_name, 'MediaItem', $this->field_name ); + + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return $type_registry->get_type( $connection_name ); + } + $type_registry->register_connection([ 'fromType' => $type_name, 'toType' => 'MediaItem', @@ -36,6 +43,7 @@ public function get_graphql_type() { } ]); + return null; } diff --git a/src/Fields/Group.php b/src/Fields/Group.php index 4a2c301..ccec4d4 100644 --- a/src/Fields/Group.php +++ b/src/Fields/Group.php @@ -20,15 +20,10 @@ class Group extends AcfField { public function get_graphql_type() { $parent_type = $this->get_parent_type(); - $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); - $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); - $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); - return ! empty( $type_name ) ? $type_name : null; - } } diff --git a/src/Fields/PostObject.php b/src/Fields/PostObject.php index 49ab6d3..a97818e 100644 --- a/src/Fields/PostObject.php +++ b/src/Fields/PostObject.php @@ -17,6 +17,8 @@ public function get_graphql_type() { $type_registry = $this->registry->get_type_registry(); + $connection_name = $this->registry->get_connection_name( $type_name, 'ContentNode', $this->field_name ); + $connection_config = [ 'fromType' => $type_name, 'toType' => 'ContentNode', @@ -41,7 +43,8 @@ public function get_graphql_type() { ]; if ( ! isset( $this->field_config['multiple'] ) || true !== (bool) $this->field_config['multiple'] ) { - $connection_config['connectionTypeName'] = ucfirst( $type_name ) . 'ToSingleContentNodeConnection'; + $connection_name = ucfirst( $type_name ) . 'ToSingleContentNodeConnection'; + $connection_config['connectionTypeName'] = $connection_name; $connection_config['oneToOne'] = true; $connection_config['resolve'] = function( $root, $args, AppContext $context, $info ) { $value = $this->resolve( $root, $args, $context, $info ); @@ -58,8 +61,13 @@ public function get_graphql_type() { }; } - $type_registry->register_connection( $connection_config ); + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return $type_registry->get_type( $connection_name ); + } + $type_registry->register_connection( $connection_config ); + return null; } } diff --git a/src/Fields/Relationship.php b/src/Fields/Relationship.php index 0b671ea..380ebaa 100644 --- a/src/Fields/Relationship.php +++ b/src/Fields/Relationship.php @@ -35,10 +35,14 @@ public function get_graphql_type() { } ]; - $type_registry->register_connection( $connection_config ); - - + $connection_name = $this->registry->get_connection_name( $type_name, 'ContentNode', $this->field_name ); + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return null; + } + $type_registry->register_connection( $connection_config ); + return null; } diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php index f544e8b..c259811 100644 --- a/src/Fields/Repeater.php +++ b/src/Fields/Repeater.php @@ -16,15 +16,16 @@ class Repeater extends AcfField { /** * Returns the GraphQL Type to use in the Schema * - * @return array|null + * @return mixed|string|string[]|null * @throws Exception */ public function get_graphql_type() { $parent_type = $this->get_parent_type(); $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); - $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); - $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); - return ! empty( $type_name ) ? [ 'non_null' => [ 'list_of' => $type_name ] ] : null; + $this->field_config['graphql_field_name'] = $parent_type . Utils::format_type_name( $title ); + $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config, [ $parent_type ] ); + register_graphql_object_type( $type_name, [] ); + return [ 'non_null' => [ 'list_of' => $this->field_config['graphql_field_name'] ] ]; } /** @@ -37,6 +38,7 @@ public function get_graphql_type() { */ public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { $value = parent::resolve( $node, $args, $context, $info ); + return ! empty( $value ) && is_array( $value ) ? $value : []; } diff --git a/src/Fields/Select.php b/src/Fields/Select.php index 868e160..2877ed7 100644 --- a/src/Fields/Select.php +++ b/src/Fields/Select.php @@ -1,6 +1,15 @@ field_config['multiple'] ) && true === (bool) $this->field_config['multiple'] ) { return [ 'list_of' => 'String' ]; } + return 'String'; } /** - * Return the value different based on single or mulitple selection allowed + * Return the value different based on single or multiple selection allowed * - * @param $node - * @param $args - * @param $context - * @param $info + * @param mixed $node The node the field belongs to + * @param array $args The field arguments + * @param AppContext $context The AppContext passed down the resolve tree + * @param ResolveInfo $info The ResolveInfo passed down the resolve tree * * @return array|mixed|null */ - public function resolve( $node, $args, $context, $info ) { + public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { $value = parent::resolve( $node, $args, $context, $info ); if ( isset( $this->field_config['multiple'] ) && true === (bool) $this->field_config['multiple'] ) { - return ! empty( $value ) && is_array( $value ) ? $value : []; + return ! empty( $value ) && is_array( $value ) ? $value : null; } + return $value; } diff --git a/src/Fields/Taxonomy.php b/src/Fields/Taxonomy.php index 98934b3..c5068ed 100644 --- a/src/Fields/Taxonomy.php +++ b/src/Fields/Taxonomy.php @@ -15,7 +15,7 @@ class Taxonomy extends AcfField { /** * Determines the GraphQL Type for Taxonomy Fields * - * @return null + * @return void * @throws Exception */ public function get_graphql_type() { @@ -47,9 +47,14 @@ public function get_graphql_type() { } ]; - $type_registry->register_connection( $connection_config ); + $connection_name = $this->registry->get_connection_name( $type_name, 'TermNode', $this->field_name ); + + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return $type_registry->get_type( $connection_name ); + } - return null; + $type_registry->register_connection( $connection_config ); } diff --git a/src/Fields/User.php b/src/Fields/User.php index 5111505..e4425cb 100644 --- a/src/Fields/User.php +++ b/src/Fields/User.php @@ -22,6 +22,8 @@ public function get_graphql_type() { $type_name = $this->get_parent_type(); $type_registry = $this->registry->get_type_registry(); + $connection_name = $this->registry->get_connection_name( $type_name, 'User', $this->field_name ); + $connection_config = [ 'fromType' => $type_name, 'toType' => 'User', @@ -45,8 +47,10 @@ public function get_graphql_type() { } ]; + if ( ! isset( $this->field_config['multiple'] ) || true !== (bool) $this->field_config['multiple'] ) { - $connection_config['connectionTypeName'] = ucfirst( $type_name ) . 'ToSingleUserConnection'; + $connection_name = ucfirst( $type_name ) . 'ToSingleUserConnection'; + $connection_config['connectionTypeName'] = $connection_name; $connection_config['oneToOne'] = true; $connection_config['resolve'] = function( $root, $args, AppContext $context, $info ) { $value = $this->resolve( $root, $args, $context, $info ); @@ -63,6 +67,11 @@ public function get_graphql_type() { }; } + // If the connection already exists, don't register it again + if ( null !== $type_registry->get_type( $connection_name ) ) { + return $connection_name; + } + $type_registry->register_connection( $connection_config ); } diff --git a/src/Registry.php b/src/Registry.php index a34f689..d4a80e3 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -12,6 +12,11 @@ use WPGraphQL\Registry\TypeRegistry; use WPGraphQL\Utils\Utils; +/** + * Class Registry + * + * @package WPGraphQL\ACF + */ class Registry { /** @@ -36,6 +41,29 @@ class Registry { */ protected $registered_field_names; + /** + * Tracks which field groups have already been registered to avoid recursion + * + * @var array + */ + protected $registered_field_groups; + + /** + * Tracks which field groups have already been registered to avoid recursion and allow + * referencing for cloned fields + * + * @var array + */ + protected $registered_field_group_interfaces; + + /** + * Tracks which field group Interfaces have already been registered to avoid recursion and + * allow referencing in cloned fields + * + * @var array + */ + protected $registered_field_group_fields_interfaces; + /** * Initialize ACF Type Registry * @@ -52,6 +80,11 @@ public function init( TypeRegistry $type_registry ) { // Get all ACF Field Groups $this->acf_field_groups = acf_get_field_groups(); + // Instantiate the field groups array + $this->registered_field_groups = []; + $this->registered_field_group_interfaces = []; + $this->registered_field_group_fields_interfaces = []; + // If there are no ACF Field Groups, don't proceed if ( empty( $this->acf_field_groups ) || ! is_array( $this->acf_field_groups ) ) { return; @@ -67,6 +100,60 @@ public function init( TypeRegistry $type_registry ) { $this->map_acf_to_graphql(); } + /** + * Determines if a field group has already been registered + * + * @param string $key + * + * @return bool + */ + public function get_registered_field_group( string $key ) { + return isset( $this->registered_field_groups[ $key ] ); + } + + /** + * + * + * @param string string $key The key the field group is registered under + * @param string $type_name $type_name The GraphQL Type name + * + * @return string + */ + public function add_registered_field_group( string $key, string $type_name ) { + $this->registered_field_groups[ $key ] = $type_name; + return $type_name; + } + + public function add_registered_field_group_interface( $key, $interface_name ) { + $this->registered_field_group_interface[ $key ] = $interface_name; + } + + public function add_registered_field_group_fields_interface( $key, $interface_name ) { + $this->registered_field_group_fields_interface[ $key ] = $interface_name; + } + + /** + * Given a from Type, to Type and from field name, a connection name is returned + * + * @param string $from_type The Type name the connection is coming from + * @param string $to_type The Type name the connection is going to + * @param string $from_field_name The name of the field the connection resolves from + * + * @return string + */ + public function get_connection_name( string $from_type, string $to_type, string $from_field_name ) { + // Create connection name using $from_type + To + $to_type + Connection. + $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $to_type ) . 'Connection'; + + // If connection type already exists with that connection name. Set connection name using + // $from_field_name + To + $to_type + Connection. + if ( ! empty( $this->type_registry->get_type( $connection_name ) ) ) { + $connection_name = ucfirst( $from_type ) . 'To' . ucfirst( $from_field_name ) . 'Connection'; + } + + return $connection_name; + } + /** * @return TypeRegistry */ @@ -87,36 +174,35 @@ public function get_type_registry() { */ public function resolve_meta_from_parent( bool $should, $object_id, string $meta_key, bool $single ) { - // Loop through all registered ACF fields that show in GraphQL. - if ( is_array( $this->registered_field_names ) && ! empty( $this->registered_field_names ) ) { - - $matches = null; + if ( empty( $this->registered_field_names ) || ! is_array( $this->registered_field_names ) ) { + return $should; + } - // Iterate over all field names - foreach ( $this->registered_field_names as $field_name ) { + $matches = null; - // If the field name is an exact match with the $meta_key, the ACF field should - // resolve from the revision meta, so we can return false here, so that meta can - // resolve from the revision instead of the parent - if ( $field_name === $meta_key ) { - return false; - } + // Iterate over all field names + foreach ( $this->registered_field_names as $field_name ) { - // For flex fields/repeaters, the meta keys are structured a bit funky. - // This checks to see if the $meta_key starts with the same string as one of the - // acf fields (a flex/repeater field) and then checks if it's preceeded by an underscore and a number. - if ( $field_name === substr( $meta_key, 0, strlen( $field_name ) ) ) { - // match any string that starts with the field name, followed by an underscore, followed by a number, followed by another string - // ex my_flex_field_0_text_field or some_repeater_field_12_25MostPopularDogToys - $pattern = '/' . $field_name . '_\d+_\w+/m'; - preg_match( $pattern, $meta_key, $matches ); - } + // If the field name is an exact match with the $meta_key, the ACF field should + // resolve from the revision meta, so we can return false here, so that meta can + // resolve from the revision instead of the parent + if ( $field_name === $meta_key ) { + return false; + } - // If the meta key matches the pattern, treat it as a sub-field of an ACF Field Group - if ( null !== $matches ) { - return false; - } + // For flex fields/repeaters, the meta keys are structured a bit funky. + // This checks to see if the $meta_key starts with the same string as one of the + // acf fields (a flex/repeater field) and then checks if it's preceeded by an underscore and a number. + if ( $field_name === substr( $meta_key, 0, strlen( $field_name ) ) ) { + // match any string that starts with the field name, followed by an underscore, followed by a number, followed by another string + // ex my_flex_field_0_text_field or some_repeater_field_12_25MostPopularDogToys + $pattern = '/' . $field_name . '_\d+_\w+/m'; + preg_match( $pattern, $meta_key, $matches ); + } + // If the meta key matches the pattern, treat it as a sub-field of an ACF Field Group + if ( null !== $matches ) { + return false; } } @@ -165,31 +251,31 @@ public function register_options_pages() { $page_slug = $options_page['menu_slug']; $type_name = isset( $options_page['graphql_field_name'] ) ? Utils::format_type_name( $options_page['graphql_field_name'] ) : Utils::format_type_name( $options_page['menu_slug'] ); - if ( null === $this->type_registry->get_type( $type_name ) ) { - - $this->type_registry->register_object_type( $type_name, [ - 'description' => sprintf( __( '%s options. Registered as an ACF Options page.', 'wp-graphql-acf' ), $page_title ), - 'fields' => [ - 'pageTitle' => [ - 'type' => 'String', - 'resolve' => function( $source ) use ( $page_title ) { - return ! empty( $page_title ) ? $page_title : null; - }, - ], - 'pageSlug' => [ - 'type' => 'String', - 'resolve' => function( $source ) use ( $page_slug ) { - return ! empty( $page_slug ) ? $page_slug : null; - }, - ], - ], - ] ); - + if ( null !== $this->type_registry->get_type( $type_name ) ) { + return; } + register_graphql_object_type( $type_name, [ + 'description' => sprintf( __( '%s options. Registered as an ACF Options page.', 'wp-graphql-acf' ), $page_title ), + 'fields' => [ + 'pageTitle' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_title ) { + return ! empty( $page_title ) ? $page_title : null; + }, + ], + 'pageSlug' => [ + 'type' => 'String', + 'resolve' => function( $source ) use ( $page_slug ) { + return ! empty( $page_slug ) ? $page_slug : null; + }, + ], + ], + ] ); + $field_name = Utils::format_field_name( $type_name ); - $this->type_registry->register_field( + register_graphql_field( 'RootQuery', $field_name, [ @@ -261,6 +347,11 @@ public function register_initial_types() { * @throws Exception */ public function map_acf_field_groups_to_types() { + + if ( empty( $this->acf_field_groups ) || ! is_array( $this->acf_field_groups ) ) { + return; + } + foreach ( $this->acf_field_groups as $field_group ) { $this->add_acf_field_group_to_graphql( $field_group ); } @@ -271,92 +362,171 @@ public function map_acf_field_groups_to_types() { * field group should show on. * * @param array $field_group The ACF Field Group config to add to the Schema + * @param array $graphql_types The GraphQL Types the field group should show on + * @param array $interfaces Interfaces to apply to the Types * - * @return mixed|string|null + * @return mixed|string|void * * @throws Exception */ - public function add_acf_field_group_to_graphql( array $field_group ) { + public function add_acf_field_group_to_graphql( array $field_group, array $graphql_types = [], $interfaces = [] ) { - if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { - return null; + // If the field group has already been registered, return the registered Type + if ( isset( $field_group['key'] ) && $this->get_registered_field_group( $field_group['key'] ) ) { + return $this->get_registered_field_group( $field_group['key'] ); } - $type_name = $this->get_field_group_type_name( $field_group ); - $interface_name = 'With' . $type_name; + if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { + return; + } - // Check if a GraphQL Type already exists for the Type - if ( null === $this->type_registry->get_type( $type_name ) ) { + $field_group_name = ''; - $this->type_registry->register_object_type( $type_name, [ - 'description' => __( 'Acf Field Group', 'wp-graphql' ), - 'interfaces' => [ 'AcfFieldGroup', 'Node' ], - 'fields' => [ - 'id' => [ - 'resolve' => function() use ( $field_group ) { - return Relay::toGlobalId( 'AcfFieldGroup', $field_group['ID'] ); - } - ], - '_fieldGroupConfig' => [ - 'resolve' => function( $root ) use ( $field_group ) { - return $field_group; - }, - ], - 'fieldGroupName' => [ - 'resolve' => function() use ( $type_name ) { - return lcfirst( $type_name ); - } - ] - ], - ] ); + if ( isset( $field_group['graphql_field_name'] ) ) { + $field_group_name = $field_group['graphql_field_name']; + } else if ( isset( $field_group['title'] ) ) { + $field_group_name = $field_group['title']; + } else if ( isset( $field_group['label'] ) ) { + $field_group_name = $field_group['label']; + } else if ( isset( $field_group['name'] ) ) { + $field_group_name = $field_group['name']; } - $this->map_acf_fields_to_field_group( $field_group ); - - $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); + if ( empty( $field_group_name ) ) { + graphql_debug( __( 'No name could be determined for the field group', 'wp-graphql-acf'), [ 'fieldGroup' => $field_group ] ); + return; + } - if ( ! empty( $graphql_types ) && is_array( $graphql_types ) ) { + $type_name = $this->get_field_group_type_name( $field_group ); + $interface_name = 'With_' . $type_name; + $fields_interface_name = $interface_name . '_' . 'Fields'; - if ( null === $this->type_registry->get_type( $interface_name ) ) { + $this->type_registry->register_interface_type( $interface_name, [ + 'description' => sprintf( __( 'Fields of the %s ACF Field Group', 'wp-graphql-acf' ), $field_group_name ), + 'fields' => [ + lcfirst( $type_name ) => [ + 'type' => $type_name, + 'description' => sprintf( __( 'Types that support the %s field group', 'wp-graphql' ), $field_group_name ), + 'resolve' => function( $root ) use ( $field_group ) { + return ! empty( $root ) ? $root : $field_group; + } + ], + ], + ] ); - $this->type_registry->register_interface_type( $interface_name, [ - 'description' => sprintf( __( 'A node that can have fields of the "%s" Field Group.', 'wp-graphql-acf' ), $type_name ), - 'fields' => [ - lcfirst( $type_name ) => [ - 'type' => $type_name, - 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $type_name ), - 'resolve' => function( $root ) use ( $field_group ) { - return ! empty( $root ) ? $root : $field_group; + $fields = [ + 'fieldGroupName' => [ + 'type' => 'String', + 'resolve' => function() use ( $type_name ) { + return lcfirst( $type_name ); + } + ] + ]; - } - ], - ], - ] ); + // wp_send_json( [ $field_group, acf_get_raw_fields( $field_group['ID'] ) ] ); - } + $mapped_fields = $this->map_acf_fields_to_field_group( $field_group ); + $mapped_fields = ! empty( $mapped_fields ) ? array_merge( $fields, $mapped_fields ) : $fields; - register_graphql_interfaces_to_types( [ $interface_name ], $graphql_types ); + $this->type_registry->register_interface_type( $fields_interface_name, [ + 'description' => sprintf( __( 'Field Groups with fields of the %s ACF Field Group', 'wp-graphql-acf' ), $field_group_name ), + 'fields' => $mapped_fields + ]); - $field_name = isset( $field_group['graphql_field_name'] ) ? lcfirst( $field_group['graphql_field_name'] ) : lcfirst( $type_name ); + $this->type_registry->register_object_type( $type_name, [ + 'interfaces' => [ 'AcfFieldGroup', $fields_interface_name ], + 'fields' => $mapped_fields + ]); - foreach ( $graphql_types as $graphql_type ) { + if ( empty( $graphql_types ) ) { + $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); + } - $this->type_registry->register_field( $graphql_type, $field_name, [ - 'type' => $type_name, - 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $graphql_type ), - 'resolve' => function( $root ) use ( $field_group ) { - return [ - 'node' => $root, - 'field_group' => $field_group - ]; - } - ] ); +// if ( 'Options' === $field_group['title'] ) { +// wp_send_json( [ $interface_name => $graphql_types ] ); +// } - } + if ( ! empty( $graphql_types ) ) { + register_graphql_interfaces_to_types( $interface_name, $graphql_types ); } - return $type_name; +// if ( false !== strpos( $field_group['key'], 'layout_' ) ) { +// wp_send_json( [ 'layout', $graphql_types, $field_group, $type_name, $interface_name ] ); +// } + + // Add the interfaces to a Registry, so they can be identified by field group key + $this->add_registered_field_group_interface( $field_group['key'], $interface_name ); + $this->add_registered_field_group_fields_interface( $field_group['key'], $fields_interface_name ); + + + // Add the field group to the list of registered field groups + return $this->add_registered_field_group( $field_group['key'], $type_name ); + +// +// // Check if a GraphQL Type already exists for the Type +// if ( null !== $this->type_registry->get_type( $type_name ) ) { +// return $type_name; +// } +// +// $this->type_registry->register_object_type( $type_name, [ +// 'description' => __( 'Acf Field Group', 'wp-graphql' ), +// 'interfaces' => [ 'AcfFieldGroup', 'Node' ], +// 'fields' => [ +// 'id' => [ +// 'resolve' => function() use ( $field_group ) { +// return Relay::toGlobalId( 'AcfFieldGroup', $field_group['ID'] ); +// } +// ], +// '_fieldGroupConfig' => [ +// 'resolve' => function( $root ) use ( $field_group ) { +// return $field_group; +// }, +// ], +// 'fieldGroupName' => [ +// 'resolve' => function() use ( $type_name ) { +// return lcfirst( $type_name ); +// } +// ] +// ], +// ] ); +// +// $this->map_acf_fields_to_field_group( $field_group ); +// +// if ( empty( $graphql_types ) ) { +// $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); +// } +// +// if ( empty( $graphql_types ) ) { +// $graphql_types = [ $type_name ]; +// } +// +// if ( ! empty( $graphql_types ) && is_array( $graphql_types ) ) { +// +// if ( null === $this->type_registry->get_type( $interface_name ) ) { +// +// $field_name = isset( $field_group['graphql_field_name'] ) ? lcfirst( $field_group['graphql_field_name'] ) : lcfirst( $type_name ); +// +// $this->type_registry->register_interface_type( $interface_name, [ +// 'description' => sprintf( __( 'A node that can have fields of the "%s" Field Group.', 'wp-graphql-acf' ), $type_name ), +// 'fields' => [ +// $field_name => [ +// 'type' => $type_name, +// 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $type_name ), +// 'resolve' => function( $root ) use ( $field_group ) { +// return ! empty( $root ) ? $root : $field_group; +// } +// ], +// ], +// ] ); +// +// } +// +// register_graphql_interfaces_to_types( [ $interface_name ], $graphql_types ); +// +// } +// +// return $type_name; } /** @@ -386,7 +556,7 @@ public function get_graphql_types_for_field_group( array $field_group ) { } } - return $graphql_types; + return ! empty( $graphql_types ) && is_array( $graphql_types ) ? array_unique( array_filter( $graphql_types ) ) : []; } @@ -428,7 +598,7 @@ protected function get_location_rules() { * * @return bool */ - protected function should_field_group_show_in_graphql( $field_group ) { + protected function should_field_group_show_in_graphql( array $field_group ) { /** * By default, field groups will not be exposed to GraphQL. @@ -442,6 +612,10 @@ protected function should_field_group_show_in_graphql( $field_group ) { $show = true; } + if ( isset( $field_group['parent'] ) && ! empty( $field_group['parent'] ) ) { + $show = true; + } + /** * Whether a field group should show in GraphQL. * @@ -454,7 +628,9 @@ protected function should_field_group_show_in_graphql( $field_group ) { } /** - * @param array $field_group + * Given a field group config array, returns the Type name to be used in the graph + * + * @param array $field_group The ACF Field Group config array * * @return string */ @@ -465,23 +641,89 @@ public function get_field_group_type_name( array $field_group ) { return $type_name; } + /** + * Get a list of supported field types that WPGraphQL for ACF supports. + * + * This is helpful for determining whether UI should be output for the field, and whether + * the field should be added to the Schema. + * + * Some fields, such as "Accordion" are not supported currently. + * + * @return array + */ + public function get_supported_field_types() { + + $supported_field_types = [ + 'text', + 'textarea', + 'number', + 'range', + 'email', + 'url', + 'password', + 'image', + 'file', + 'wysiwyg', + 'oembed', + 'gallery', + 'select', + 'checkbox', + 'radio', + 'button_group', + 'true_false', + 'link', + 'post_object', + 'page_link', + 'relationship', + 'taxonomy', + 'user', + 'google_map', + 'date_picker', + 'date_time_picker', + 'time_picker', + 'color_picker', + 'group', + 'repeater', + 'flexible_content' + ]; + + /** + * filter the supported fields to allow 3rd party extensions to hook in and + * add support for their fields. + * + * @param array $supported_fields + */ + return apply_filters( 'wpgraphql_acf_supported_fields', $supported_field_types ); + + } + /** * Map Fields to the Field Groups in the Schema * * @param array $field_group * - * @return void + * @return array */ public function map_acf_fields_to_field_group( array $field_group ) { // Get the ACF Fields for the specified field group $fields = isset( $field_group['sub_fields'] ) && is_array( $field_group['sub_fields'] ) ? $field_group['sub_fields'] : acf_get_fields( $field_group ); + // $raw_fields = acf_get_raw_fields( $field_group['ID'] ); + +// wp_send_json( [ +// 'groups' => $this->acf_field_groups, +// 'fields' => $fields, +// 'rawFields' => $raw_fields +// ] ); + // If there are no for the field group, do nothing. if ( empty( $fields ) || ! is_array( $fields ) ) { - return; + return []; } + $mapped_fields = []; + // Store a list of field keys that have been registered // to help avoid registering the same field twice on one // field group. This occasionally happens with clone fields. @@ -509,19 +751,32 @@ public function map_acf_fields_to_field_group( array $field_group ) { continue; } - $this->register_graphql_field( $field, $field_group ); + // If the ACF Field Type is not a supported field type, don't add it to the Schema. + // For example, Accordion and Tab types, and various extension types + // are not supported natively, but can be filtered in! + if ( ! in_array( $field['type'], $this->get_supported_field_types(), true ) ) { + continue; + } + + $mapped_field = $this->map_graphql_field( $field, $field_group ); + + if ( $mapped_field instanceof AcfField && null !== $mapped_field->get_graphql_field_config() ) { + $mapped_fields[ $mapped_field->get_field_name() ] = $mapped_field->get_graphql_field_config(); + } } + return $mapped_fields; + } /** * @param array $field The ACF Field config * @param array $field_group The ACF Field Group Config * - * @return void + * @return AcfField */ - public function register_graphql_field( array $field, array $field_group ) { + public function map_graphql_field( array $field, array $field_group ) { $field_type = isset( $field['type'] ) ? $field['type'] : null; @@ -535,11 +790,14 @@ public function register_graphql_field( array $field, array $field_group ) { $class_name = apply_filters( 'graphql_acf_field_class', $class_name, $field, $field_group, $this ); if ( class_exists( $class_name ) ) { - $field_class = new $class_name( $field, $field_group, $this ); - $field_class->register_field(); + $field = new $class_name( $field, $field_group, $this ); + if ( $field instanceof AcfField ) { + return $field; + } else { + return new AcfField( $field, $field_group, $this ); + } } else { - $field_class = new AcfField( $field, $field_group, $this ); - $field_class->register_field(); + return new AcfField( $field, $field_group, $this ); } } diff --git a/tests/wpunit/ClonedFieldsTest.php b/tests/wpunit/ClonedFieldsTest.php new file mode 100644 index 0000000..da28fdf --- /dev/null +++ b/tests/wpunit/ClonedFieldsTest.php @@ -0,0 +1,339 @@ +group_key = __CLASS__; + WPGraphQL::clear_schema(); + $this->register_acf_field_group(); + + $this->post_id = $this->factory()->post->create( [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => 'Test', + 'post_content' => 'test', + ] ); + + $this->test_image = dirname( __FILE__, 2 ) . '/_data/images/test.png'; + + + } + + public function tearDown(): void { + + $this->deregister_acf_field_group(); + WPGraphQL::clear_schema(); + wp_delete_post( $this->post_id, true ); + parent::tearDown(); // TODO: Change the autogenerated stub + } + + public function deregister_acf_field_group() { + acf_remove_local_field_group( $this->group_key ); + } + + public function register_acf_field_group( $config = [] ) { + + $defaults = [ + 'key' => $this->group_key, + 'title' => 'Cloned Field Group', + 'fields' => [ + [ + 'key' => 'field_60abe262d31cd', + 'label' => 'text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ], + [ + 'key' => 'field_60abe267d31ce', + 'label' => 'Image', + 'name' => 'image', + 'type' => 'image', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'return_format' => 'array', + 'preview_size' => 'medium', + 'library' => 'all', + 'min_width' => '', + 'min_height' => '', + 'min_size' => '', + 'max_width' => '', + 'max_height' => '', + 'max_size' => '', + 'mime_types' => '', + ], + [ + 'key' => 'field_60abe26ed31cf', + 'label' => 'color', + 'name' => 'color', + 'type' => 'color_picker', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'default_value' => '', + ], + ], + 'location' => [ + [ + [ + 'param' => 'post_type', + 'operator' => '==', + 'value' => 'post', + ], + ], + ], + 'menu_order' => 0, + 'position' => 'normal', + 'style' => 'default', + 'label_placement' => 'top', + 'instruction_placement' => 'label', + 'hide_on_screen' => '', + 'active' => false, + 'description' => '', + 'show_in_graphql' => 1, + 'graphql_field_name' => 'cloneGroup', + 'graphql_types' => '', + 'map_graphql_types_from_location_rules' => false, + ]; + + acf_add_local_field_group( array_merge( $defaults, $config ) ); + + } + + public function register_acf_field( $config = [] ) { + + $defaults = [ + 'parent' => $this->group_key, + 'key' => 'field_5d7812fd000a4', + 'label' => 'Text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ]; + + acf_add_local_field( array_merge( $defaults, $config ) ); + } + + /** + * @throws Exception + */ + public function testBasicQuery() { + $query = '{ posts { nodes { id } } }'; + $actual = graphql( [ 'query' => $query ] ); + $this->assertArrayNotHasKey( 'errors', $actual ); + } + + public function test_cloned_field_group_exists_in_schema() { + + $this->register_acf_field([ + 'name' => 'text_field_clone_test', + 'type' => 'text', + ]); + + $query = ' + query getType( $name: String! ){ + __type(name: $name) { + name + kind + fields { + name + type { + kind + } + } + } + } + '; + + $name = 'CloneGroup'; + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'name' => $name + ] + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + $this->assertSame( $name, $actual['data']['__type']['name'] ); + + } + + public function testFieldGroupWithClonedFieldsIsQueryable() { + + // Register a field to the clone field group + $this->register_acf_field([ + 'name' => 'text_field_clone_test', + 'type' => 'text', + ]); + + // Register a new field group, which sets it's fields + // as a clone of the clone field group + $this->register_acf_field_group([ + 'key' => 'group_60abfd20a6b4e', + 'title' => 'Post Group With Clone Fields', + 'fields' => array( + array( + 'key' => 'field_60abfd2d03112', + 'label' => 'clone', + 'name' => 'clone', + 'type' => 'clone', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'clone' => array( + 0 => $this->group_key, + ), + 'display' => 'seamless', + 'layout' => 'block', + 'prefix_label' => 0, + 'prefix_name' => 0, + ), + ), + 'location' => array( + array( + array( + 'param' => 'post_type', + 'operator' => '==', + 'value' => 'post', + ), + ), + ), + 'menu_order' => 0, + 'position' => 'normal', + 'style' => 'default', + 'label_placement' => 'top', + 'instruction_placement' => 'label', + 'hide_on_screen' => '', + 'active' => true, + 'description' => '', + 'show_in_graphql' => true, + 'graphql_field_name' => 'postGroupWithCloneFields', + 'map_graphql_types_from_location_rules' => 0, + 'graphql_types' => '', + ]); + + // Query to make sure the field we registered to the clone field group + // shows in the Schema + $query = ' + query getType( $name: String! ){ + __type(name: $name) { + name + kind + fields { + name + type { + kind + } + } + } + } + '; + + $name = 'PostGroupWithCloneFields'; + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'name' => $name + ] + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + $this->assertSame( $name, $actual['data']['__type']['name'] ); + $field_names = wp_list_pluck( $actual['data']['__type']['fields'], 'name' ); + + // Test to make sure the field we just registered exists in the Schema on the `PostGroupWithCloneFields` type + $this->assertTrue( in_array( 'textFieldCloneTest', $field_names, true ) ); + + $query = ' + query getPostById( $id: ID! ) { + post(id:$id idType:DATABASE_ID) { + databaseId + postGroupWithCloneFields { + textFieldCloneTest + } + } + } + '; + + // Update the ACF field value + $value = 'test value'; + update_field( 'text_field_clone_test', $value, $this->post_id ); + + // Query the field group that has cloned fields assigned to it + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'id' => $this->post_id, + ] + ]); + + codecept_debug( $actual ); + + delete_field( 'text_field_clone_test', $this->post_id ); + + // Assert that the value of the clone field is returned + $this->assertArrayNotHasKey( 'errors', $actual ); + $this->assertSame( $value, $actual['data']['post']['postGroupWithCloneFields']['textFieldCloneTest'] ); + + acf_remove_local_field_group( 'group_60abfd20a6b4e' ); + } + +} diff --git a/tests/wpunit/FieldGroupTest.php b/tests/wpunit/FieldGroupTest.php new file mode 100644 index 0000000..443f8cd --- /dev/null +++ b/tests/wpunit/FieldGroupTest.php @@ -0,0 +1,114 @@ +group_key = __CLASS__; + WPGraphQL::clear_schema(); + $this->register_acf_field_group(); + + $this->post_id = $this->factory()->post->create( [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => 'Test', + 'post_content' => 'test', + ] ); + + $this->test_image = dirname( __FILE__, 2 ) . '/_data/images/test.png'; + + + } + + public function tearDown(): void { + + $this->deregister_acf_field_group(); + WPGraphQL::clear_schema(); + wp_delete_post( $this->post_id, true ); + parent::tearDown(); // TODO: Change the autogenerated stub + } + + public function deregister_acf_field_group() { + acf_remove_local_field_group( $this->group_key ); + } + + public function register_acf_field_group( $config = [] ) { + + $defaults = [ + 'key' => $this->group_key, + 'title' => 'Post Object Fields', + 'fields' => [], + 'location' => [ + [ + [ + 'param' => 'post_type', + 'operator' => '==', + 'value' => 'post', + ], + ], + ], + 'menu_order' => 0, + 'position' => 'normal', + 'style' => 'default', + 'label_placement' => 'top', + 'instruction_placement' => 'label', + 'hide_on_screen' => '', + 'active' => true, + 'description' => '', + 'show_in_graphql' => 1, + 'graphql_field_name' => 'postFields', + 'graphql_types' => [ 'Post' ] + ]; + + acf_add_local_field_group( array_merge( $defaults, $config ) ); + + } + + public function register_acf_field( $config = [] ) { + + $defaults = [ + 'parent' => $this->group_key, + 'key' => 'field_5d7812fd000a4', + 'label' => 'Text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ]; + + acf_add_local_field( array_merge( $defaults, $config ) ); + } + + /** + * @throws Exception + */ + public function testBasicQuery() { + $query = '{ posts { nodes { id } } }'; + $actual = graphql( [ 'query' => $query ] ); + $this->assertArrayNotHasKey( 'errors', $actual ); + } + + public function test_query_flex_field_on_post() { + + } + +} diff --git a/tests/wpunit/FlexFieldsTest.php b/tests/wpunit/FlexFieldsTest.php new file mode 100644 index 0000000..35e6710 --- /dev/null +++ b/tests/wpunit/FlexFieldsTest.php @@ -0,0 +1,118 @@ +group_key = __CLASS__; + WPGraphQL::clear_schema(); + $this->register_acf_field_group(); + + $this->post_id = $this->factory()->post->create( [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => 'Test', + 'post_content' => 'test', + ] ); + + $this->test_image = dirname( __FILE__, 2 ) . '/_data/images/test.png'; + + + } + + public function tearDown(): void { + + $this->deregister_acf_field_group(); + WPGraphQL::clear_schema(); + wp_delete_post( $this->post_id, true ); + parent::tearDown(); // TODO: Change the autogenerated stub + } + + public function deregister_acf_field_group() { + acf_remove_local_field_group( $this->group_key ); + } + + public function register_acf_field_group( $config = [] ) { + + $defaults = [ + 'key' => $this->group_key, + 'title' => 'Post Object Fields', + 'fields' => [], + 'location' => [ + [ + [ + 'param' => 'post_type', + 'operator' => '==', + 'value' => 'post', + ], + ], + ], + 'menu_order' => 0, + 'position' => 'normal', + 'style' => 'default', + 'label_placement' => 'top', + 'instruction_placement' => 'label', + 'hide_on_screen' => '', + 'active' => true, + 'description' => '', + 'show_in_graphql' => 1, + 'graphql_field_name' => 'postFields', + 'graphql_types' => [ 'Post' ] + ]; + + acf_add_local_field_group( array_merge( $defaults, $config ) ); + + } + + public function register_acf_field( $config = [] ) { + + $defaults = [ + 'parent' => $this->group_key, + 'key' => 'field_5d7812fd000a4', + 'label' => 'Text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ]; + + acf_add_local_field( array_merge( $defaults, $config ) ); + } + + /** + * @throws Exception + */ + public function testBasicQuery() { + $query = '{ posts { nodes { id } } }'; + $actual = graphql( [ 'query' => $query ] ); + $this->assertArrayNotHasKey( 'errors', $actual ); + } + + public function test_query_flex_field_on_post() { + + $this->register_acf_field( + + ); + + } + +} diff --git a/tests/wpunit/PostObjectFieldsTest.php b/tests/wpunit/PostObjectFieldsTest.php index 8208583..d8fbb32 100644 --- a/tests/wpunit/PostObjectFieldsTest.php +++ b/tests/wpunit/PostObjectFieldsTest.php @@ -145,7 +145,6 @@ public function testAcfTextField() { $this->assertSame( $expected_text_1, $actual['data']['postBy']['postFields']['textField'] ); - } /** @@ -1238,7 +1237,7 @@ public function testQueryMultipleSelectFieldWithNoValueSet() { codecept_debug( $actual ); - $this->assertSame( [], $actual['data']['postBy']['postFields']['selectMultiple'] ); + $this->assertSame( null, $actual['data']['postBy']['postFields']['selectMultiple'] ); } @@ -1500,9 +1499,6 @@ public function test_taxonomy_field_in_repeater_returns_terms() { $this->register_acf_field([ 'type' => 'repeater', 'name' => 'repeater_field', - 'post_type' => [ - 'post', - ], 'sub_fields' => [ [ 'key' => 'field_609d76ed7dc3e', @@ -1616,13 +1612,10 @@ public function test_repeater_field_with_no_values_returns_empty_array() { $this->register_acf_field([ 'type' => 'repeater', - 'name' => 'repeater_field', - 'post_type' => [ - 'post', - ], + 'name' => 'repeater_test', 'sub_fields' => [ [ - 'key' => 'field_609d76ed7dc3e', + 'key' => 'field_609d76easdfere', 'label' => 'text', 'name' => 'text', 'type' => 'text', @@ -1650,13 +1643,15 @@ public function test_repeater_field_with_no_values_returns_empty_array() { id title postFields { - repeaterField { + repeaterTest { text } } } }'; + update_field( 'repeater_test', [], $this->post_id ); + $actual = graphql([ 'query' => $query, 'variables' => [ @@ -1664,19 +1659,21 @@ public function test_repeater_field_with_no_values_returns_empty_array() { ] ]); + codecept_debug( $actual ); + $this->assertArrayNotHasKey( 'errors', $actual ); - $this->assertEmpty( $actual['data']['postBy']['postFields']['repeaterField'] ); + $this->assertEmpty( $actual['data']['postBy']['postFields']['repeaterTest'] ); - update_field( 'repeater_field', [ + update_field( 'repeater_test', [ [ - 'field_609d76ed7dc3e' => 'text one' + 'field_609d76easdfere' => 'text one' ], [ - 'field_609d76ed7dc3e' => 'text two' + 'field_609d76easdfere' => 'text two' ], [ - 'field_609d76ed7dc3e' => 'text three' + 'field_609d76easdfere' => 'text three' ] ], $this->post_id ); @@ -1686,7 +1683,7 @@ public function test_repeater_field_with_no_values_returns_empty_array() { id title postFields { - repeaterField { + repeaterTest { text } } @@ -1704,9 +1701,83 @@ public function test_repeater_field_with_no_values_returns_empty_array() { $this->assertArrayNotHasKey( 'errors', $actual ); - $this->assertSame( 'text one', $actual['data']['postBy']['postFields']['repeaterField'][0]['text'] ); - $this->assertSame( 'text two', $actual['data']['postBy']['postFields']['repeaterField'][1]['text'] ); - $this->assertSame( 'text three', $actual['data']['postBy']['postFields']['repeaterField'][2]['text'] ); + $this->assertSame( 'text one', $actual['data']['postBy']['postFields']['repeaterTest'][0]['text'] ); + $this->assertSame( 'text two', $actual['data']['postBy']['postFields']['repeaterTest'][1]['text'] ); + $this->assertSame( 'text three', $actual['data']['postBy']['postFields']['repeaterTest'][2]['text'] ); + + } + + public function test_flex_field_with_empty_layout_does_not_return_errors() { + + // @todo: Write this test!!! + // Register Flex Field + // Query flex field, with no data saved to it + // Assert no errors + // Update field with empty layout + // Query field + // Assert no errors + // Update field with layout data + // Query Field + // Assert no errors + +// $this->register_acf_field([ +// 'key' => 'field_60a2eba592eca', +// 'label' => 'FlexFieldWithEmptyLayout', +// 'name' => 'flex_field_with_empty_layout', +// 'type' => 'flexible_content', +// 'instructions' => '', +// 'required' => 0, +// 'conditional_logic' => 0, +// 'wrapper' => array( +// 'width' => '', +// 'class' => '', +// 'id' => '', +// ), +// 'show_in_graphql' => 1, +// 'layouts' => array( +// 'layout_60a2ebb0ddd96' => array( +// 'key' => 'layout_60a2ebb0ddd96', +// 'name' => 'layout_one', +// 'label' => 'Layout One', +// 'display' => 'block', +// 'sub_fields' => array( +// // No subfields in the layout, intentionally +// ), +// 'min' => '', +// 'max' => '', +// ), +// ), +// 'button_label' => 'Add Row', +// 'min' => '', +// 'max' => '', +// ]); +// +// $query = ' +// query GET_POST_WITH_ACF_FIELD( $postId: Int! ) { +// postBy( postId: $postId ) { +// id +// title +// postFields { +// flexFieldWithEmptyLayout { +// __typename +// } +// } +// } +// }'; +// +// $actual = graphql([ +// 'query' => $query, +// 'variables' => [ +// 'postId' => $this->post_id, +// ] +// ]); +// +// codecept_debug( $actual ); +// +// +// $this->assertArrayNotHasKey( 'errors', $actual ); +// $this->assertEmpty( $actual['data']['postBy']['postFields']['flexFieldWithEmptyLayout']); + } diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index bc12c14..ce6cd69 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -30,7 +30,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', + 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -42,7 +42,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', + 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', ), ), ); diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index f0c58ce..7560120 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', + 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -18,7 +18,7 @@ 'aliases' => array ( ), - 'reference' => '34e9141d2149c1efb95b8af89fafc3af9af97ece', + 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', ), ), ); diff --git a/wp-graphql-acf.php b/wp-graphql-acf.php index f8ff463..b39fa79 100644 --- a/wp-graphql-acf.php +++ b/wp-graphql-acf.php @@ -116,4 +116,3 @@ function can_load_plugin() { return true; } - From b8424096b193836bbaffc9bccbf285d71b961a5d Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Tue, 29 Jun 2021 13:12:42 -0600 Subject: [PATCH 09/12] - Update WPGraphQL Test Case to v2.0 - Add initial work to open Field Group as a Fragment in GraphiQL (in progress, but shows where in the Schema Field Groups will show in the Field Group posts table) - Add helper function `get_parent_type_fields_interface()` to AcfField.php - Update fields/FlexibleContent.php to register Layout Interfaces and return the Flex Field type as a list of the Layout Interface - Update Connection Fields to use the Field Group Interface type as the to-type instead of the Field Group Object Type - Add a complex field group export for tests - Add initial tests for Settings (testing the fragment generation) - Add initial tests for ComplexFlexAndCloneFields --- composer.json | 3 +- src/AcfSettings.php | 244 +- src/Fields/AcfField.php | 9 + src/Fields/File.php | 4 +- src/Fields/FlexibleContent.php | 123 +- src/Fields/Gallery.php | 2 +- src/Fields/Group.php | 3 +- src/Fields/PostObject.php | 2 +- src/Fields/Relationship.php | 2 +- src/Fields/Repeater.php | 5 +- src/Fields/Taxonomy.php | 2 +- src/Fields/User.php | 2 +- src/Registry.php | 208 +- .../complex-field-group/acf-export.json | 3896 +++++++++++++++++ tests/wpunit/AcfSettingsTest.php | 109 + .../wpunit/ComplexFlexAndCloneFieldsTest.php | 232 + tests/wpunit/FlexFieldsTest.php | 186 +- vendor/autoload.php | 2 +- vendor/composer/InstalledVersions.php | 4 +- vendor/composer/autoload_real.php | 8 +- vendor/composer/autoload_static.php | 8 +- vendor/composer/installed.php | 4 +- 22 files changed, 4823 insertions(+), 235 deletions(-) create mode 100644 tests/_data/field-group-exports/complex-field-group/acf-export.json create mode 100644 tests/wpunit/AcfSettingsTest.php create mode 100644 tests/wpunit/ComplexFlexAndCloneFieldsTest.php diff --git a/composer.json b/composer.json index 315d167..8d6ce47 100644 --- a/composer.json +++ b/composer.json @@ -78,8 +78,7 @@ "phpstan/phpstan": "^0.12.64", "szepeviktor/phpstan-wordpress": "^0.7.1", "codeception/module-rest": "^1.2", - "wp-graphql/wp-graphql-testcase": "^1.0", - "phpunit/phpunit": "9.4.1", + "wp-graphql/wp-graphql-testcase": "^2.0", "simpod/php-coveralls-mirror": "^3.0", "phpstan/extension-installer": "^1.1", "wp-graphql/wp-graphql": "^v1.3.5", diff --git a/src/AcfSettings.php b/src/AcfSettings.php index 098b788..92aa665 100644 --- a/src/AcfSettings.php +++ b/src/AcfSettings.php @@ -7,6 +7,9 @@ namespace WPGraphQL\ACF; +use Exception; +use WPGraphQL\Utils\Utils; + /** * Class ACF_Settings * @@ -47,20 +50,36 @@ public function init() { public function admin_table_columns_html( $column_name, $post_id ) { + + $field_group = acf_get_field_group( $post_id ); $location_rules = new LocationRules( [ $field_group ] ); $location_rules->determine_location_rules(); $rules = $location_rules->get_rules(); - $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $group_name = $field_group['graphql_field_name'] ?? $field_group['title']; $group_name = $location_rules->format_field_name( $group_name ); + $show_in_graphql = isset( $field_group['show_in_graphql'] ) && true === (bool) $field_group['show_in_graphql']; + switch( $column_name ) { case 'graphql_types': echo isset( $rules[ $group_name ] ) ? implode( ', ', $rules[ $group_name ] ) : ''; break; case 'graphql_field_name': - echo isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : ''; + $field_name = ! empty( $group_name ) ? $group_name : ''; + echo $show_in_graphql ? $field_name : ''; + break; + case 'graphql_type_name': + // @todo: add link to open fragment in GraphiQL + // I think a good way to do this would be to generate some sort of ID that is passed + // in the query params + // and when GraphiQL is loaded, the ID signals that the "default query" for + // GraphiQL should be the fragment for this, that way + // we don't have to do introspection in the WP List Table for all field groups + // and generate the fragment in the WP-Admin, we can generate it from + // GraphiQL when asked + echo $show_in_graphql ? Utils::format_type_name( $group_name ) : __( 'This Field Group is set to NOT show in the GraphQL Schema', 'wp-graphql-acf' ); break; default: break; @@ -68,9 +87,226 @@ public function admin_table_columns_html( $column_name, $post_id ) { } + /** + * Returns the query used to generate the Fragment for the Field Group + * + * @return string + */ + public function get_graphql_fragment_query() { + return ' + query getType($name: String!) { + __type(name: $name) { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + type { + ...TypeRef + fields { + ...FieldRef + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + + fragment FieldRef on __Field { + __typename + name + } + + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + } + '; + } + + /** + * Given an array of fields from introspection, this maps over and returns the field name or + * recursively maps again until leaf fields are determined + * + * @param array $fields Fields from introspection + * + * @return string[] + */ + public function map_fields_to_query( array $fields ) { + + // Map over the fields and return the field name, or continue mapping the nested fields + return array_map( function( $field ) { + + $excluded_names = [ 'pageInfo', 'edges' ]; + + if ( in_array( $field['name'], $excluded_names, true ) ) { + return null; + } + + if ( isset( $field['type']['fields'] ) && ! empty( $field['type']['fields'] ) ) { + + $mapped = $this->map_fields_to_query( $field['type']['fields'] ); + $mapped = array_filter( $mapped ); + + if ( empty( $mapped ) || ! is_array( $mapped ) ) { + $mapped = '__typename'; + } + + if ( is_array( $mapped ) && ! empty( $mapped ) ) { + $mapped = implode( ' ', $mapped ); + } else { + return null; + } + return $field['name'] . ' { ' . $mapped . ' } '; + + } + + if ( + ( isset( $field['type']['kind'] ) && 'SCALAR' === $field['type']['kind'] ) || + ( isset( $field['type']['ofType']['kind'] ) && 'SCALAR' === $field['type']['ofType']['kind'] ) + ) { + return $field['name'] . ' '; + } + + }, $fields ); + + } + + /** + * Given a GraphQL Type, this builds a Fragment for the Type + * + * @param string $type + * + * @return string + */ + public function build_fragment_from_introspection( array $type ) { + + $type_name = $type['name']; + $mapped = implode( ' ', $this->map_fields_to_query( $type['fields'] ) ); + + return "fragment {$type_name}_Fields on {$type_name} { + __typename + {$mapped} + }"; + + } + + /** + * Given a GraphQL TypeName, the Type is returned from Introspection + * + * @param string $type_name The name of the Type in the Schema to introspect + * + * @return mixed|array|null + * @throws Exception + */ + public function get_graphql_type_from_introspection( string $type_name ) { + + $get_type = graphql([ + 'query' => $this->get_graphql_fragment_query(), + 'variables' => [ + 'name' => $type_name + ] + ]); + + return $get_type['data']['__type'] ?? null; + + } + + public function get_fragment_from_type_name( $type_name ) { + + $type = $this->get_graphql_type_from_introspection( $type_name ); + + if ( empty( $type ) ) { + return ''; + } + + // Build the fragment + return $this->build_fragment_from_introspection( $type ); + + } + + /** + * Given a GraphQL Type Name, this creates a Fragment for it with several levels of nested fields + * + * @param string $type_name The name of the Type to generate a Fragment link for + * + * @return string + * @throws Exception + */ + public function get_graphiql_link( string $type_name ) { + + $fragment = $this->get_fragment_from_type_name( $type_name ); + + // Return the URL + return '/wp-admin/admin.php?page=graphiql-ide&explorerIsOpen=true&query=' . urlencode( wp_strip_all_tags( $fragment, null ) ); + + } + public function admin_table_columns( $columns ) { $columns['graphql_types'] = __( 'GraphQL Schema Location', 'wp-graphql-acf' ); $columns['graphql_field_name'] = __( 'GraphQL Field Name', 'wp-graphql-acf' ); + $columns['graphql_type_name'] = __( 'GraphQL Type Name', 'wp-graphql-acf' ); return $columns; } @@ -92,11 +328,11 @@ public function ajax_callback() { wp_send_json( __( 'No form data.', 'wp-graphql-acf' ) ); } - $field_group = isset( $form_data['acf_field_group'] ) ? $form_data['acf_field_group'] : []; + $field_group = $form_data['acf_field_group'] ?? []; $rules = new LocationRules( [ $field_group ] ); $rules->determine_location_rules(); - $group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $group_name = $field_group['graphql_field_name'] ?? $field_group['title']; $group_name = $rules->format_field_name( $group_name ); $all_rules = $rules->get_rules(); diff --git a/src/Fields/AcfField.php b/src/Fields/AcfField.php index 612be15..0d9cff3 100644 --- a/src/Fields/AcfField.php +++ b/src/Fields/AcfField.php @@ -138,6 +138,15 @@ public function get_parent_type() { return $this->registry->get_field_group_type_name( $this->field_group ); } + /** + * Returns the name of the Parent Type's fields Interface + * + * @return mixed|string|null + */ + public function get_parent_type_fields_interface() { + return ! empty( $this->get_parent_type() ) ? 'With_' . $this->get_parent_type() . '_Fields' : null; + } + /** * Returns the config array for the ACF Field * diff --git a/src/Fields/File.php b/src/Fields/File.php index fe63041..b4bd100 100644 --- a/src/Fields/File.php +++ b/src/Fields/File.php @@ -20,14 +20,14 @@ class File extends AcfField { */ public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); $connection_name = ucfirst( $type_name ) . 'ToSingleMediaItemConnection'; // If the connection already exists, don't register it again - if ( null !== $type_registry->get_type( $connection_name ) ) { + if ( $type_registry->get_type( $connection_name ) ) { return $connection_name; } diff --git a/src/Fields/FlexibleContent.php b/src/Fields/FlexibleContent.php index d0f24d7..d21c48f 100644 --- a/src/Fields/FlexibleContent.php +++ b/src/Fields/FlexibleContent.php @@ -26,10 +26,11 @@ public function get_graphql_type() { if ( isset( $this->field_config['layouts'] ) && is_array( $this->field_config['layouts'] ) ) { $layouts = array_map( function( $layout ) { $layout['parent'] = $this->field_config['key']; - $graphql_field_name = isset( $layout['graphql_field_name'] ) ? $layout['graphql_field_name'] : $layout['name']; + $graphql_field_name = $layout['graphql_field_name'] ?? $layout['name']; $graphql_field_name = $this->get_parent_type() . '_' . Utils::format_type_name( $this->field_name ) . '_' . Utils::format_type_name( $graphql_field_name ); $layout['graphql_field_name'] = $graphql_field_name; $layout['graphql_types'] = [ $this->get_parent_type() ]; + $layout['isFlexLayout'] = true; return $layout; }, $this->field_config['layouts'] ); } @@ -42,116 +43,36 @@ public function get_graphql_type() { $type_name = $parent_type . '_' . Utils::format_type_name( $this->field_config['name'] ); $layout_interface_name = $type_name . '_Layout'; - register_graphql_interface_type( $layout_interface_name, [ - 'description' => sprintf( __( 'Layouts of the %s Flexible Field Type', 'wp-graphql-acf' ), $layout_interface_name ), - 'fields' => [ - 'layoutName' => [ - 'type' => 'String', - 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), + if ( ! $this->registry->get_registered_field_group_interface( $this->field_config['key'] ) ) { + + $this->registry->get_type_registry()->register_interface_type( $layout_interface_name, [ + 'description' => sprintf( __( 'Layouts of the %s Flexible Field Type', 'wp-graphql-acf' ), $layout_interface_name ), + 'fields' => [ + 'layoutName' => [ + 'type' => 'String', + 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), + ], ], - ], - 'resolveType' => function( $object ) use ( $layouts, $type_name ) { - return $type_name . '_' . Utils::format_type_name( $object['acf_fc_layout'] ); - } - ] ); + 'resolveType' => function( $object ) use ( $layouts, $type_name ) { + return $type_name . '_' . Utils::format_type_name( $object['acf_fc_layout'] ); + } + ] ); + $this->registry->add_registered_field_group_interface( $this->field_config['key'], $layout_interface_name ); -// wp_send_json( [ $this->field_config, $this->field_group, $layouts ]); + } $registered = []; foreach ( $layouts as $layout ) { - $registered[] = $this->registry->add_acf_field_group_to_graphql( $layout, [ $parent_type ], [ $layout_interface_name ] ); + $registered[] = $this->registry->add_acf_field_group_to_graphql( $layout, [], [ $layout_interface_name ] ); } - -// if ( ! empty( $registered ) ) { -// register_graphql_interfaces_to_types( $layout_interface_name, $registered ); -// } - - // wp_send_json( [ $registered, $layout_interface_name, $this->registry->get_type_registry()->get_type( $registered[0] )->config ]); - -// wp_send_json( $registered_types ); - -// -// wp_send_json( [ 'list_of' => $registered[0] ] ); - - return [ 'list_of' => $registered[0] ]; - -// // Get the raw fields for the field group so we can -// // determine which fields are clones and which fields are not -// $raw_fields = acf_get_raw_fields( $this->field_config['key'] ); -// $layout_type_names = []; -// wp_send_json( $layouts ); -// -// /** -// * Iterate over the layouts to determine their GraphQL Type -// */ -// foreach ( $layouts as $layout ) { -// -// $cloned = false; -// -// if ( ! isset( $layout['name'] ) ) { -// continue; -// } -// -// foreach ( $raw_fields as $raw_field ) { -// if ( $layout['key'] === $raw_field['parent_layout'] ) { -// $layout['raw_fields'][] = $raw_field; -// if ( isset( $raw_field['clone'] ) && is_array( $raw_field['clone'] ) && 1 === count( $raw_field['clone'] ) ) { -// -// if ( 'Hero' === $raw_field['label'] ) { -// -// if ( false !== strpos( $raw_field['clone'][0], 'group_' ) ) { -// $cloned_group = acf_get_field_group( $raw_field['clone'][0] ); -// if ( is_array( $cloned_group ) && ! empty( $cloned_group ) ) { -// $cloned = true; -// $layout_type_names[] = $this->registry->get_field_group_type_name( $cloned_group ); -// } -// } -// -// } -// } -// } -// } -// -// if ( true !== $cloned ) { -// -// $layout_type_name = $type_name . '_' . Utils::format_type_name( $layout['name'] ); -// $layout['title'] = $layout['label']; -// $layout['graphql_field_name'] = $layout_type_name; -// -// -// if ( null === $this->registry->get_type_registry()->get_type( $layout_type_name ) ) { -// -// $this->registry->get_type_registry()->register_object_type( $layout_type_name, [ -// 'description' => sprintf( __( '%s Flexible Field Layout', 'wp-graphql' ), $layout_type_name ), -// 'interfaces' => [ 'AcfFieldGroup', $layout_interface_name ], -// 'fields' => [ -// 'layoutName' => [ -// 'type' => 'String', -// 'description' => __( 'The name of the flexible field layout', 'wp-graphql-acf' ), -// 'resolve' => function() use ( $layout ) { -// return isset( $layout['label'] ) ? $layout['label'] : null; -// } -// ], -// ] -// ] ); -// -// } -// -// $layout_type_names[] = $layout_type_name; -// -// } -// -// if ( ! empty( $layout_type_names ) ) { -// register_graphql_interfaces_to_types( $layout_interface_name, $layout_type_names ); -// } -// -// $this->registry->map_acf_fields_to_field_group( $layout ); -// -// } + if ( ! empty( $registered ) ) { + register_graphql_interfaces_to_types( $layout_interface_name, $registered ); + } return [ 'list_of' => $layout_interface_name ]; + } public function resolve( $node, array $args, AppContext $context, ResolveInfo $info ) { diff --git a/src/Fields/Gallery.php b/src/Fields/Gallery.php index 7211dd3..b27e82d 100644 --- a/src/Fields/Gallery.php +++ b/src/Fields/Gallery.php @@ -8,7 +8,7 @@ class Gallery extends AcfField { public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); diff --git a/src/Fields/Group.php b/src/Fields/Group.php index ccec4d4..2a9afc5 100644 --- a/src/Fields/Group.php +++ b/src/Fields/Group.php @@ -20,8 +20,9 @@ class Group extends AcfField { public function get_graphql_type() { $parent_type = $this->get_parent_type(); - $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); + $title = $this->field_config['title'] ?? ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); $this->field_config['graphql_field_name'] = $parent_type . '_' . Utils::format_type_name( $title ); + $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config ); return ! empty( $type_name ) ? $type_name : null; } diff --git a/src/Fields/PostObject.php b/src/Fields/PostObject.php index a97818e..c8f0827 100644 --- a/src/Fields/PostObject.php +++ b/src/Fields/PostObject.php @@ -13,7 +13,7 @@ class PostObject extends AcfField { */ public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); diff --git a/src/Fields/Relationship.php b/src/Fields/Relationship.php index 380ebaa..31004f0 100644 --- a/src/Fields/Relationship.php +++ b/src/Fields/Relationship.php @@ -8,7 +8,7 @@ class Relationship extends AcfField { public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); diff --git a/src/Fields/Repeater.php b/src/Fields/Repeater.php index c259811..2f0376a 100644 --- a/src/Fields/Repeater.php +++ b/src/Fields/Repeater.php @@ -21,11 +21,10 @@ class Repeater extends AcfField { */ public function get_graphql_type() { $parent_type = $this->get_parent_type(); - $title = isset( $this->field_config['title'] ) ? $this->field_config['title'] : ( isset( $this->field_config['label'] ) ? $this->field_config['label'] : 'no label or title' ); + $title = $this->field_config['title'] ?? ( $this->field_config['label'] ?? 'no label or title' ); $this->field_config['graphql_field_name'] = $parent_type . Utils::format_type_name( $title ); $type_name = $this->registry->add_acf_field_group_to_graphql( $this->field_config, [ $parent_type ] ); - register_graphql_object_type( $type_name, [] ); - return [ 'non_null' => [ 'list_of' => $this->field_config['graphql_field_name'] ] ]; + return [ 'non_null' => [ 'list_of' => $type_name ] ]; } /** diff --git a/src/Fields/Taxonomy.php b/src/Fields/Taxonomy.php index c5068ed..415ab05 100644 --- a/src/Fields/Taxonomy.php +++ b/src/Fields/Taxonomy.php @@ -20,7 +20,7 @@ class Taxonomy extends AcfField { */ public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); diff --git a/src/Fields/User.php b/src/Fields/User.php index e4425cb..e581da4 100644 --- a/src/Fields/User.php +++ b/src/Fields/User.php @@ -20,7 +20,7 @@ class User extends AcfField { */ public function get_graphql_type() { - $type_name = $this->get_parent_type(); + $type_name = $this->get_parent_type_fields_interface(); $type_registry = $this->registry->get_type_registry(); $connection_name = $this->registry->get_connection_name( $type_name, 'User', $this->field_name ); diff --git a/src/Registry.php b/src/Registry.php index d4a80e3..87fb766 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -105,10 +105,10 @@ public function init( TypeRegistry $type_registry ) { * * @param string $key * - * @return bool + * @return mixed|string|null */ public function get_registered_field_group( string $key ) { - return isset( $this->registered_field_groups[ $key ] ); + return $this->registered_field_groups[ $key ] ?? null; } /** @@ -124,12 +124,42 @@ public function add_registered_field_group( string $key, string $type_name ) { return $type_name; } - public function add_registered_field_group_interface( $key, $interface_name ) { - $this->registered_field_group_interface[ $key ] = $interface_name; + /** + * @param $key + * + * @return mixed + */ + public function get_registered_field_group_interface( string $key ) { + return $this->registered_field_group_interfaces[ $key ] ?? null; + } + + /** + * @param $key + * @param $interface_name + * @return string + */ + public function add_registered_field_group_interface( string $key, string $interface_name ) { + $this->registered_field_group_interfaces[ $key ] = $interface_name; + return $interface_name; + } + + /** + * @param $key + * + * @return mixed + */ + public function get_registered_field_group_fields_interface( string $key ) { + return $this->registered_field_group_fields_interfaces[ $key ] ?? null; } - public function add_registered_field_group_fields_interface( $key, $interface_name ) { - $this->registered_field_group_fields_interface[ $key ] = $interface_name; + /** + * @param $key + * @param $interface_name + * @return string + */ + public function add_registered_field_group_fields_interface( string $key, string $interface_name ) { + $this->registered_field_group_fields_interfaces[ $key ] = $interface_name; + return $interface_name; } /** @@ -383,7 +413,6 @@ public function add_acf_field_group_to_graphql( array $field_group, array $graph $field_group_name = ''; if ( isset( $field_group['graphql_field_name'] ) ) { - $field_group_name = $field_group['graphql_field_name']; } else if ( isset( $field_group['title'] ) ) { $field_group_name = $field_group['title']; @@ -400,7 +429,7 @@ public function add_acf_field_group_to_graphql( array $field_group, array $graph $type_name = $this->get_field_group_type_name( $field_group ); $interface_name = 'With_' . $type_name; - $fields_interface_name = $interface_name . '_' . 'Fields'; + $fields_interface_name = 'With_' . $type_name . '_Fields'; $this->type_registry->register_interface_type( $interface_name, [ 'description' => sprintf( __( 'Fields of the %s ACF Field Group', 'wp-graphql-acf' ), $field_group_name ), @@ -424,8 +453,6 @@ public function add_acf_field_group_to_graphql( array $field_group, array $graph ] ]; - // wp_send_json( [ $field_group, acf_get_raw_fields( $field_group['ID'] ) ] ); - $mapped_fields = $this->map_acf_fields_to_field_group( $field_group ); $mapped_fields = ! empty( $mapped_fields ) ? array_merge( $fields, $mapped_fields ) : $fields; @@ -434,99 +461,43 @@ public function add_acf_field_group_to_graphql( array $field_group, array $graph 'fields' => $mapped_fields ]); + $layout_interfaces = [ 'AcfFieldGroup', $fields_interface_name ]; + + if ( ! empty( $interfaces ) ) { + $interfaces = array_merge( $layout_interfaces, $interfaces ); + } else { + $interfaces = $layout_interfaces; + } + $this->type_registry->register_object_type( $type_name, [ - 'interfaces' => [ 'AcfFieldGroup', $fields_interface_name ], + 'interfaces' => $interfaces, 'fields' => $mapped_fields ]); - if ( empty( $graphql_types ) ) { - $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); + $is_flex_layout = false; + + if ( isset( $field_group['isFlexLayout'] ) && true === $field_group['isFlexLayout'] ) { + $is_flex_layout = true; } -// if ( 'Options' === $field_group['title'] ) { -// wp_send_json( [ $interface_name => $graphql_types ] ); -// } + // For flex layouts we want to leave the GraphQL Types empty + // As they don't need to be added as fields in the Schema independently + if ( empty( $graphql_types ) && ! $is_flex_layout ) { + $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); + } if ( ! empty( $graphql_types ) ) { register_graphql_interfaces_to_types( $interface_name, $graphql_types ); } -// if ( false !== strpos( $field_group['key'], 'layout_' ) ) { -// wp_send_json( [ 'layout', $graphql_types, $field_group, $type_name, $interface_name ] ); -// } // Add the interfaces to a Registry, so they can be identified by field group key $this->add_registered_field_group_interface( $field_group['key'], $interface_name ); $this->add_registered_field_group_fields_interface( $field_group['key'], $fields_interface_name ); - // Add the field group to the list of registered field groups return $this->add_registered_field_group( $field_group['key'], $type_name ); -// -// // Check if a GraphQL Type already exists for the Type -// if ( null !== $this->type_registry->get_type( $type_name ) ) { -// return $type_name; -// } -// -// $this->type_registry->register_object_type( $type_name, [ -// 'description' => __( 'Acf Field Group', 'wp-graphql' ), -// 'interfaces' => [ 'AcfFieldGroup', 'Node' ], -// 'fields' => [ -// 'id' => [ -// 'resolve' => function() use ( $field_group ) { -// return Relay::toGlobalId( 'AcfFieldGroup', $field_group['ID'] ); -// } -// ], -// '_fieldGroupConfig' => [ -// 'resolve' => function( $root ) use ( $field_group ) { -// return $field_group; -// }, -// ], -// 'fieldGroupName' => [ -// 'resolve' => function() use ( $type_name ) { -// return lcfirst( $type_name ); -// } -// ] -// ], -// ] ); -// -// $this->map_acf_fields_to_field_group( $field_group ); -// -// if ( empty( $graphql_types ) ) { -// $graphql_types = $this->get_graphql_types_for_field_group( $field_group ); -// } -// -// if ( empty( $graphql_types ) ) { -// $graphql_types = [ $type_name ]; -// } -// -// if ( ! empty( $graphql_types ) && is_array( $graphql_types ) ) { -// -// if ( null === $this->type_registry->get_type( $interface_name ) ) { -// -// $field_name = isset( $field_group['graphql_field_name'] ) ? lcfirst( $field_group['graphql_field_name'] ) : lcfirst( $type_name ); -// -// $this->type_registry->register_interface_type( $interface_name, [ -// 'description' => sprintf( __( 'A node that can have fields of the "%s" Field Group.', 'wp-graphql-acf' ), $type_name ), -// 'fields' => [ -// $field_name => [ -// 'type' => $type_name, -// 'description' => sprintf( __( 'Fields of the "%s" Field Group.', 'wp-graphql' ), $type_name ), -// 'resolve' => function( $root ) use ( $field_group ) { -// return ! empty( $root ) ? $root : $field_group; -// } -// ], -// ], -// ] ); -// -// } -// -// register_graphql_interfaces_to_types( [ $interface_name ], $graphql_types ); -// -// } -// -// return $type_name; } /** @@ -540,9 +511,9 @@ public function add_acf_field_group_to_graphql( array $field_group, array $graph */ public function get_graphql_types_for_field_group( array $field_group ) { - $graphql_types = isset( $field_group['graphql_types'] ) ? $field_group['graphql_types'] : []; + $graphql_types = $field_group['graphql_types'] ?? []; - $field_group_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $field_group_name = $field_group['graphql_field_name'] ?? $field_group['title']; $field_group_name = Utils::format_field_name( $field_group_name ); $manually_set_graphql_types = isset( $field_group['map_graphql_types_from_location_rules'] ) ? (bool) $field_group['map_graphql_types_from_location_rules'] : false; @@ -635,7 +606,7 @@ protected function should_field_group_show_in_graphql( array $field_group ) { * @return string */ public function get_field_group_type_name( array $field_group ) { - $type_name = isset( $field_group['graphql_field_name'] ) ? $field_group['graphql_field_name'] : $field_group['title']; + $type_name = $field_group['graphql_field_name'] ?? $field_group['title']; $type_name = ucfirst( $type_name ); return $type_name; @@ -684,7 +655,8 @@ public function get_supported_field_types() { 'color_picker', 'group', 'repeater', - 'flexible_content' + 'flexible_content', + 'clone' ]; /** @@ -709,19 +681,53 @@ public function map_acf_fields_to_field_group( array $field_group ) { // Get the ACF Fields for the specified field group $fields = isset( $field_group['sub_fields'] ) && is_array( $field_group['sub_fields'] ) ? $field_group['sub_fields'] : acf_get_fields( $field_group ); - // $raw_fields = acf_get_raw_fields( $field_group['ID'] ); - -// wp_send_json( [ -// 'groups' => $this->acf_field_groups, -// 'fields' => $fields, -// 'rawFields' => $raw_fields -// ] ); - // If there are no for the field group, do nothing. if ( empty( $fields ) || ! is_array( $fields ) ) { return []; } + $cloned_groups = []; + + foreach ( $fields as $field ) { + + if ( ! isset( $field['_clone'] ) ) { + continue; + } + + $field_config = acf_get_field( $field['_clone'] ); + + if ( ! isset( $field_config['clone'] ) || ! is_array( $field_config['clone' ] ) ) { + continue; + } + + foreach ( $field_config['clone'] as $cloned ) { + if ( 0 !== strpos( $cloned, 'group_' ) ) { + continue; + } + + if ( isset( $groups[ $cloned ] ) ) { + continue; + } + + $cloned_field_group = acf_get_field_group( $cloned ); + $cloned_groups[ $cloned ] = 'With_' . $this->get_field_group_type_name( $cloned_field_group ) . '_Fields'; + + } + + } + + if ( ! empty( $cloned_groups ) ) { + + graphql_debug( [ + 'register_graphql_interfaces_to_types', + array_values( $cloned_groups ), + $this->get_field_group_type_name( $field_group ) + ]); + + register_graphql_interfaces_to_types( array_values( $cloned_groups ), [ $this->get_field_group_type_name( $field_group ) ]); + // graphql_debug( [ $this->get_field_group_type_name( $field_group ), $cloned_groups ] ); + } + $mapped_fields = []; // Store a list of field keys that have been registered @@ -737,7 +743,7 @@ public function map_acf_fields_to_field_group( array $field_group ) { } // If a field doesn't have a name or key, it's not valid - if ( ! isset( $field['name'] ) || ! isset( $field['key'] ) ) { + if ( ! isset( $field['name'], $field['key'] ) ) { continue; } @@ -778,7 +784,7 @@ public function map_acf_fields_to_field_group( array $field_group ) { */ public function map_graphql_field( array $field, array $field_group ) { - $field_type = isset( $field['type'] ) ? $field['type'] : null; + $field_type = $field['type'] ?? null; $class_name = Utils::format_type_name( $field_type ); $class_name = '\\WPGraphQL\\ACF\Fields\\' . $class_name; @@ -793,13 +799,13 @@ public function map_graphql_field( array $field, array $field_group ) { $field = new $class_name( $field, $field_group, $this ); if ( $field instanceof AcfField ) { return $field; - } else { - return new AcfField( $field, $field_group, $this ); } - } else { + return new AcfField( $field, $field_group, $this ); } + return new AcfField( $field, $field_group, $this ); + } } diff --git a/tests/_data/field-group-exports/complex-field-group/acf-export.json b/tests/_data/field-group-exports/complex-field-group/acf-export.json new file mode 100644 index 0000000..7fe35c4 --- /dev/null +++ b/tests/_data/field-group-exports/complex-field-group/acf-export.json @@ -0,0 +1,3896 @@ +[ + { + "key": "group_5fedf8bf735af", + "title": "Project Details", + "fields": [ + { + "key": "field_5fedf8dddd6f8", + "label": "Details", + "name": "details", + "type": "repeater", + "instructions": "Add pertinent details about the project", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_5fedf8eedd6f9", + "min": 0, + "max": 0, + "layout": "block", + "button_label": "Add Detail", + "sub_fields": [ + { + "key": "field_5fedf8eedd6f9", + "label": "Title", + "name": "title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fedf8f8dd6fa", + "label": "Content", + "name": "content", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "project" + } + ] + ], + "menu_order": -1, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectDetails" + }, + { + "key": "group_5fce88119bcf2", + "title": "Background Image with Content", + "fields": [ + { + "key": "field_5fce884513772", + "label": "Background Image", + "name": "background_image", + "type": "image", + "instructions": "Upload or select image for use as section background. If link is included below, image will change to full color on hover, so be sure to upload a color image.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5fea57ad0007a", + "label": "Section Headline", + "name": "section_headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fce887313773", + "label": "Section Content", + "name": "section_content", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 4, + "new_lines": "" + }, + { + "key": "field_5fce889a13774", + "label": "Section Link", + "name": "section_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "Full width image section with text overlay", + "show_in_graphql": 1, + "graphql_field_name": "acfFullImageComponent" + }, + { + "key": "group_5fecf293ca9b2", + "title": "Background Shade", + "fields": [ + { + "key": "field_5fecf2c91082e", + "label": "Shade", + "name": "shade", + "type": "true_false", + "instructions": "Add light gray background to section.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "message": "Add background shade?", + "default_value": 0, + "ui": 0, + "ui_on_text": "", + "ui_off_text": "" + }, + { + "key": "field_60198deb9dc35", + "label": "Component Padding", + "name": "padding", + "type": "group", + "instructions": "Provide vertical padding for section interior when needed.", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_5fecf2c91082e", + "operator": "==", + "value": "1" + } + ] + ], + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layout": "block", + "sub_fields": [ + { + "key": "field_60198e089dc36", + "label": "Padding Top", + "name": "padding_top", + "type": "button_group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "default": "Default (50px)", + "pt0": "0px", + "pt2": "25px" + }, + "allow_null": 0, + "default_value": "default", + "layout": "horizontal", + "return_format": "value" + }, + { + "key": "field_60198e3f9dc37", + "label": "Padding Bottom", + "name": "padding_bottom", + "type": "button_group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "default": "Default (50px)", + "pb0": "0px", + "pb2": "25px" + }, + "allow_null": 0, + "default_value": "default", + "layout": "horizontal", + "return_format": "value" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "Boolean for displaying background shade", + "show_in_graphql": 1, + "graphql_field_name": "acfBackgroundShade" + }, + { + "key": "group_5feb58ee935c4", + "title": "Circular Image Row", + "fields": [ + { + "key": "field_5feb591cf9a69", + "label": "Items", + "name": "row_items", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_5feb598ef9a6b", + "min": 2, + "max": 0, + "layout": "block", + "button_label": "Add Item", + "sub_fields": [ + { + "key": "field_5feb597ef9a6a", + "label": "Image", + "name": "image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5feb598ef9a6b", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5feb599ef9a6c", + "label": "Sub Headline", + "name": "subheadline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5feb59b1f9a6d", + "label": "Item Content", + "name": "item_content", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 4, + "new_lines": "" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfCircleRowComponent" + }, + { + "key": "group_6023fa60f3606", + "title": "Contact Page", + "fields": [ + { + "key": "field_6023fa85e6ecb", + "label": "Headline", + "name": "headline", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 3, + "new_lines": "br" + }, + { + "key": "field_6023fab4e6ecc", + "label": "Locations", + "name": "locations", + "type": "group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layout": "block", + "sub_fields": [ + { + "key": "field_6023fae0e6ecd", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_6023faf8e6ece", + "label": "Facility", + "name": "facility", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "", + "min": 0, + "max": 0, + "layout": "table", + "button_label": "", + "sub_fields": [ + { + "key": "field_6023fb08e6ecf", + "label": "Title", + "name": "title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_6023fb14e6ed0", + "label": "Address", + "name": "address", + "type": "wysiwyg", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "tabs": "all", + "toolbar": "basic", + "media_upload": 0, + "delay": 0 + }, + { + "key": "field_6023fb49e6ed2", + "label": "Phone", + "name": "phone", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_6023fb51e6ed3", + "label": "Fax", + "name": "fax", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + } + ] + } + ] + }, + { + "key": "field_6023fb93e6ed4", + "label": "Contacts", + "name": "contacts", + "type": "group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layout": "block", + "sub_fields": [ + { + "key": "field_6023fba1e6ed5", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_6023fbb0e6ed6", + "label": "Contact", + "name": "contact", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_6023fbb9e6ed7", + "min": 0, + "max": 0, + "layout": "table", + "button_label": "", + "sub_fields": [ + { + "key": "field_6023fbb9e6ed7", + "label": "Title", + "name": "title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_6023fbc1e6ed8", + "label": "Email", + "name": "email", + "type": "email", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "" + } + ] + }, + { + "key": "field_6023fbe1e6ed9", + "label": "Social", + "name": "social", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_6023fc45e6edb", + "min": 0, + "max": 0, + "layout": "table", + "button_label": "Add Social Icon", + "sub_fields": [ + { + "key": "field_6023fc36e6eda", + "label": "Icon", + "name": "icon", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_6023fc45e6edb", + "label": "Link", + "name": "link", + "type": "url", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "" + } + ] + } + ] + } + ], + "location": [ + [ + { + "param": "page", + "operator": "==", + "value": "36" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfContactPage" + }, + { + "key": "group_5fce89c5b3912", + "title": "Content Component", + "fields": [ + { + "key": "field_5fce89ed0825f", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fce89f808260", + "label": "Content", + "name": "content", + "type": "wysiwyg", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "tabs": "all", + "toolbar": "basic", + "media_upload": 0, + "delay": 0 + }, + { + "key": "field_6032c5cbdf717", + "label": "Columns", + "name": "list_columns", + "type": "true_false", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "message": "Display list items in columns", + "default_value": 0, + "ui": 0, + "ui_on_text": "", + "ui_off_text": "" + }, + { + "key": "field_5fce8a1508261", + "label": "Section Link", + "name": "section_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "Headline + copy section", + "show_in_graphql": 1, + "graphql_field_name": "acfContentComponent" + }, + { + "key": "group_5f7cbbc9a7b58", + "title": "Global Settings", + "fields": [ + { + "key": "field_5f7cbbf28b673", + "label": "Logo", + "name": "logo", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "medium", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5fda2666e2179", + "label": "Login Link", + "name": "login_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + }, + { + "key": "field_5fda2618e2178", + "label": "Address", + "name": "address", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 4, + "new_lines": "" + }, + { + "key": "field_6026dbc62e47f", + "label": "Fallback Meta Image", + "name": "fallback_meta_image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + } + ], + "location": [ + [ + { + "param": "options_page", + "operator": "==", + "value": "global-settings" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": true, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "globalSettings" + }, + { + "key": "group_5f7e16238aedf", + "title": "Hero", + "fields": [ + { + "key": "field_5f7e1632abb71", + "label": "Hero Image", + "name": "hero_image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "medium", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5f7e1666abb72", + "label": "Hero Headline", + "name": "hero_headline", + "type": "text", + "instructions": "Add headline. You can wrap word(s) in <span class=\"yellow\"> to color them with LeJeune yellow", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fc7cc6579e9e", + "label": "Hero Sub Headline", + "name": "hero_sub_headline", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 4, + "new_lines": "" + }, + { + "key": "field_5fc7cf6948369", + "label": "Has CTA", + "name": "has_cta", + "type": "true_false", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "message": "Include CTA?", + "default_value": 0, + "ui": 0, + "ui_on_text": "", + "ui_off_text": "" + }, + { + "key": "field_5fce92774b177", + "label": "CTA Type", + "name": "cta_type", + "type": "select", + "instructions": "Choose Type of CTA", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_5fc7cf6948369", + "operator": "==", + "value": "1" + } + ] + ], + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "link": "Link", + "video": "Video" + }, + "default_value": false, + "allow_null": 1, + "multiple": 0, + "ui": 0, + "return_format": "value", + "ajax": 0, + "placeholder": "" + }, + { + "key": "field_5fc7cf874836a", + "label": "Hero Link", + "name": "link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_5fc7cf6948369", + "operator": "==", + "value": "1" + }, + { + "field": "field_5fce92774b177", + "operator": "==", + "value": "link" + } + ] + ], + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + }, + { + "key": "field_603e8b5421811", + "label": "Hero Video", + "name": "hero_video", + "type": "text", + "instructions": "Enter URL for hosted video", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_5fc7cf6948369", + "operator": "==", + "value": "1" + }, + { + "field": "field_5fce92774b177", + "operator": "==", + "value": "video" + } + ] + ], + "wrapper": { + "width": "33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfHero" + }, + { + "key": "group_5fce85b420cd6", + "title": "Icons Component", + "fields": [ + { + "key": "field_5fce864a0e510", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fce865f0e511", + "label": "Icon Repeater", + "name": "icon_repeater", + "type": "repeater", + "instructions": "Create row of icons. IMPORTANT: Front-end only accepts SVG file type for output, so make sure to upload SVGs below.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_5fce86860e512", + "min": 0, + "max": 0, + "layout": "block", + "button_label": "Add Icon", + "sub_fields": [ + { + "key": "field_5fce869a0e513", + "label": "Icon", + "name": "icon", + "type": "image", + "instructions": "Upload an SVG", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "svg" + }, + { + "key": "field_5fce86860e512", + "label": "Icon Title", + "name": "icon_title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_lofgic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fce86b60e514", + "label": "Icon Link", + "name": "icon_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + } + ] + }, + { + "key": "field_5fce86fd0e515", + "label": "Link Elsewhere", + "name": "link_elsewhere", + "type": "true_false", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "message": "Link to Another Page?", + "default_value": 0, + "ui": 0, + "ui_on_text": "", + "ui_off_text": "" + }, + { + "key": "field_5fce87310e516", + "label": "Section Link", + "name": "section_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_5fce86fd0e515", + "operator": "==", + "value": "1" + } + ] + ], + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfIconsComponent" + }, + { + "key": "group_5fce9442d5581", + "title": "Multi Column Component", + "fields": [ + { + "key": "field_5fcfb69aa763a", + "label": "Section Headline", + "name": "section_headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fce987f8d53c", + "label": "Section Link", + "name": "section_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + }, + { + "key": "field_5fcfb51507336", + "label": "Columns", + "name": "columns", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "field_5fcfb56c07338", + "min": 2, + "max": 4, + "layout": "block", + "button_label": "Add Column", + "sub_fields": [ + { + "key": "field_5fcfb55807337", + "label": "Image", + "name": "column_image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5fcfb56c07338", + "label": "Title", + "name": "column_title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fcfb5910733a", + "label": "Link", + "name": "column_link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "33.33", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + }, + { + "key": "field_5fcfb57a07339", + "label": "Content", + "name": "column_content", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "maxlength": "", + "rows": 4, + "new_lines": "" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "permalink", + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfMultiColumnComponent" + }, + { + "key": "group_5f7e17665ae67", + "title": "Page Builder", + "fields": [ + { + "key": "field_5f7e1791756e9", + "label": "Components", + "name": "components", + "type": "flexible_content", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layouts": { + "layout_5f7e1809d7739": { + "key": "layout_5f7e1809d7739", + "name": "hero", + "label": "Hero", + "display": "block", + "sub_fields": [ + { + "key": "field_5f7e182b756ea", + "label": "Hero", + "name": "hero", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5f7e16238aedf" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fce8a3da96a4": { + "key": "layout_5fce8a3da96a4", + "name": "content_section", + "label": "Content Section", + "display": "block", + "sub_fields": [ + { + "key": "field_60199058e70b6", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fecf4118fd25", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60199109ec139", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_6019906ae70b7", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fce8a47a96a5", + "label": "Content Section", + "name": "content_section", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "clone": [ + "group_5fce89c5b3912" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fc7d1fe93bb2": { + "key": "layout_5fc7d1fe93bb2", + "name": "image_with_content", + "label": "Image with Content", + "display": "block", + "sub_fields": [ + { + "key": "field_60199078e70b8", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fecf4c382f55", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60199132ec13a", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_6019908ae70b9", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fc7d20a93bb3", + "label": "Image with Content", + "name": "imgWithContent", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "clone": [ + "group_5fc7ca09bcb0f" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fce87677d38a": { + "key": "layout_5fce87677d38a", + "name": "icons_component", + "label": "Icons Component", + "display": "block", + "sub_fields": [ + { + "key": "field_60199094e70ba", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fecf47082f52", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60199149ec13b", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_6019909fe70bb", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fce87757d38b", + "label": "Icons Component", + "name": "iconsComponent", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fce85b420cd6" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fce88d2e8d9e": { + "key": "layout_5fce88d2e8d9e", + "name": "background_image_with_text", + "label": "Background Image with Text", + "display": "block", + "sub_fields": [ + { + "key": "field_5fce88e6e8d9f", + "label": "Full Width Image", + "name": "full_width_image", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fce88119bcf2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fce96a2784f7": { + "key": "layout_5fce96a2784f7", + "name": "multi_column", + "label": "Multi Column with Image", + "display": "block", + "sub_fields": [ + { + "key": "field_60198fa3fa2be", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fecf49782f53", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60197c263cffe", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60198fc2fa2bf", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fce96ad784f8", + "label": "Three Column", + "name": "three_column", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fce9442d5581" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5ff4c73f6860b": { + "key": "layout_5ff4c73f6860b", + "name": "multi_column_content", + "label": "Multi Column Content Only", + "display": "block", + "sub_fields": [ + { + "key": "field_601990b0e70bc", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5ff4c75b6860c", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60199171ec13c", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_601990b9e70bd", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5ff4c77c6860d", + "label": "Multi Column Content Only", + "name": "multi_column_content", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5ff4c66dc6cc3" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fea4e74f8566": { + "key": "layout_5fea4e74f8566", + "name": "stat_component", + "label": "Stat Component", + "display": "block", + "sub_fields": [ + { + "key": "field_601990c8e70be", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fecf4ac82f54", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_60199195ec13d", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_601990d2e70bf", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fea4e84f8567", + "label": "Stat Component", + "name": "stat_component", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fea4d8beff77" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5feb5a68ccd5e": { + "key": "layout_5feb5a68ccd5e", + "name": "circular_image_row", + "label": "Circular Image Row", + "display": "block", + "sub_fields": [ + { + "key": "field_5feb5a89ccd5f", + "label": "Circular Image Row", + "name": "circular_image_row", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5feb58ee935c4" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5ff369e0f7468": { + "key": "layout_5ff369e0f7468", + "name": "project_carousel_pages", + "label": "Project Carousel", + "display": "block", + "sub_fields": [ + { + "key": "field_5ff369edf7469", + "label": "Project Carousel", + "name": "project_carousel_pages", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5ff36850095be" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_60099d9bc9235": { + "key": "layout_60099d9bc9235", + "name": "projects", + "label": "Projects", + "display": "block", + "sub_fields": [ + { + "key": "field_60099dacc9236", + "label": "Projects", + "name": "projects", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5ff88c842f967" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_6023fc981cca3": { + "key": "layout_6023fc981cca3", + "name": "contact_page", + "label": "Contact Page", + "display": "block", + "sub_fields": [ + { + "key": "field_6023fca11cca4", + "label": "Contact Page", + "name": "contact_page", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_6023fa60f3606" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + } + }, + "button_label": "Add Component", + "min": "", + "max": "" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "page" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": true, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfPageBuilder" + }, + { + "key": "group_60d109596e399", + "title": "Post Fields", + "fields": [ + { + "key": "field_60d1096121224", + "label": "text", + "name": "text", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_60d1096821225", + "label": "image", + "name": "image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "medium", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": true, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "postFields", + "map_graphql_types_from_location_rules": 0, + "graphql_types": "" + }, + { + "key": "group_5ff343799f06b", + "title": "Project Carousel", + "fields": [ + { + "key": "field_5ff343ce9d131", + "label": "Project Name", + "name": "project_name", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5ff349269bfb6", + "label": "Images", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 0, + "placement": "left", + "endpoint": 0 + }, + { + "key": "field_5ff347be9d132", + "label": "Project Images", + "name": "project_images", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fedf9c87fca8" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_5ff3493d9bfb7", + "label": "Details", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 0, + "placement": "left", + "endpoint": 0 + }, + { + "key": "field_5ff347db9d133", + "label": "Project Details", + "name": "project_details", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fedf8bf735af" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectCarousel" + }, + { + "key": "group_5ff36850095be", + "title": "Project Carousel for Pages", + "fields": [ + { + "key": "field_5ffe15c033d7f", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5ff3687f3e1d6", + "label": "Featured Projects", + "name": "featured_projects", + "type": "relationship", + "instructions": "Select up to 5 projects to feature in carousel.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "post_type": [ + "project" + ], + "taxonomy": "", + "filters": [ + "search", + "taxonomy" + ], + "elements": "", + "min": 1, + "max": 5, + "return_format": "object" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "page" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectCarouselPages" + }, + { + "key": "group_5ff88c842f967", + "title": "Project Vertical", + "fields": [ + { + "key": "field_60099e0ce46fa", + "label": "Title", + "name": "title", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5ff88e7ecffd2", + "label": "Projects", + "name": "projects", + "type": "relationship", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "post_type": [ + "project" + ], + "taxonomy": "", + "filters": [ + "search", + "taxonomy" + ], + "elements": "", + "min": "", + "max": "", + "return_format": "object" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "page" + }, + { + "param": "page", + "operator": "==", + "value": "28" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectVertical" + }, + { + "key": "group_5fedfb4b1eadb", + "title": "Related Projects", + "fields": [ + { + "key": "field_5fedfb5c0084c", + "label": "Related Projects", + "name": "related_projects", + "type": "relationship", + "instructions": "Choose up to six related projects", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "post_type": [ + "project" + ], + "taxonomy": "", + "filters": [ + "search", + "taxonomy" + ], + "elements": "", + "min": "", + "max": 2, + "return_format": "object" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "project" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfRelatedProjects" + }, + { + "key": "group_60087adf59a40", + "title": "Resource Builder", + "fields": [ + { + "key": "field_60087b1fe0236", + "label": "Resource Builder", + "name": "resource_builder", + "type": "flexible_content", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layouts": { + "layout_60087db2dd04c": { + "key": "layout_60087db2dd04c", + "name": "content", + "label": "Content", + "display": "block", + "sub_fields": [ + { + "key": "field_60087dbadd04d", + "label": "Content", + "name": "content", + "type": "wysiwyg", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "tabs": "all", + "toolbar": "full", + "media_upload": 1, + "delay": 0 + } + ], + "min": "", + "max": "" + }, + "layout_60087dd0dd04e": { + "key": "layout_60087dd0dd04e", + "name": "full_width_image", + "label": "Full Width Image", + "display": "block", + "sub_fields": [ + { + "key": "field_60087dd8dd04f", + "label": "Full Width Image", + "name": "full_width_image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + } + ], + "min": "", + "max": "" + }, + "layout_60087deedd050": { + "key": "layout_60087deedd050", + "name": "button", + "label": "Button", + "display": "block", + "sub_fields": [ + { + "key": "field_60087e19dd051", + "label": "Link Type", + "name": "link_type", + "type": "radio", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "link": "Link", + "file": "File" + }, + "allow_null": 0, + "other_choice": 0, + "default_value": "", + "layout": "horizontal", + "return_format": "value", + "save_other_choice": 0 + }, + { + "key": "field_60087e57dd052", + "label": "Link", + "name": "link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_60087e19dd051", + "operator": "==", + "value": "link" + } + ] + ], + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + }, + { + "key": "field_60087e65dd053", + "label": "File", + "name": "file", + "type": "file", + "instructions": "", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_60087e19dd051", + "operator": "==", + "value": "file" + } + ] + ], + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "library": "all", + "min_size": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_60087e7bdd054", + "label": "Button Text", + "name": "button_text", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": [ + [ + { + "field": "field_60087e19dd051", + "operator": "==", + "value": "file" + } + ] + ], + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + } + ], + "min": "", + "max": "" + }, + "layout_60087f280d16c": { + "key": "layout_60087f280d16c", + "name": "related_resources", + "label": "Related Resources", + "display": "block", + "sub_fields": [ + { + "key": "field_60119f2dd43e3", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_60087f320d16d", + "label": "Related Resources", + "name": "related_resources", + "type": "relationship", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "post_type": [ + "resource" + ], + "taxonomy": "", + "filters": [ + "search", + "taxonomy" + ], + "elements": "", + "min": "", + "max": 2, + "return_format": "object" + } + ], + "min": "", + "max": "1" + } + }, + "button_label": "Add Component", + "min": "", + "max": "" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "resource" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "featured_image", + "send-trackbacks" + ], + "active": true, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfResourceBuilder" + }, + { + "key": "group_5fc7ca09bcb0f", + "title": "Side-by-Side Image\/Content", + "fields": [ + { + "key": "field_5fc7ca1ff4fbc", + "label": "Image Upload", + "name": "image_upload", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + }, + { + "key": "field_5fc7ca9df4fbf", + "label": "Image Side", + "name": "image_side", + "type": "radio", + "instructions": "Choose which side the image will display", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "left": "Left", + "right": "Right" + }, + "allow_null": 0, + "other_choice": 0, + "default_value": "left", + "layout": "horizontal", + "return_format": "value", + "save_other_choice": 0 + }, + { + "key": "field_5fc7ca67f4fbd", + "label": "Headline", + "name": "headline", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "Max 50 characters", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fc7ca85f4fbe", + "label": "Content", + "name": "content", + "type": "wysiwyg", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "tabs": "all", + "toolbar": "basic", + "media_upload": 0, + "delay": 0 + }, + { + "key": "field_5fc7d1452298a", + "label": "Link", + "name": "link", + "type": "link", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfImageContent" + }, + { + "key": "group_5fea4d8beff77", + "title": "Stat Component", + "fields": [ + { + "key": "field_5fea4daa5fd82", + "label": "Stats", + "name": "stats", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "", + "min": 2, + "max": 4, + "layout": "table", + "button_label": "Add Stat", + "sub_fields": [ + { + "key": "field_5fea4df55fd83", + "label": "Stat Count", + "name": "stat_count", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + }, + { + "key": "field_5fea4e405fd84", + "label": "Stat Description", + "name": "stat_description", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "default_value": "", + "placeholder": "", + "prepend": "", + "append": "", + "maxlength": "" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "page" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfStatComponent" + }, + { + "key": "group_60197b43a8abc", + "title": "Vertical Margin", + "fields": [ + { + "key": "field_60198a39174e9", + "label": "Margins", + "name": "margins", + "type": "group", + "instructions": "Provide vertical spacing outside section when needed.", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layout": "block", + "sub_fields": [ + { + "key": "field_60198a5d174ea", + "label": "Top Margin", + "name": "margin_top", + "type": "button_group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "default": "Default (0px)", + "mt2": "25px", + "mt5": "50px", + "mt10": "100px" + }, + "allow_null": 0, + "default_value": "default", + "layout": "horizontal", + "return_format": "value" + }, + { + "key": "field_60198b26188a2", + "label": "Bottom Margin", + "name": "margin_bottom", + "type": "button_group", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "50", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "choices": { + "default": "Default (100px)", + "mb0": "0px", + "mb2": "25px", + "mb5": "50px" + }, + "allow_null": 0, + "default_value": "default", + "layout": "horizontal", + "return_format": "value" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "post" + } + ] + ], + "menu_order": 0, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "Options for vertical spacing", + "show_in_graphql": 1, + "graphql_field_name": "acfVerticalSpacing" + }, + { + "key": "group_5fedf5817fcaa", + "title": "Project Builder", + "fields": [ + { + "key": "field_5fedf5d16f777", + "label": "Components", + "name": "components", + "type": "flexible_content", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "layouts": { + "layout_5fedf5d9ecd19": { + "key": "layout_5fedf5d9ecd19", + "name": "hero", + "label": "Hero", + "display": "block", + "sub_fields": [ + { + "key": "field_5fedf5f56f778", + "label": "Hero", + "name": "hero", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5f7e16238aedf" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fedf6336f779": { + "key": "layout_5fedf6336f779", + "name": "content_section", + "label": "Content Section", + "display": "block", + "sub_fields": [ + { + "key": "field_601af7af227d7", + "label": "Layout and Style", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fedf6416f77a", + "label": "Shade", + "name": "shade", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fecf293ca9b2" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_601af7d5227d9", + "label": "Margins", + "name": "margins", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_60197b43a8abc" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + }, + { + "key": "field_601af7bb227d8", + "label": "Content", + "name": "", + "type": "tab", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "placement": "top", + "endpoint": 0 + }, + { + "key": "field_5fedf6526f77b", + "label": "Content Section", + "name": "content_section", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fce89c5b3912" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5ff3480674076": { + "key": "layout_5ff3480674076", + "name": "project_carousel", + "label": "Project Carousel", + "display": "block", + "sub_fields": [ + { + "key": "field_5ff3480d74077", + "label": "Project Carousel", + "name": "project_carousel", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5ff343799f06b" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + }, + "layout_5fedfc4d60e96": { + "key": "layout_5fedfc4d60e96", + "name": "related_projects", + "label": "Related Projects", + "display": "block", + "sub_fields": [ + { + "key": "field_5fedfc5760e97", + "label": "Related Projects", + "name": "related_projects", + "type": "clone", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "clone": [ + "group_5fedfb4b1eadb" + ], + "display": "seamless", + "layout": "block", + "prefix_label": 0, + "prefix_name": 0 + } + ], + "min": "", + "max": "" + } + }, + "button_label": "Add Component", + "min": "", + "max": "" + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "project" + } + ] + ], + "menu_order": 2, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": [ + "the_content", + "excerpt", + "discussion", + "comments", + "revisions", + "slug", + "author", + "format", + "page_attributes", + "featured_image", + "categories", + "tags", + "send-trackbacks" + ], + "active": true, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectBuilder" + }, + { + "key": "group_5fedf9c87fca8", + "title": "Project Images", + "fields": [ + { + "key": "field_5fedf9e086435", + "label": "Images", + "name": "images", + "type": "repeater", + "instructions": "Add images for project carousel", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "collapsed": "", + "min": 0, + "max": 0, + "layout": "row", + "button_label": "Add Image", + "sub_fields": [ + { + "key": "field_5fedf9f286436", + "label": "Image", + "name": "image", + "type": "image", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "show_in_graphql": 1, + "return_format": "array", + "preview_size": "thumbnail", + "library": "all", + "min_width": "", + "min_height": "", + "min_size": "", + "max_width": "", + "max_height": "", + "max_size": "", + "mime_types": "" + } + ] + } + ], + "location": [ + [ + { + "param": "post_type", + "operator": "==", + "value": "project" + } + ] + ], + "menu_order": 2, + "position": "normal", + "style": "default", + "label_placement": "top", + "instruction_placement": "label", + "hide_on_screen": "", + "active": false, + "description": "", + "show_in_graphql": 1, + "graphql_field_name": "acfProjectImages" + } +] diff --git a/tests/wpunit/AcfSettingsTest.php b/tests/wpunit/AcfSettingsTest.php new file mode 100644 index 0000000..2b079f2 --- /dev/null +++ b/tests/wpunit/AcfSettingsTest.php @@ -0,0 +1,109 @@ +group_key = __CLASS__; + $this->clearSchema(); + $this->register_acf_field_group(); + parent::setUp(); + // your set up methods here + } + + public function tearDown(): void { + $this->deregister_acf_field_group(); + // your tear down methods here + $this->clearSchema(); + // then + parent::tearDown(); + } + + public function deregister_acf_field_group() { + acf_remove_local_field_group( $this->group_key ); + } + + public function register_acf_field_group( $config = [] ) { + + $defaults = [ + 'key' => $this->group_key, + 'title' => 'Post Object Fields', + 'fields' => [], + 'location' => [ + [ + [ + 'param' => 'post_type', + 'operator' => '==', + 'value' => 'post', + ], + ], + ], + 'menu_order' => 0, + 'position' => 'normal', + 'style' => 'default', + 'label_placement' => 'top', + 'instruction_placement' => 'label', + 'hide_on_screen' => '', + 'active' => true, + 'description' => '', + 'show_in_graphql' => 1, + 'graphql_field_name' => 'postFieldsSettingsTest', + 'graphql_types' => [ 'Post' ] + ]; + + acf_add_local_field_group( array_merge( $defaults, $config ) ); + + } + + public function register_acf_field( $config = [] ) { + + $defaults = [ + 'parent' => $this->group_key, + 'key' => 'field_5d7812ert5tg', + 'label' => 'Text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ]; + + acf_add_local_field( array_merge( $defaults, $config ) ); + } + + public function testBuildFragmentFromIntrospection() { + + $this->register_acf_field([ + 'name' => 'textFieldTest', + 'type' => 'text', + ]); + + $this->clearSchema(); + + $type_name = 'PostFieldsSettingsTest'; + $settings = new \WPGraphQL\ACF\AcfSettings(); + $fragment = $settings->get_graphql_type_from_introspection( $type_name ); + + codecept_debug( $fragment ); + + $this->assertSame( $type_name, $fragment['name'] ); + + } + +} diff --git a/tests/wpunit/ComplexFlexAndCloneFieldsTest.php b/tests/wpunit/ComplexFlexAndCloneFieldsTest.php new file mode 100644 index 0000000..2cc45e3 --- /dev/null +++ b/tests/wpunit/ComplexFlexAndCloneFieldsTest.php @@ -0,0 +1,232 @@ +import_field_groups( $this->import_file ); + + $this->group_key = __CLASS__; + WPGraphQL::clear_schema(); + + $this->post_id = $this->factory()->post->create( [ + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_title' => 'Test', + 'post_content' => 'test', + ] ); + + $this->page_id = $this->factory()->post->create( [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Test Page', + 'post_content' => 'test Page Content...', + ] ); + + $this->test_image = dirname( __FILE__, 2 ) . '/_data/images/test.png'; + + + } + + public function tearDown(): void { + + $this->remove_imported_acf_field_groups(); + + WPGraphQL::clear_schema(); + wp_delete_post( $this->post_id, true ); + parent::tearDown(); // TODO: Change the autogenerated stub + } + + public function remove_imported_acf_field_groups() { + + if ( ! empty( $this->imported_group_ids ) ) { + foreach ( $this->imported_group_ids as $id ) { + acf_delete_field_group( $id ); + } + } + } + + public function import_field_groups( string $import_file ) { + + $file = dirname( __FILE__, 2 ) . '/_data/field-group-exports/' . $import_file ; + + $json = file_get_contents( $file ); + $json = json_decode( $json, true ); + + if( ! $json || !is_array($json) ) { + codecept_debug( 'Cannot import ' . $file ); + } + + // Ensure $json is an array of groups. + if( isset($json['key']) ) { + $json = array( $json ); + } + + // Loop over json + foreach( $json as $field_group ) { + + // Import field group. + $imported_field_group = acf_import_field_group( $field_group ); + + // append message + $this->imported_group_ids[] = $imported_field_group['ID']; + } + + // Count number of imported field groups. + $total = count($this->imported_group_ids); + + // Generate text. + $text = sprintf( _n( 'Imported 1 field group', 'Imported %s field groups', $total, 'acf' ), $total ); + codecept_debug( $text ); + + } + + public function deregister_acf_field_group() { + acf_delete_field_group( $this->group_key ); + } + + /** + * @throws Exception + */ + public function testBasicQuery() { + $query = '{ posts { nodes { id } } }'; + $actual = graphql( [ 'query' => $query ] ); + $this->assertArrayNotHasKey( 'errors', $actual ); + } + + public function testPageBuilderQuery() { + + $hero_fragment = ' + fragment Hero on With_AcfHero_Fields { + heroImage { + node { + databaseId + } + } + heroHeadline + heroSubHeadline + hasCta + ctaType + link { + url + } + heroVideo + } + '; + + $shade_fragment = ' + fragment Shade on With_AcfBackgroundShade_Fields { + shade + } + '; + + $margins_fragment = ' + fragment Margins on With_AcfVerticalSpacing_Fields { + margins { + marginTop + marginBottom + } + } + '; + + $query = ' + query GetPage( $id:ID! ) { + page(id: $id idType: DATABASE_ID) { + id + title + acfPageBuilder { + componentNames: components { + __typename + } + components { + __typename + ...Hero + ...Shade + ...Margins + } + } + } + } + ' . $hero_fragment . ' + ' . $shade_fragment . ' + ' . $margins_fragment . ' + '; + +// +// $margins_fragment = ' +// fragment Margins on With_AcfVerticalSpacing_Fields { +// margins { +// marginTop +// marginBottom +// } +// } +// '; +// +// $content_component_fragment = ' +// fragment Content on With_AcfContentComponent_Fields { +// headline +// content +// listColumns +// sectionLink { +// url +// } +// } +// '; +// +// $content_section_fragment = ' +// fragment ContentSection on AcfPageBuilderContentSection { +// shade +// ...Margins +// ...Content +// } +// '; +// +// $page_builder_fragment = ' +// fragment PageBuilder on With_AcfPageBuilder { +// pageBuilder { +// components { +// ...Hero +// ...ContentSection +// } +// } +// } +// '; +// +// $query = ' +// { +// page( id: $id idType: DATABASE_ID ) { +// id +// databaseId +// ...PageBuilder +// } +// } +// '. $page_builder_fragment .' +// '. $content_section_fragment .' +// '. $content_component_fragment .' +// '. $margins_fragment .' +// '. $hero_fragment .' +// '; +// + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'id' => $this->page_id, + ] + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + + } + +} diff --git a/tests/wpunit/FlexFieldsTest.php b/tests/wpunit/FlexFieldsTest.php index 35e6710..fa1dfba 100644 --- a/tests/wpunit/FlexFieldsTest.php +++ b/tests/wpunit/FlexFieldsTest.php @@ -75,7 +75,7 @@ public function register_acf_field( $config = [] ) { $defaults = [ 'parent' => $this->group_key, - 'key' => 'field_5d7812fd000a4', + 'key' => 'field_5d7812fd0789678', 'label' => 'Text', 'name' => 'text', 'type' => 'text', @@ -109,9 +109,189 @@ public function testBasicQuery() { public function test_query_flex_field_on_post() { - $this->register_acf_field( + $this->register_acf_field([ + 'key' => 'field_flex_12345', + 'type' => 'flexible_content', + 'name' => 'flex_field', + 'layouts' => [ + 'layout_60a6eec68967' => array( + 'key' => 'layout_60a6eec68967', + 'name' => 'layout_one', + 'label' => 'Layout One', + 'display' => 'block', + 'sub_fields' => array( + array( + 'key' => 'field_60a6eec4566', + 'label' => 'text', + 'name' => 'text', + 'type' => 'text', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'default_value' => '', + 'placeholder' => '', + 'prepend' => '', + 'append' => '', + 'maxlength' => '', + ), + array( + 'key' => 'field_60a6eee67866', + 'label' => 'Image', + 'name' => 'image', + 'type' => 'image', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => array( + 'width' => '', + 'class' => '', + 'id' => '', + ), + 'show_in_graphql' => 1, + 'return_format' => 'array', + 'preview_size' => 'medium', + 'library' => 'all', + 'min_width' => '', + 'min_height' => '', + 'min_size' => '', + 'max_width' => '', + 'max_height' => '', + 'max_size' => '', + 'mime_types' => '', + ), + ), + 'min' => '', + 'max' => '', + ), + 'layout_60a6eee6567687' => [ + 'key' => 'layout_60a6eee6567687', + 'name' => 'layout_two', + 'label' => 'Layout Two', + 'display' => 'block', + 'sub_fields' => [ + [ + 'key' => 'field_60a6eee345453', + 'label' => 'Gallery', + 'name' => 'gallery', + 'type' => 'gallery', + 'instructions' => '', + 'required' => 0, + 'conditional_logic' => 0, + 'wrapper' => [ + 'width' => '', + 'class' => '', + 'id' => '', + ], + 'show_in_graphql' => 1, + 'return_format' => 'array', + 'preview_size' => 'medium', + 'insert' => 'append', + 'library' => 'all', + 'min' => '', + 'max' => '', + 'min_width' => '', + 'min_height' => '', + 'min_size' => '', + 'max_width' => '', + 'max_height' => '', + 'max_size' => '', + 'mime_types' => '', + ], + ], + 'min' => '', + 'max' => '', + ], + ], + 'button_label' => 'Add Row', + 'min' => '', + 'max' => '', + ]); + + $query = ' + query post($id: ID!) { + post(id: $id, idType: DATABASE_ID) { + id + databaseId + postFields { + flexField { + __typename + ...LayoutOne + ...LayoutTwo + } + } + } + } + + fragment LayoutOne on PostFields_FlexField_LayoutOne { + text + image { + node { + __typename + databaseId + } + } + } + + fragment LayoutTwo on PostFields_FlexField_LayoutTwo { + gallery { + nodes { + __typename + databaseId + } + } + } + '; + + $gallery_ids = []; + $image_id = $this->factory()->attachment->create(); + $gallery_ids[] = $this->factory()->attachment->create(); + $gallery_ids[] = $this->factory()->attachment->create(); + + // Update the Flex Field data + update_field( 'flex_field', [ + [ + 'acf_fc_layout' => 'layout_one', + 'image' => $image_id, + 'text' => 'Some Example Text...' + ], + [ + 'acf_fc_layout' => 'layout_two', + 'gallery' => $gallery_ids, + ] + ], $this->post_id ); + + codecept_debug( get_post_custom( $this->post_id )); + + $actual = graphql([ + 'query' => $query, + 'variables' => [ + 'id' => $this->post_id, + ], + ]); + + codecept_debug( $actual ); + + $this->assertArrayNotHasKey( 'errors', $actual ); + $this->assertSame( $this->post_id, $actual['data']['post']['databaseId'] ); + + // Assert that there are 2 rows of flex fields + $this->assertSame( 2, count( $actual['data']['post']['postFields']['flexField'] ) ); + + + // Assert the first layout values + $this->assertSame( 'Some Example Text...', $actual['data']['post']['postFields']['flexField'][0]['text'] ); + $this->assertSame( $image_id, $actual['data']['post']['postFields']['flexField'][0]['image']['node']['databaseId'] ); + + // Assert the 2nd layout values + $this->assertSame( $gallery_ids[0], $actual['data']['post']['postFields']['flexField'][1]['gallery']['nodes'][0]['databaseId'] ); + $this->assertSame( $gallery_ids[1], $actual['data']['post']['postFields']['flexField'][1]['gallery']['nodes'][1]['databaseId'] ); - ); } diff --git a/vendor/autoload.php b/vendor/autoload.php index 34912ad..3fd5d42 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit241f81fdd635f043534d2f7ba5ab43ab::getLoader(); +return ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7::getLoader(); diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index ce6cd69..27cb2c9 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -30,7 +30,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', + 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -42,7 +42,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', + 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', ), ), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index b6968e9..fbffd1b 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit241f81fdd635f043534d2f7ba5ab43ab +class ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7 { private static $loader; @@ -24,15 +24,15 @@ public static function getLoader() require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit241f81fdd635f043534d2f7ba5ab43ab', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); - spl_autoload_unregister(array('ComposerAutoloaderInit241f81fdd635f043534d2f7ba5ab43ab', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 2e91839..71a176d 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab +class ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7 { public static $prefixLengthsPsr4 = array ( 'W' => @@ -49,9 +49,9 @@ class ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit241f81fdd635f043534d2f7ba5ab43ab::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 7560120..789750d 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', + 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -18,7 +18,7 @@ 'aliases' => array ( ), - 'reference' => 'b68f991196c1e9b6519c0e0319f2909821920a53', + 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', ), ), ); From 5797366977f2dddf1d7a512ebeec21d025b566e1 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Tue, 29 Jun 2021 15:46:54 -0600 Subject: [PATCH 10/12] - scaffold out files for docs --- docs/fields/accordion.md | 6 ++++++ docs/fields/button-group.md | 2 ++ docs/fields/checkbox.md | 0 docs/fields/clone.md | 0 docs/fields/color-picker.md | 0 docs/fields/date-picker.md | 0 docs/fields/date-time-picker.md | 0 docs/fields/email.md | 0 docs/fields/flexible-content.md | 0 docs/fields/gallery.md | 0 docs/fields/google-map.md | 0 docs/fields/group.md | 0 docs/fields/image.md | 0 docs/fields/link.md | 0 docs/fields/message.md | 0 docs/fields/number.md | 0 docs/fields/oembed.md | 0 docs/fields/page-link.md | 0 docs/fields/password.md | 0 docs/fields/post-object.md | 0 docs/fields/radio.md | 0 docs/fields/range.md | 0 docs/fields/relationship.md | 0 docs/fields/repeater.md | 0 docs/fields/select.md | 0 docs/fields/tab.md | 0 docs/fields/taxonomy.md | 0 docs/fields/text-area.md | 0 docs/fields/text.md | 3 +++ docs/fields/time-picker.md | 0 docs/fields/true-false.md | 0 docs/fields/url.md | 0 docs/fields/user.md | 0 docs/fields/wysiwyg.md | 0 34 files changed, 11 insertions(+) create mode 100644 docs/fields/accordion.md create mode 100644 docs/fields/button-group.md create mode 100644 docs/fields/checkbox.md create mode 100644 docs/fields/clone.md create mode 100644 docs/fields/color-picker.md create mode 100644 docs/fields/date-picker.md create mode 100644 docs/fields/date-time-picker.md create mode 100644 docs/fields/email.md create mode 100644 docs/fields/flexible-content.md create mode 100644 docs/fields/gallery.md create mode 100644 docs/fields/google-map.md create mode 100644 docs/fields/group.md create mode 100644 docs/fields/image.md create mode 100644 docs/fields/link.md create mode 100644 docs/fields/message.md create mode 100644 docs/fields/number.md create mode 100644 docs/fields/oembed.md create mode 100644 docs/fields/page-link.md create mode 100644 docs/fields/password.md create mode 100644 docs/fields/post-object.md create mode 100644 docs/fields/radio.md create mode 100644 docs/fields/range.md create mode 100644 docs/fields/relationship.md create mode 100644 docs/fields/repeater.md create mode 100644 docs/fields/select.md create mode 100644 docs/fields/tab.md create mode 100644 docs/fields/taxonomy.md create mode 100644 docs/fields/text-area.md create mode 100644 docs/fields/text.md create mode 100644 docs/fields/time-picker.md create mode 100644 docs/fields/true-false.md create mode 100644 docs/fields/url.md create mode 100644 docs/fields/user.md create mode 100644 docs/fields/wysiwyg.md diff --git a/docs/fields/accordion.md b/docs/fields/accordion.md new file mode 100644 index 0000000..628b378 --- /dev/null +++ b/docs/fields/accordion.md @@ -0,0 +1,6 @@ +# Accordion + +The Accordion Field in Advanced Custom Fields that lets users separate other fields in collapsible +sections. + +This field is for administrative display purposes and is not exposed in WPGraphQL. diff --git a/docs/fields/button-group.md b/docs/fields/button-group.md new file mode 100644 index 0000000..5317257 --- /dev/null +++ b/docs/fields/button-group.md @@ -0,0 +1,2 @@ +# Button Group Field + diff --git a/docs/fields/checkbox.md b/docs/fields/checkbox.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/clone.md b/docs/fields/clone.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/color-picker.md b/docs/fields/color-picker.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/date-picker.md b/docs/fields/date-picker.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/date-time-picker.md b/docs/fields/date-time-picker.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/email.md b/docs/fields/email.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/flexible-content.md b/docs/fields/flexible-content.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/gallery.md b/docs/fields/gallery.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/google-map.md b/docs/fields/google-map.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/group.md b/docs/fields/group.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/image.md b/docs/fields/image.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/link.md b/docs/fields/link.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/message.md b/docs/fields/message.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/number.md b/docs/fields/number.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/oembed.md b/docs/fields/oembed.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/page-link.md b/docs/fields/page-link.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/password.md b/docs/fields/password.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/post-object.md b/docs/fields/post-object.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/radio.md b/docs/fields/radio.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/range.md b/docs/fields/range.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/relationship.md b/docs/fields/relationship.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/repeater.md b/docs/fields/repeater.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/select.md b/docs/fields/select.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/tab.md b/docs/fields/tab.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/taxonomy.md b/docs/fields/taxonomy.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/text-area.md b/docs/fields/text-area.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/text.md b/docs/fields/text.md new file mode 100644 index 0000000..e72d708 --- /dev/null +++ b/docs/fields/text.md @@ -0,0 +1,3 @@ +# Text Field + + diff --git a/docs/fields/time-picker.md b/docs/fields/time-picker.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/true-false.md b/docs/fields/true-false.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/url.md b/docs/fields/url.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/user.md b/docs/fields/user.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/fields/wysiwyg.md b/docs/fields/wysiwyg.md new file mode 100644 index 0000000..e69de29 From b140f63a9121df0a80372642a564d10d099b176c Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Wed, 30 Jun 2021 08:05:07 -0600 Subject: [PATCH 11/12] Move docs for each field type out of the main README.md and into their own files. No changes to the content of the docs, just organization of the docs. --- README.md | 1661 +------------------------------ docs/fields/accordion.md | 10 + docs/fields/button-group.md | 41 + docs/fields/checkbox.md | 43 + docs/fields/clone.md | 9 + docs/fields/color-picker.md | 40 + docs/fields/date-picker.md | 40 + docs/fields/date-time-picker.md | 40 + docs/fields/email.md | 40 + docs/fields/file.md | 48 + docs/fields/flexible-content.md | 144 +++ docs/fields/gallery.md | 55 + docs/fields/google-map.md | 71 ++ docs/fields/group.md | 44 + docs/fields/image.md | 48 + docs/fields/link.md | 55 + docs/fields/message.md | 8 + docs/fields/number.md | 40 + docs/fields/oembed.md | 41 + docs/fields/page-link.md | 87 ++ docs/fields/password.md | 40 + docs/fields/post-object.md | 125 +++ docs/fields/radio.md | 40 + docs/fields/range.md | 40 + docs/fields/relationship.md | 72 ++ docs/fields/repeater.md | 68 ++ docs/fields/select.md | 40 + docs/fields/tab.md | 8 + docs/fields/taxonomy.md | 53 + docs/fields/text-area.md | 41 + docs/fields/text.md | 41 + docs/fields/time-picker.md | 41 + docs/fields/true-false.md | 41 + docs/fields/url.md | 41 + docs/fields/user.md | 81 ++ docs/fields/wysiwyg.md | 39 + 36 files changed, 1766 insertions(+), 1610 deletions(-) create mode 100644 docs/fields/accordion.md create mode 100644 docs/fields/button-group.md create mode 100644 docs/fields/checkbox.md create mode 100644 docs/fields/clone.md create mode 100644 docs/fields/color-picker.md create mode 100644 docs/fields/date-picker.md create mode 100644 docs/fields/date-time-picker.md create mode 100644 docs/fields/email.md create mode 100644 docs/fields/file.md create mode 100644 docs/fields/flexible-content.md create mode 100644 docs/fields/gallery.md create mode 100644 docs/fields/google-map.md create mode 100644 docs/fields/group.md create mode 100644 docs/fields/image.md create mode 100644 docs/fields/link.md create mode 100644 docs/fields/message.md create mode 100644 docs/fields/number.md create mode 100644 docs/fields/oembed.md create mode 100644 docs/fields/page-link.md create mode 100644 docs/fields/password.md create mode 100644 docs/fields/post-object.md create mode 100644 docs/fields/radio.md create mode 100644 docs/fields/range.md create mode 100644 docs/fields/relationship.md create mode 100644 docs/fields/repeater.md create mode 100644 docs/fields/select.md create mode 100644 docs/fields/tab.md create mode 100644 docs/fields/taxonomy.md create mode 100644 docs/fields/text-area.md create mode 100644 docs/fields/text.md create mode 100644 docs/fields/time-picker.md create mode 100644 docs/fields/true-false.md create mode 100644 docs/fields/url.md create mode 100644 docs/fields/user.md create mode 100644 docs/fields/wysiwyg.md diff --git a/README.md b/README.md index f231b0f..1ad8cba 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # WPGraphQL for Advanced Custom Fields -WPGraphQL for Advanced Custom Fields automatically exposes your ACF fields to the WPGraphQL Schema. +WPGraphQL for Advanced Custom Fields automatically exposes your ACF fields to the WPGraphQL Schema. - [Install and Activate](#install-and-activate) - [Installing from Github](#install-from-github) @@ -8,51 +8,51 @@ WPGraphQL for Advanced Custom Fields automatically exposes your ACF fields to th - [Dependencies](#dependencies) - [Adding Fields to the WPGraphQL Schema](#adding-fields-to-wpgraphql) - [Supported Fields](#supported-fields) - - [Text](#text-field) - - [Text Area](#text-area-field) - - [Number](#number-field) - - [Range](#range-field) - - [Email](#email-field) - - [URL](#url-field) - - [Password](#password-field) - - [Image](#image-field) - - [File](#file-field) - - [WYSIWYG Editor](#wysiwyg-field) - - [oEmbed](#oembed-field) - - [Gallery](#gallery-field) - - [Select](#select-field) - - [Checkbox](#checkbox-field) - - [Radio Button](#radio-button-field) - - [Button Group](#button-group-field) - - [True/False](#true-false-field) - - [Link](#link-field) - - [Post Object](#post-object-field) - - [Page Link](#page-link-field) - - [Relationship](#relationship-field) - - [Taxonomy](#taxonomy-field) - - [User](#user-field) - - [Google Map](#google-map-field) - - [Date Picker](#date-picker-field) - - [Date/Time Picker](#date-time-picker-field) - - [Time Picker](#time-picker-field) - - [Color Picker](#color-picker-field) - - [Message](#message-field) - - [Accordion](#accordion-field) - - [Tab](#tab-field) - - [Group](#group-field) - - [Repeater](#repeater-field) - - [Flexible Content](#flexible-content-field) - - [Clone](#clone-field) + - [Accordion](./docs/fields/accordion.md) + - [Button Group](./docs/fields/button-group.md) + - [Checkbox](./docs/fields/checkbox.md) + - [Clone](./docs/fields/clone.md) + - [Color Picker](./docs/fields/color-picker.md) + - [Date Picker](./docs/fields/date-picker.md) + - [Date/Time Picker](./docs/fields/date-time-picker.md) + - [Email](./docs/fields/email.md) + - [File](./docs/fields/file.md) + - [Flexible Content](./docs/fields/flexible-content.md) + - [Gallery](./docs/fields/gallery.md) + - [Google Map](./docs/fields/google-map.md) + - [Group](./docs/fields/group.md) + - [Image](./docs/fields/image.md) + - [Link](./docs/fields/link.md) + - [Message](./docs/fields/message.md) + - [Number](./docs/fields/number.md) + - [Oembed](./docs/fields/oembed.md) + - [Page Link](./docs/fields/page-link.md) + - [Password](./docs/fields/password.md) + - [Post Object](./docs/fields/post-object.md) + - [Radio](./docs/fields/radio.md) + - [Range](./docs/fields/range.md) + - [Relationship](./docs/fields/relationship.md) + - [Repeater](./docs/fields/repeater.md) + - [Select](./docs/fields/select.md) + - [Tab](./docs/fields/tab.md) + - [Taxonomy](./docs/fields/taxonomy.md) + - [Text](./docs/fields/text.md) + - [Text Area](./docs/fields/text-area.md) + - [Time Picker](./docs/fields/time-picker.md) + - [True/False](./docs/fields/true-false.md) + - [Url](./docs/fields/url.md) + - [User](./docs/fields/user.md) + - [WYSIWYG](./docs/fields/wysiwyg.md) - [Options Pages](#options-pages) - [Location Rules](#location-rules) ## Install and Activate -WPGraphQL for Advanced Custom Fields is not currently available on the WordPress.org repository, so you must download it from Github, or Composer. +WPGraphQL for Advanced Custom Fields is not currently available on the WordPress.org repository, so you must download it from Github, or Composer. ### Installing From Github -To install the plugin from Github, you can [download the latest release zip file](https://github.com/wp-graphql/wp-graphql-acf/archive/master.zip), upload the Zip file to your WordPress install, and activate the plugin. +To install the plugin from Github, you can [download the latest release zip file](https://github.com/wp-graphql/wp-graphql-acf/archive/master.zip), upload the Zip file to your WordPress install, and activate the plugin. [Click here](https://wordpress.org/support/article/managing-plugins/) to learn more about installing WordPress plugins from a Zip file. @@ -62,19 +62,19 @@ To install the plugin from Github, you can [download the latest release zip file ## Dependencies -In order to use WPGraphQL for Advanced Custom Fields, you must have [WPGraphQL](https://github.com/wp-graphql/wp-graphql) and [Advanced Custom Fields](https://advancedcustomfields.com) (free or pro) installed and activated. +In order to use WPGraphQL for Advanced Custom Fields, you must have [WPGraphQL](https://github.com/wp-graphql/wp-graphql) and [Advanced Custom Fields](https://advancedcustomfields.com) (free or pro) installed and activated. ## Adding Fields to the WPGraphQL Schema **TL;DR:** [Here's a video](https://www.youtube.com/watch?v=rIg4MHc8elg) showing an overview of usage. -Advanced Custom Fields, or ACF for short, enables users to add Field Groups, either using a [Graphical User Interface](https://www.advancedcustomfields.com/resources/creating-a-field-group/), [PHP code](https://www.advancedcustomfields.com/resources/register-fields-via-php/), or [local JSON](https://www.advancedcustomfields.com/resources/local-json/) to various screens in the WordPress dashboard, such as (but not limited to) the Edit Post, Edit User and Edit Term screens. +Advanced Custom Fields, or ACF for short, enables users to add Field Groups, either using a [Graphical User Interface](https://www.advancedcustomfields.com/resources/creating-a-field-group/), [PHP code](https://www.advancedcustomfields.com/resources/register-fields-via-php/), or [local JSON](https://www.advancedcustomfields.com/resources/local-json/) to various screens in the WordPress dashboard, such as (but not limited to) the Edit Post, Edit User and Edit Term screens. -Whatever method you use to register ACF fields to your WordPress site should work with WPGraphQL for Advanced Custom Fields. For the sake of simplicity, the documentation below will _primarily_ use the Graphic User Interface for examples. +Whatever method you use to register ACF fields to your WordPress site should work with WPGraphQL for Advanced Custom Fields. For the sake of simplicity, the documentation below will _primarily_ use the Graphic User Interface for examples. ### Add ACF Fields to the WPGraphQL Schema -The first step in using Advanced Custom Fields with WPGraphQL is to [create an ACF Field Group](https://www.advancedcustomfields.com/resources/creating-a-field-group/). +The first step in using Advanced Custom Fields with WPGraphQL is to [create an ACF Field Group](https://www.advancedcustomfields.com/resources/creating-a-field-group/). By default, field groups are _not_ exposed to WPGraphQL. You must opt-in to expose your ACF Field Groups and fields to the WPGraphQL Schema as some information managed by your ACF fields may not be intended for exposure in a queryable API like WPGraphQL. @@ -96,7 +96,7 @@ When registering ACF Fields in PHP, you need to add `show_in_graphql` and `graph ``` function my_acf_add_local_field_groups() { - + acf_add_local_field_group(array( 'key' => 'group_1', 'title' => 'My Group', @@ -120,7 +120,7 @@ function my_acf_add_local_field_groups() { ), ), )); - + } add_action('acf/init', 'my_acf_add_local_field_groups'); @@ -130,7 +130,7 @@ Each individual field will inherit its GraphQL name from the supplied `name` tag ## Supported Fields -In order to document interacting with the fields in GraphQL, an example field group has been created with one field of each type. +In order to document interacting with the fields in GraphQL, an example field group has been created with one field of each type. To replicate the same field group documented here you can download the [example field group](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/field-group-examples-export.json) and [import it](https://support.advancedcustomfields.com/forums/topic/importing-exporting-acf-settings/) into your environment. @@ -138,1565 +138,6 @@ For the sake of documentation, this example field group has the [location rule]( ![Location rule set to Post Type is equal to Post](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/location-rule-post-type-post.png?raw=true) -### Text Field - -Text fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Text fields can be queried and a String will be returned. - -Here, we have a Text field named `text` on the Post Edit screen within the "ACF Docs" Field Group. - -![Text field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/text-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - text - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "text": "Text Value" - } - } - } -} -``` - -### Text Area Field - -Text Area fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Text Area fields can be queried and a String will be returned. - -Here, we have a Text Area field named `text_area` on the Post Edit screen within the "ACF Docs" Field Group. - -![Text Area field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/text-area-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - textArea - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "textArea": "Text value" - } - } - } -} -``` - -### Number Field - -Number fields are added to the WPGraphQL Schema as a field with the Type `Float`. - -Number fields can be queried and a Float will be returned. - -Here, we have a Number field named `number` on the Post Edit screen within the "ACF Docs" Field Group. - -![Number field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/number-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - number - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "number": 5 - } - } - } -} -``` - -### Range Field - -Range fields are added to the WPGraphQL Schema as a field with the Type `Float`. - -Range fields can be queried and a Float will be returned. - -Here, we have a Range field named `range` on the Post Edit screen within the "ACF Docs" Field Group. - -![Range field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/range-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - range - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "range": 5 - } - } - } -} -``` - -### Email Field - -Email fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Email fields can be queried and a String will be returned. - -Here, we have an Email field named `email` on the Post Edit screen within the "ACF Docs" Field Group. - -![Email field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/email-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - email - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "email": "test@example.com" - } - } - } -} -``` - -### URL Field - -Url fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Url fields can be queried and a String will be returned. - -Here, we have a URL field named `url` on the Post Edit screen within the "ACF Docs" Field Group. - -![Url field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/url-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - url - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "url": "https://wpgraphql.com" - } - } - } -} -``` - -### Password Field - -Password fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Password fields can be queried and a String will be returned. - -Here, we have a Password field named `password` on the Post Edit screen within the "ACF Docs" Field Group. - -![Password field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/password-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - password - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "password": "123456" - } - } - } -} -``` - -### Image Field - -Image fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`. - -Image fields can be queried and a MediaItem will be returned. - -The `MediaItem` type is an Object type that has it's own fields that can be selected. So, instead of _just_ getting the Image ID returned and having to ask for the MediaItem object in a follow-up request, we can ask for fields available on the MediaItem Type. For this example, we ask for the `id` and `sourceUrl`. - -Here, we have an Image field named `image` on the Post Edit screen within the "ACF Docs" Field Group. - -![Image field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/image-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - image { - id - sourceUrl(size: MEDIUM) - } - } - } -} -``` - -And the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "image": { - "id": "YXR0YWNobWVudDozMjM=", - "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/03/babe-ruth-300x169.jpg" - } - } - } - } -} -``` - -### File Field - -File fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`. - -File fields can be queried and a MediaItem will be returned. - -The `MediaItem` type is an Object type that has it's own fields that can be selected. So, instead of _just_ getting the File ID returned and having to ask for the MediaItem object in a follow-up request, we can ask for fields available on the MediaItem Type. For this example, we ask for the `id` and `mediaItemUrl`. - -Here, we have a File field named `file` on the Post Edit screen within the "ACF Docs" Field Group. - -![File field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/file-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - file { - id - mediaItemUrl - } - } - } -} -``` - -And the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "file": { - "id": "YXR0YWNobWVudDozMjQ=", - "mediaItemUrl": "http://acf2.local/wp-content/uploads/2020/03/little-ceasars-receipt-01282020.pdf" - } - } - } - } -} -``` - -### WYSIWYG Editor Field - -WYSIWYG fields are added to the WPGraphQL Schema as a field with the Type `String`. - -WYSIWYG fields can be queried and a String will be returned. - -Here, we have a WYSIWYG field named `wysiwyg` on the Post Edit screen within the "ACF Docs" Field Group. - -![WYSIWYG field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/wysiwyg-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post( id: "acf-example-test" idType: URI ) { - acfDocs { - wysiwyg - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "wysiwyg": "

Some content in a WYSIWYG field

\n" - } - } - } -} -``` - -### oEmbed Field
- -oEmbed fields are added to the WPGraphQL Schema as a field with the Type `String`. - -oEmbed fields can be queried and a String will be returned. - -Here, we have a oEmbed field named `oembed` on the Post Edit screen within the "ACF Docs" Field Group. - -![oEmbed field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/oEmbed-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - oembed - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "oembed": "https://www.youtube.com/watch?v=ZEytXfaWwcc" - } - } - } -} -``` - -### Gallery Field - -Gallery fields are added to the WPGraphQL Schema as a field with the Type of `['list_of' => 'MediaItem']`. - -Gallery fields can be queried and a list of MediaItem types will be returned. - -Since the type is a list, we can expect an array to be returned. And since the Type within the list is `MediaItem`, we can ask for fields we want returned for each `MediaItem` in the list. In this case, let's say we want to ask for the `id` of each image and the `sourceUrl`, (size large). - -Here, we have a Gallery field named `gallery` on the Post Edit screen within the "ACF Docs" Field Group. - -![Gallery field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/gallery-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - gallery { - id - sourceUrl(size: LARGE) - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "gallery": [ - { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/02/babe-ruth.jpg" - }, - { - "id": "YXR0YWNobWVudDoyNTU=", - "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/02/babe-ruth-baseball-986x1024.jpg" - } - ] - } - } - } -} -``` - -### Select Field - -Select fields (when configured to _not_ allow mutliple selections) are added to the WPGraphQL Schema as a field with the Type `String`. - -Select fields, without multiple selections allowed, can be queried and a String will be returned. - -Here, we have a Select field named `select` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 1" is selected. - -![Select field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/select-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - select - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "select": "choice_1" - } - } - } -} -``` - -### Checkbox Field - -Checkbox fields are added to the WPGraphQL Schema as a field with the Type `[ 'list_of' => 'String' ]`. - -Checkbox fields can be queried and a list (array) of Strings (the selected values) will be returned. - -Here, we have a Checkbox field named `checkbox` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 1" is selected. - -![Checkbox field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/checkbox-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - checkbox - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "checkbox": [ - "choice_1" - ] - } - } - } -} -``` - -### Radio Button Field - -Radio Button fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Radio Button fields can be queried and a String will be returned. - -Here, we have a Radio Button field named `radio_button` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 2" is selected. - -![Radio Button field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/radio-button-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - radioButton - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "radioButton": "choice_2" - } - } - } -} -``` - -### Button Group Field - -Button Group fields are added to the WPGraphQL Schema as a field with the Type `String`. - -Button Group fields can be queried and a String will be returned. - -Here, we have a Button Group field named `button_group` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 2" is selected. - -![Button Group field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/button-group-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - buttonGroup - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "buttonGroup": "choice_2" - } - } - } -} -``` - -### True/False Field - -True/False fields are added to the WPGraphQL Schema as a field with the Type `Boolean`. - -True/False fields can be queried and a Boolean will be returned. - -Here, we have a True/False field named `true_false` on the Post Edit screen within the "ACF Docs" Field Group, and "true" is selected. - -![True/False field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/true-false-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - trueFalse - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "trueFalse": true - } - } - } -} -``` - -### Link Field - -Link fields are added to the WPGraphQL Schema as a field with the Type `ACF_Link`. - -Link fields can be queried and a `ACF_Link` will be returned. The ACF Link is an object with fields that can be selected. - -The available fields on the `ACF_Link` Type are: - -- **target** (String): The target of the link -- **title** (String): The target of the link -- **url** (String): The url of the link - -Here, we have a Link field named `link` on the Post Edit screen within the "ACF Docs" Field Group. - -![Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/link-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - link { - target - title - url - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "link": { - "target": "", - "title": "Hello world!", - "url": "http://acf2.local/hello-world/" - } - } - } - } -} -``` - -### Post Object Field - -Post Object fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. - -If the field is configured to allow multiple selections, it will be added to the Schema as a List Of the Union Type. - -Since Post Object fields can be configured to be limited to certain Post Types, the Union will represent those Types. - -For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: - -![Post Object field Post Type Config](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-post-type-config.png?raw=true) - -Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: - -![Post Object field Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-possible-types.png?raw=true) - -Here, we have a Post Object field named `post_object` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Post "Hello World!". - -![Post Object field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-input-post.png?raw=true) - -As a GraphQL consumer, we don't know in advance if the value is going to be a Page or a Post. - -So we can specify, via GraphQL fragment, what fields we want if the object is a Post, or if it is a Page. - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - postObject { - __typename - ... on Post { - id - title - date - } - ... on Page { - id - title - } - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "postObject": { - "__typename": "Post", - "id": "cG9zdDox", - "title": "Hello world!", - "date": "2020-02-20T23:12:21" - } - } - } - } -} -``` - -If the input of the field was saved as a Page, instead of a Post, like so: - -![Post Object field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-input-page.png?raw=true) - -Then the same query above, would return the following results: - -```json -{ - "data": { - "post": { - "acfDocs": { - "postObject": { - "__typename": "Page", - "id": "cGFnZToy", - "title": "Sample Page" - } - } - } - } -} -``` - -Now, if the field were configured to allow multiple values, the field would be added to the Schema as a `listOf`, returning an Array of the Union. - -If the field were set with a value of one Page, and one Post, like so: - -![Post Object field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/post-object-field-input-multi.png?raw=true) - -Then the results of the same query as above would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "postObject": [ - { - "__typename": "Page", - "id": "cGFnZToy", - "title": "Sample Page" - }, - { - "__typename": "Post", - "id": "cG9zdDox", - "title": "Hello world!", - "date": "2020-02-20T23:12:21" - } - ] - } - } - } -} -``` - -### Page Link Field - -Page Link fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. - -Since Page Link fields can be configured to be limited to certain Post Types, the Union will represent those Types. - -For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: - -![Page Link field Post Type Config](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-post-type-config.png?raw=true) - -Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: - -![Page Link field Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-possible-types.png?raw=true) - -Here, we have a Page Link field named `page_link` on the Post Edit screen within the "ACF Docs" Field Group, and the value is set to the "Sample Page" page. - -![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - pageLink { - __typename - ... on Post { - id - title - date - } - ... on Page { - id - title - } - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "pageLink": { - "__typename": "Page", - "id": "cGFnZToy", - "title": "Sample Page" - } - } - } - } -} -``` - -Here, we set the value to the "Hello World" Post: - -![Page Link field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/page-link-field-input-2.png?raw=true) - -And the results of the same query are now: - -```json -{ - "data": { - "post": { - "acfDocs": { - "pageLink": { - "__typename": "Post", - "id": "cG9zdDox", - "title": "Hello world!", - "date": "2020-02-20T23:12:21" - } - } - } - } -} -``` - -### Relationship Field - -Relationship fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. - -Since Relationship fields can be configured to be limited to certain Post Types, the Union will represent those Types. - -For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: - -![Relationship field Post Type Config](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/relationship-field-post-type-config.png?raw=true) - -Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: - -![Relationship field Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/relationship-field-possible-types.png?raw=true) - -Here, we have a Relationship field named `relationship` on the Post Edit screen within the "ACF Docs" Field Group, and the value is set to "Hello World!" post, and the "Sample Page" page. - -![Relationship field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/relationship-field-input.png?raw=true) - -This field can be Queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - relationship { - __typename - ... on Post { - id - title - date - } - ... on Page { - id - title - } - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "relationship": [ - { - "__typename": "Post", - "id": "cG9zdDox", - "title": "Hello world!", - "date": "2020-02-20T23:12:21" - }, - { - "__typename": "Page", - "id": "cGFnZToy", - "title": "Sample Page" - } - ] - } - } - } -} -``` - -### Taxonomy Field - -The Taxonomy field is added to the GraphQL Schema as a List Of the Taxonomy Type. - -For example, if the field is configured to the "Category" taxonomy, then the field in the Schema will be a List of the Category type. - -![Taxonomy field Taxonomy Config](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/taxonomy-field-taxonomy-config.png?raw=true) - -Here, we have a Taxonomy field named `taxonomy` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Category "Test Category". - -![Taxonomy field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/taxonomy-field-input.png?raw=true) - -This field can be queried like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - taxonomy { - __typename - id - name - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "taxonomy": [ - { - "__typename": "Category", - "id": "Y2F0ZWdvcnk6Mg==", - "name": "Test Category" - } - ] - } - } - } -} -``` - -### User Field - -User fields are added to the WPGraphQL Schema as a field with a User type. - -Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" as the value. - -![User field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - user { - id - username - firstName - lastName - } - } - } -} -``` - -and the response would look like: - -```json -{ - "data": { - "post": { - "acfDocs": { - "user": { - "id": "dXNlcjox", - "username": "jasonbahl", - "firstName": "Jason", - "lastName": "Bahl" - } - } - } - } -} -``` - -If the field is configured to allow multiple selections, it's added to the Schema as a List Of the User type. - -Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" and "WPGraphQL" as the value. - -![User field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/user-field-input-multiple.png?raw=true) - -and the response to the same query would look like: - -```json -{ - "data": { - "post": { - "acfDocs": { - "user": [ - { - "id": "dXNlcjox", - "username": "jasonbahl", - "firstName": "Jason", - "lastName": "Bahl" - }, - { - "id": "dXNlcjoy", - "username": "WPGraphQL", - "firstName": "WP", - "lastName": "GraphQL" - } - ] - } - } - } -} -``` - -### Google Map Field - -Google Map fields are added to the WPGraphQL Schema as the `ACF_GoogleMap` Type. - -The `ACF_GoogleMap` Type has fields that expose location data. The available fields are: - -- **city** (String): The city associated with the location -- **country** (String): The country associated with the location -- **countryShort** (String): The country abbreviation associated with the location -- **latitude** (String): The latitude associated with the location -- **longitude** (String): The longitude associated with the location -- **placeId** (String): Place IDs uniquely identify a place in the Google Places database and on Google Maps. -- **postCode** (String): The post code associated with the location -- **state** (String): The state associated with the location -- **stateShort** (String): The state abbreviation associated with the location -- **streetAddress** (String): The street address associated with the location -- **streetName** (String): The street name associated with the location -- **streetNumber** (String): The street number associated with the location -- **zoom** (String): The zoom defined with the location - -Here, we have a Google Map field named `google_map` on the Post Edit screen within the "ACF Docs" Field Group, set with the Address "1 Infinite Loop, Cupertino, CA 95014, USA" as the value. - -![Google Map field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/map-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - googleMap { - streetAddress - streetNumber - streetName - city - state - postCode - countryShort - } - } - } -} -``` - -and the response would look like: - -```json -{ - "data": { - "post": { - "acfDocs": { - "googleMap": { - "streetAddress": "1 Infinite Loop, Cupertino, CA 95014, USA", - "streetNumber": "1", - "streetName": "Infinite Loop", - "city": "Cupertino", - "state": "California", - "postCode": "95014", - "placeId": "ChIJHTRqF7e1j4ARzZ_Fv8VA4Eo", - "countryShort": "US" - } - } - } - } -} -``` - -### Date Picker Field - -The Date Picker field is added to the WPGraphQL Schema as field with the Type `String`. - -Date Picker fields can be queried and a String will be returned. - -Here, we have a Date Picker field named `date_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "13/03/2020" is the date set. - -![Date Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/date-picker-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - datePicker - } - } -} -``` - -and the result of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "datePicker": "13/03/2020" - } - } - } -} -``` - -### Date/Time Picker Field - -The Date/Time Picker field is added to the WPGraphQL Schema as field with the Type `String`. - -Date/Time Picker fields can be queried and a String will be returned. - -Here, we have a Date/Time Picker field named `date_time_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "20/03/2020 8:15 am" is the value. - -![Date Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/date-time-picker-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - dateTimePicker - } - } -} -``` - -and the result of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "dateTimePicker": "20/03/2020 8:15 am" - } - } - } -} -``` - -### Time Picker Field - -The Time Picker field is added to the WPGraphQL Schema as field with the Type `String`. - -Time Picker fields can be queried and a String will be returned. - -Here, we have a Time Picker field named `time_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "12:30 am" is the value. - -![Time Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/time-picker-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - timePicker - } - } -} -``` - -and the result of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "timePicker": "12:30 am" - } - } - } -} -``` - -### Color Picker Field - -The Color Picker field is added to the WPGraphQL Schema as field with the Type `String`. - -Color Picker fields can be queried and a String will be returned. - -Here, we have a Color Picker field named `color_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "#dd3333" is the value. - -![Color Picker field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/color-picker-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - colorPicker - } - } -} -``` - -and the result of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "colorPicker": "12:30 am" - } - } - } -} -``` - -### Message Field - -Message fields are not currently supported. - -### Accordion Field - -Accordion Fields are not currently supported. - -### Tab Field - -Tab fields are not currently supported. - -### Group Field - -Group Fields are added to the WPGraphQL Schema as fields resolving to an Object Type named after the Group. - -Here, we have a Group field named `group` on the Post Edit screen within the "ACF Docs" Field Group. Within the "group" field, we have a Text Field named `text_field_in_group` and a Text Area field named `text_area_field_in_group` - -![Group field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/group-field-input.png?raw=true) - -We can query the fields within the group like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - group { - textFieldInGroup - textAreaFieldInGroup - } - } - } -} -``` - -And the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "group": { - "textFieldInGroup": "Text value, in group", - "textAreaFieldInGroup": "Text are value, in group" - } - } - } - } -} -``` - -### Repeater Field - -Repeater Fields are added to the Schema as a List Of the Type of group that makes up the fields. - -For example, we've created a Repeater Field that has a Text Field named `text_field_in_repeater` and an Image Field named `image_field_in_repeater`. - -Here, the Repeater Field is populated with 2 rows: -- Row 1: - - Text Field: Text Value 1 - - Image: 256 -- Row 2: - - Text Field: Text Value 2 - - Image: 255 - -![Repeater field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/repeater-field-input.png?raw=true) - -This field can be queried in GraphQL like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - repeater { - textFieldInRepeater - imageFieldInRepeater { - databaseId - id - sourceUrl - } - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "repeater": [ - { - "textFieldInRepeater": "Text Value 1", - "imageFieldInRepeater": { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth.jpg" - } - }, - { - "textFieldInRepeater": "Text Value 2", - "imageFieldInRepeater": { - "id": "YXR0YWNobWVudDoyNTU=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-scaled.jpg" - } - } - ] - } - } - } -} -``` - -### Flexible Content Field - -The Flexible Content is a powerful ACF field that allows for groups of fields to be organized into "Layouts". - -These Layouts can be made up of other types of fields, and can be added and arranged in any order. - -Flexible Content Fields are added to the WPGraphQL Schema as a List Of [Unions](https://graphql.org/learn/schema/#union-types). - -The Union for a Flex Field is made up of each Layout in the Flex Field as the possible Types. - -In our example, we've created a Flex Field with 3 layouts named "Layout One", "Layout Two" and "Layout Three". In the Schema, we can see the Flex Field Union's Possible Types are these 3 layouts. - -![Flex Fields Schema Union Possible Types](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/flex-field-union-possible-types.png?raw=true) - -Each of these Layout types will contain the fields defined for the layout and can be queried like fields in any other Group. - -Here's an example of a Flex Field named `flexible_content`, with 3 layouts: - -- Layout One - - Text field named "text" - - Text field named "another_text_field" -- Layout Two - - Image field named "image" -- Layout Three - - Gallery field named "gallery" - -Above are the possible layouts and their fields. These layouts can be added and arranged in any order. While we, as a GraphQL consumer, don't know ahead of time what order they will be in, we _do_ know what the possibilities are. - -Here's an example of a Flex Field named `flexible_content` with the values saved as "Layout One", "Layout Two" and "Layout Three", in that order, all populated with their respective fields. - -![Flex field in the Edit Post screen](https://github.com/wp-graphql/wp-graphql-acf/blob/master/docs/img/flex-field-input.png?raw=true) - -We can query this field like so: - -```graphql -{ - post(id: "acf-example-test", idType: URI) { - acfDocs { - flexibleContent { - __typename - ... on Post_Acfdocs_FlexibleContent_LayoutOne { - text - anotherTextField - } - ... on Post_Acfdocs_FlexibleContent_LayoutTwo { - image { - id - sourceUrl(size: MEDIUM) - } - } - ... on Post_Acfdocs_FlexibleContent_LayoutThree { - gallery { - id - sourceUrl(size: MEDIUM) - } - } - } - } - } -} -``` - -and the results of the query would be: - -```json -{ - "data": { - "post": { - "acfDocs": { - "flexibleContent": [ - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutOne", - "text": "Text Value One", - "anotherTextField": "Another Text Value" - }, - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutTwo", - "image": { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" - } - }, - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutThree", - "gallery": [ - { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" - }, - { - "id": "YXR0YWNobWVudDoyNTU=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-289x300.jpg" - } - ] - } - ] - } - } - } -} -``` - -If we were to re-arrange the layouts, so that the order was "Layout Three", "Layout One", "Layout Two", the results of the query would be: - -```json -"data": { - "post": { - "acfDocs": { - "flexibleContent": [ - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutThree", - "gallery": [ - { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" - }, - { - "id": "YXR0YWNobWVudDoyNTU=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-289x300.jpg" - } - ] - } - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutOne", - "text": "Text Value One", - "anotherTextField": "Another Text Value" - }, - { - "__typename": "Post_Acfdocs_FlexibleContent_LayoutTwo", - "image": { - "id": "YXR0YWNobWVudDoyNTY=", - "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" - } - }, - ] - } - } - } -``` - -### Clone Field - -The clone field is not fully supported (yet). We plan to support it in the future. - ## Options Pages **Reference**: https://www.advancedcustomfields.com/add-ons/options-page/ @@ -1738,11 +179,11 @@ Alternatively, it's you can check the Fields API Reference to learn about exposi ## Location Rules -Advanced Custom Fields field groups are added to the WordPress dashboard by being assigned "Location Rules". +Advanced Custom Fields field groups are added to the WordPress dashboard by being assigned "Location Rules". -WPGraphQL for Advanced Custom Fields uses the Location Rules to determine where in the GraphQL Schema the field groups/fields should be added to the Schema. +WPGraphQL for Advanced Custom Fields uses the Location Rules to determine where in the GraphQL Schema the field groups/fields should be added to the Schema. -For example, if a Field Group were assigned to "Post Type is equal to Post", then the field group would show in the WPGraphQL Schema on the `Post` type, allowing you to query for ACF fields of the Post, anywhere you can interact with the `Post` type in the Schema. +For example, if a Field Group were assigned to "Post Type is equal to Post", then the field group would show in the WPGraphQL Schema on the `Post` type, allowing you to query for ACF fields of the Post, anywhere you can interact with the `Post` type in the Schema. ### Supported Locations @@ -1750,10 +191,10 @@ For example, if a Field Group were assigned to "Post Type is equal to Post", the ### Why aren't all location rules supported? -You might notice that some location rules don't add fields to the Schema. This is because some location rules are based on context that doesn't exist when the GraphQL Schema is generated. +You might notice that some location rules don't add fields to the Schema. This is because some location rules are based on context that doesn't exist when the GraphQL Schema is generated. -For example, if you have a location rule to show a field group only on a specific page, how would that be exposed the the Schema? There's no Type in the Schema for just one specific page. +For example, if you have a location rule to show a field group only on a specific page, how would that be exposed the the Schema? There's no Type in the Schema for just one specific page. -If you're not seeing a field group in the Schema, look at the location rules, and think about _how_ the field group would be added to a Schema that isn't aware of context like which admin page you're on, what category a Post is assigned to, etc. +If you're not seeing a field group in the Schema, look at the location rules, and think about _how_ the field group would be added to a Schema that isn't aware of context like which admin page you're on, what category a Post is assigned to, etc. If you have ideas on how these specific contextual rules should be handled in WPGraphQL, submit an issue so we can consider how to best support it! diff --git a/docs/fields/accordion.md b/docs/fields/accordion.md new file mode 100644 index 0000000..d5aa667 --- /dev/null +++ b/docs/fields/accordion.md @@ -0,0 +1,10 @@ +# Accordion + +The Accordion Field in Advanced Custom Fields that lets users separate other fields in collapsible +sections. + +This field is for administrative display purposes and is not exposed in WPGraphQL. + +---- + +- **Next Field:** [Button Group](./button-group.md) diff --git a/docs/fields/button-group.md b/docs/fields/button-group.md new file mode 100644 index 0000000..142b774 --- /dev/null +++ b/docs/fields/button-group.md @@ -0,0 +1,41 @@ +# Button Group Field + +Button Group fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Button Group fields can be queried and a String will be returned. + +Here, we have a Button Group field named `button_group` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 2" is selected. + +![Button Group field in the Edit Post screen](../img/button-group-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + buttonGroup + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "buttonGroup": "choice_2" + } + } + } +} +``` + +---- + +- **Previous Field:** [Accordion](./accordion.md) +- **Next Field:** [Checkbox](./checkbox.md) + diff --git a/docs/fields/checkbox.md b/docs/fields/checkbox.md new file mode 100644 index 0000000..c95c0c9 --- /dev/null +++ b/docs/fields/checkbox.md @@ -0,0 +1,43 @@ +# Checkbox Field + +Checkbox fields are added to the WPGraphQL Schema as a field with the Type `[ 'list_of' => 'String' ]`. + +Checkbox fields can be queried and a list (array) of Strings (the selected values) will be returned. + +Here, we have a Checkbox field named `checkbox` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 1" is selected. + +![Checkbox field in the Edit Post screen](../img/checkbox-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + checkbox + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "checkbox": [ + "choice_1" + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Button Group](./button-group.md) +- **Next Field:** [Clone](./clone.md) + diff --git a/docs/fields/clone.md b/docs/fields/clone.md new file mode 100644 index 0000000..93ff021 --- /dev/null +++ b/docs/fields/clone.md @@ -0,0 +1,9 @@ +# Clone Field + +The clone field is not fully supported (yet). We plan to support it in the future. + +---- + +- **Previous Field:** [Checkbox](./checkbox.md) +- **Next Field:** [Color Picker](./color-picker.md) + diff --git a/docs/fields/color-picker.md b/docs/fields/color-picker.md new file mode 100644 index 0000000..73fee41 --- /dev/null +++ b/docs/fields/color-picker.md @@ -0,0 +1,40 @@ +# Color Picker Field + +The Color Picker field is added to the WPGraphQL Schema as field with the Type `String`. + +Color Picker fields can be queried and a String will be returned. + +Here, we have a Color Picker field named `color_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "#dd3333" is the value. + +![Color Picker field in the Edit Post screen](../img/color-picker-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + colorPicker + } + } +} +``` + +and the result of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "colorPicker": "12:30 am" + } + } + } +} +``` + +---- + +- **Previous Field:** [Clone](./clone.md) +- **Next Field:** [Date Picker](./date-picker.md) diff --git a/docs/fields/date-picker.md b/docs/fields/date-picker.md new file mode 100644 index 0000000..4ac098c --- /dev/null +++ b/docs/fields/date-picker.md @@ -0,0 +1,40 @@ +# Date Picker Field + +The Date Picker field is added to the WPGraphQL Schema as field with the Type `String`. + +Date Picker fields can be queried and a String will be returned. + +Here, we have a Date Picker field named `date_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "13/03/2020" is the date set. + +![Date Picker field in the Edit Post screen](../img/date-picker-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + datePicker + } + } +} +``` + +and the result of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "datePicker": "13/03/2020" + } + } + } +} +``` + +---- + +- **Previous Field:** [Color Picker](./color-picker.md) +- **Next Field:** [Date/Time Picker](./date-time-picker.md) diff --git a/docs/fields/date-time-picker.md b/docs/fields/date-time-picker.md new file mode 100644 index 0000000..5188ff4 --- /dev/null +++ b/docs/fields/date-time-picker.md @@ -0,0 +1,40 @@ +# Date/Time Picker Field + +The Date/Time Picker field is added to the WPGraphQL Schema as field with the Type `String`. + +Date/Time Picker fields can be queried, and a String will be returned. + +Here, we have a Date/Time Picker field named `date_time_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "20/03/2020 8:15 am" is the value. + +![Date Picker field in the Edit Post screen](../img/date-time-picker-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + dateTimePicker + } + } +} +``` + +and the result of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "dateTimePicker": "20/03/2020 8:15 am" + } + } + } +} +``` + +---- + +- **Previous Field:** [Date Picker](./date-picker.md) +- **Next Field:** [Email](./email.md) diff --git a/docs/fields/email.md b/docs/fields/email.md new file mode 100644 index 0000000..112b167 --- /dev/null +++ b/docs/fields/email.md @@ -0,0 +1,40 @@ +# Email Field + +Email fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Email fields can be queried and a String will be returned. + +Here, we have an Email field named `email` on the Post Edit screen within the "ACF Docs" Field Group. + +![Email field in the Edit Post screen](../img/email-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + email + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "email": "test@example.com" + } + } + } +} +``` + +---- + +- **Previous Field:** [Date/Time Picker](./date-time-picker.md) +- **Next Field:** [File](./file.md) diff --git a/docs/fields/file.md b/docs/fields/file.md new file mode 100644 index 0000000..4010a14 --- /dev/null +++ b/docs/fields/file.md @@ -0,0 +1,48 @@ +# File Field + +File fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`. + +File fields can be queried and a MediaItem will be returned. + +The `MediaItem` type is an Object type that has it's own fields that can be selected. So, instead of _just_ getting the File ID returned and having to ask for the MediaItem object in a follow-up request, we can ask for fields available on the MediaItem Type. For this example, we ask for the `id` and `mediaItemUrl`. + +Here, we have a File field named `file` on the Post Edit screen within the "ACF Docs" Field Group. + +![File field in the Edit Post screen](../img/file-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + file { + id + mediaItemUrl + } + } + } +} +``` + +And the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "file": { + "id": "YXR0YWNobWVudDozMjQ=", + "mediaItemUrl": "http://acf2.local/wp-content/uploads/2020/03/little-ceasars-receipt-01282020.pdf" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Email](./email.md) +- **Next Field:** [Flexible Content](./flexible-content.md) diff --git a/docs/fields/flexible-content.md b/docs/fields/flexible-content.md new file mode 100644 index 0000000..8c90d07 --- /dev/null +++ b/docs/fields/flexible-content.md @@ -0,0 +1,144 @@ +# Flexible Content Field + +The Flexible Content is a powerful ACF field that allows for groups of fields to be organized into "Layouts". + +These Layouts can be made up of other types of fields, and can be added and arranged in any order. + +Flexible Content Fields are added to the WPGraphQL Schema as a List Of [Unions](https://graphql.org/learn/schema/#union-types). + +The Union for a Flex Field is made up of each Layout in the Flex Field as the possible Types. + +In our example, we've created a Flex Field with 3 layouts named "Layout One", "Layout Two" and "Layout Three". In the Schema, we can see the Flex Field Union's Possible Types are these 3 layouts. + +![Flex Fields Schema Union Possible Types](../img/flex-field-union-possible-types.png?raw=true) + +Each of these Layout types will contain the fields defined for the layout and can be queried like fields in any other Group. + +Here's an example of a Flex Field named `flexible_content`, with 3 layouts: + +- Layout One + - Text field named "text" + - Text field named "another_text_field" +- Layout Two + - Image field named "image" +- Layout Three + - Gallery field named "gallery" + +Above are the possible layouts and their fields. These layouts can be added and arranged in any order. While we, as a GraphQL consumer, don't know ahead of time what order they will be in, we _do_ know what the possibilities are. + +Here's an example of a Flex Field named `flexible_content` with the values saved as "Layout One", "Layout Two" and "Layout Three", in that order, all populated with their respective fields. + +![Flex field in the Edit Post screen](../img/flex-field-input.png?raw=true) + +We can query this field like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + flexibleContent { + __typename + ... on Post_Acfdocs_FlexibleContent_LayoutOne { + text + anotherTextField + } + ... on Post_Acfdocs_FlexibleContent_LayoutTwo { + image { + id + sourceUrl(size: MEDIUM) + } + } + ... on Post_Acfdocs_FlexibleContent_LayoutThree { + gallery { + id + sourceUrl(size: MEDIUM) + } + } + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "flexibleContent": [ + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutOne", + "text": "Text Value One", + "anotherTextField": "Another Text Value" + }, + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutTwo", + "image": { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" + } + }, + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutThree", + "gallery": [ + { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" + }, + { + "id": "YXR0YWNobWVudDoyNTU=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-289x300.jpg" + } + ] + } + ] + } + } + } +} +``` + +If we were to re-arrange the layouts, so that the order was "Layout Three", "Layout One", "Layout Two", the results of the query would be: + +```json +"data": { + "post": { + "acfDocs": { + "flexibleContent": [ + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutThree", + "gallery": [ + { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" + }, + { + "id": "YXR0YWNobWVudDoyNTU=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-289x300.jpg" + } + ] + } + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutOne", + "text": "Text Value One", + "anotherTextField": "Another Text Value" + }, + { + "__typename": "Post_Acfdocs_FlexibleContent_LayoutTwo", + "image": { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-300x169.jpg" + } + }, + ] + } + } + } +``` + +---- + +- **Previous Field:** [File](./file.md) +- **Next Field:** [Gallery](./gallery.md) diff --git a/docs/fields/gallery.md b/docs/fields/gallery.md new file mode 100644 index 0000000..2eb3cd7 --- /dev/null +++ b/docs/fields/gallery.md @@ -0,0 +1,55 @@ +# Gallery Field + +Gallery fields are added to the WPGraphQL Schema as a field with the Type of `['list_of' => 'MediaItem']`. + +Gallery fields can be queried and a list of MediaItem types will be returned. + +Since the type is a list, we can expect an array to be returned. And since the Type within the list is `MediaItem`, we can ask for fields we want returned for each `MediaItem` in the list. In this case, let's say we want to ask for the `id` of each image and the `sourceUrl`, (size large). + +Here, we have a Gallery field named `gallery` on the Post Edit screen within the "ACF Docs" Field Group. + +![Gallery field in the Edit Post screen](../img/gallery-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + gallery { + id + sourceUrl(size: LARGE) + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "gallery": [ + { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/02/babe-ruth.jpg" + }, + { + "id": "YXR0YWNobWVudDoyNTU=", + "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/02/babe-ruth-baseball-986x1024.jpg" + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Flexible Content](./flexible-content.md) +- **Next Field:** [Google Map](./google-map.md) + diff --git a/docs/fields/google-map.md b/docs/fields/google-map.md new file mode 100644 index 0000000..f99b8fc --- /dev/null +++ b/docs/fields/google-map.md @@ -0,0 +1,71 @@ +# Google Map Field + +Google Map fields are added to the WPGraphQL Schema as the `ACF_GoogleMap` Type. + +The `ACF_GoogleMap` Type has fields that expose location data. The available fields are: + +- **city** (String): The city associated with the location +- **country** (String): The country associated with the location +- **countryShort** (String): The country abbreviation associated with the location +- **latitude** (String): The latitude associated with the location +- **longitude** (String): The longitude associated with the location +- **placeId** (String): Place IDs uniquely identify a place in the Google Places database and on Google Maps. +- **postCode** (String): The post code associated with the location +- **state** (String): The state associated with the location +- **stateShort** (String): The state abbreviation associated with the location +- **streetAddress** (String): The street address associated with the location +- **streetName** (String): The street name associated with the location +- **streetNumber** (String): The street number associated with the location +- **zoom** (String): The zoom defined with the location + +Here, we have a Google Map field named `google_map` on the Post Edit screen within the "ACF Docs" Field Group, set with the Address "1 Infinite Loop, Cupertino, CA 95014, USA" as the value. + +![Google Map field in the Edit Post screen](../img/map-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + googleMap { + streetAddress + streetNumber + streetName + city + state + postCode + countryShort + } + } + } +} +``` + +and the response would look like: + +```json +{ + "data": { + "post": { + "acfDocs": { + "googleMap": { + "streetAddress": "1 Infinite Loop, Cupertino, CA 95014, USA", + "streetNumber": "1", + "streetName": "Infinite Loop", + "city": "Cupertino", + "state": "California", + "postCode": "95014", + "placeId": "ChIJHTRqF7e1j4ARzZ_Fv8VA4Eo", + "countryShort": "US" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Gallery](./gallery.md) +- **Next Field:** [Group](./group.md) diff --git a/docs/fields/group.md b/docs/fields/group.md new file mode 100644 index 0000000..8e38227 --- /dev/null +++ b/docs/fields/group.md @@ -0,0 +1,44 @@ +# Group Field + +Group Fields are added to the WPGraphQL Schema as fields resolving to an Object Type named after the Group. + +Here, we have a Group field named `group` on the Post Edit screen within the "ACF Docs" Field Group. Within the "group" field, we have a Text Field named `text_field_in_group` and a Text Area field named `text_area_field_in_group` + +![Group field in the Edit Post screen](../img/group-field-input.png?raw=true) + +We can query the fields within the group like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + group { + textFieldInGroup + textAreaFieldInGroup + } + } + } +} +``` + +And the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "group": { + "textFieldInGroup": "Text value, in group", + "textAreaFieldInGroup": "Text are value, in group" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Google Map](./google-map.md) +- **Next Field:** [image](./image.md) diff --git a/docs/fields/image.md b/docs/fields/image.md new file mode 100644 index 0000000..4f34999 --- /dev/null +++ b/docs/fields/image.md @@ -0,0 +1,48 @@ +# Image Field + +Image fields are added to the WPGraphQL Schema as a field with the Type `MediaItem`. + +Image fields can be queried and a MediaItem will be returned. + +The `MediaItem` type is an Object type that has it's own fields that can be selected. So, instead of _just_ getting the Image ID returned and having to ask for the MediaItem object in a follow-up request, we can ask for fields available on the MediaItem Type. For this example, we ask for the `id` and `sourceUrl`. + +Here, we have an Image field named `image` on the Post Edit screen within the "ACF Docs" Field Group. + +![Image field in the Edit Post screen](../img/image-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + image { + id + sourceUrl(size: MEDIUM) + } + } + } +} +``` + +And the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "image": { + "id": "YXR0YWNobWVudDozMjM=", + "sourceUrl": "http://wpgraphql.local/wp-content/uploads/2020/03/babe-ruth-300x169.jpg" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Group](./group.md) +- **Next Field:** [Link](./link.md) diff --git a/docs/fields/link.md b/docs/fields/link.md new file mode 100644 index 0000000..fa6ab5e --- /dev/null +++ b/docs/fields/link.md @@ -0,0 +1,55 @@ +# Link Field + +Link fields are added to the WPGraphQL Schema as a field with the Type `ACF_Link`. + +Link fields can be queried and a `ACF_Link` will be returned. The ACF Link is an object with fields that can be selected. + +The available fields on the `ACF_Link` Type are: + +- **target** (String): The target of the link +- **title** (String): The target of the link +- **url** (String): The url of the link + +Here, we have a Link field named `link` on the Post Edit screen within the "ACF Docs" Field Group. + +![Link field in the Edit Post screen](../img/link-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + link { + target + title + url + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "link": { + "target": "", + "title": "Hello world!", + "url": "http://acf2.local/hello-world/" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Image](./image.md) +- **Next Field:** [Message](./message.md) + diff --git a/docs/fields/message.md b/docs/fields/message.md new file mode 100644 index 0000000..bbefeab --- /dev/null +++ b/docs/fields/message.md @@ -0,0 +1,8 @@ +# Message Field + +Message fields are not currently supported. + +---- + +- **Previous Field:** [Link](./link.md) +- **Next Field:** [Number](./number.md) diff --git a/docs/fields/number.md b/docs/fields/number.md new file mode 100644 index 0000000..71e4d99 --- /dev/null +++ b/docs/fields/number.md @@ -0,0 +1,40 @@ +# Number Field + +Number fields are added to the WPGraphQL Schema as a field with the Type `Float`. + +Number fields can be queried, and a Float will be returned. + +Here, we have a Number field named `number` on the Post Edit screen within the "ACF Docs" Field Group. + +![Number field in the Edit Post screen](../img/number-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + number + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "number": 5 + } + } + } +} +``` + +---- + +- **Previous Field:** [Message](./message.md) +- **Next Field:** [Oembed](./oembed.md) diff --git a/docs/fields/oembed.md b/docs/fields/oembed.md new file mode 100644 index 0000000..07604c2 --- /dev/null +++ b/docs/fields/oembed.md @@ -0,0 +1,41 @@ +# oEmbed Field + +oEmbed fields are added to the WPGraphQL Schema as a field with the Type `String`. + +oEmbed fields can be queried, and a String will be returned. + +Here, we have a oEmbed field named `oembed` on the Post Edit screen within the "ACF Docs" Field Group. + +![oEmbed field in the Edit Post screen](../img/oEmbed-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + oembed + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "oembed": "https://www.youtube.com/watch?v=ZEytXfaWwcc" + } + } + } +} +``` + +---- + +- **Previous Field:** [Number](./number.md) +- **Next Field:** [Page Link](./page-link.md) + diff --git a/docs/fields/page-link.md b/docs/fields/page-link.md new file mode 100644 index 0000000..49a3747 --- /dev/null +++ b/docs/fields/page-link.md @@ -0,0 +1,87 @@ +# Page Link Field + +Page Link fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. + +Since Page Link fields can be configured to be limited to certain Post Types, the Union will represent those Types. + +For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: + +![Page Link field Post Type Config](../img/page-link-field-post-type-config.png?raw=true) + +Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: + +![Page Link field Union Possible Types](../img/page-link-field-possible-types.png?raw=true) + +Here, we have a Page Link field named `page_link` on the Post Edit screen within the "ACF Docs" Field Group, and the value is set to the "Sample Page" page. + +![Page Link field in the Edit Post screen](../img/page-link-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + pageLink { + __typename + ... on Post { + id + title + date + } + ... on Page { + id + title + } + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "pageLink": { + "__typename": "Page", + "id": "cGFnZToy", + "title": "Sample Page" + } + } + } + } +} +``` + +Here, we set the value to the "Hello World" Post: + +![Page Link field in the Edit Post screen](../img/page-link-field-input-2.png?raw=true) + +And the results of the same query are now: + +```json +{ + "data": { + "post": { + "acfDocs": { + "pageLink": { + "__typename": "Post", + "id": "cG9zdDox", + "title": "Hello world!", + "date": "2020-02-20T23:12:21" + } + } + } + } +} +``` + +---- + +- **Previous Field:** [Oembed](./oembed.md) +- **Next Field:** [Checkbox](./password.md) + diff --git a/docs/fields/password.md b/docs/fields/password.md new file mode 100644 index 0000000..8b89f7b --- /dev/null +++ b/docs/fields/password.md @@ -0,0 +1,40 @@ +# Password Field + +Password fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Password fields can be queried, and a String will be returned. + +Here, we have a Password field named `password` on the Post Edit screen within the "ACF Docs" Field Group. + +![Password field in the Edit Post screen](../img/password-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + password + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "password": "123456" + } + } + } +} +``` + +---- + +- **Previous Field:** [Page Link](./page-link.md) +- **Next Field:** [Post Object](./post-object.md) diff --git a/docs/fields/post-object.md b/docs/fields/post-object.md new file mode 100644 index 0000000..deed218 --- /dev/null +++ b/docs/fields/post-object.md @@ -0,0 +1,125 @@ +# Post Object Field + +Post Object fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. + +If the field is configured to allow multiple selections, it will be added to the Schema as a List Of the Union Type. + +Since Post Object fields can be configured to be limited to certain Post Types, the Union will represent those Types. + +For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: + +![Post Object field Post Type Config](../img/post-object-field-post-type-config.png?raw=true) + +Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: + +![Post Object field Union Possible Types](../img/post-object-field-possible-types.png?raw=true) + +Here, we have a Post Object field named `post_object` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Post "Hello World!". + +![Post Object field in the Edit Post screen](../img/post-object-field-input-post.png?raw=true) + +As a GraphQL consumer, we don't know in advance if the value is going to be a Page or a Post. + +So we can specify, via GraphQL fragment, what fields we want if the object is a Post, or if it is a Page. + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + postObject { + __typename + ... on Post { + id + title + date + } + ... on Page { + id + title + } + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "postObject": { + "__typename": "Post", + "id": "cG9zdDox", + "title": "Hello world!", + "date": "2020-02-20T23:12:21" + } + } + } + } +} +``` + +If the input of the field was saved as a Page, instead of a Post, like so: + +![Post Object field in the Edit Post screen](../img/post-object-field-input-page.png?raw=true) + +Then the same query above, would return the following results: + +```json +{ + "data": { + "post": { + "acfDocs": { + "postObject": { + "__typename": "Page", + "id": "cGFnZToy", + "title": "Sample Page" + } + } + } + } +} +``` + +Now, if the field were configured to allow multiple values, the field would be added to the Schema as a `listOf`, returning an Array of the Union. + +If the field were set with a value of one Page, and one Post, like so: + +![Post Object field in the Edit Post screen](../img/post-object-field-input-multi.png?raw=true) + +Then the results of the same query as above would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "postObject": [ + { + "__typename": "Page", + "id": "cGFnZToy", + "title": "Sample Page" + }, + { + "__typename": "Post", + "id": "cG9zdDox", + "title": "Hello world!", + "date": "2020-02-20T23:12:21" + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Password](./password.md) +- **Next Field:** [Radio](./radio.md) + diff --git a/docs/fields/radio.md b/docs/fields/radio.md new file mode 100644 index 0000000..a11d9b5 --- /dev/null +++ b/docs/fields/radio.md @@ -0,0 +1,40 @@ +# Radio Button Field + +Radio Button fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Radio Button fields can be queried and a String will be returned. + +Here, we have a Radio Button field named `radio_button` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 2" is selected. + +![Radio Button field in the Edit Post screen](../img/radio-button-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + radioButton + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "radioButton": "choice_2" + } + } + } +} +``` + +---- + +- **Previous Field:** [Post Object](./post-object.md) +- **Next Field:** [Range](./range.md) diff --git a/docs/fields/range.md b/docs/fields/range.md new file mode 100644 index 0000000..2768acd --- /dev/null +++ b/docs/fields/range.md @@ -0,0 +1,40 @@ +# Range Field + +Range fields are added to the WPGraphQL Schema as a field with the Type `Float`. + +Range fields can be queried, and a Float will be returned. + +Here, we have a Range field named `range` on the Post Edit screen within the "ACF Docs" Field Group. + +![Range field in the Edit Post screen](../img/range-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + range + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "range": 5 + } + } + } +} +``` + +---- + +- **Previous Field:** [Radio](./radio.md) +- **Next Field:** [Relationship](./relationship.md) diff --git a/docs/fields/relationship.md b/docs/fields/relationship.md new file mode 100644 index 0000000..5c0fb30 --- /dev/null +++ b/docs/fields/relationship.md @@ -0,0 +1,72 @@ +# Relationship Field + +Relationship fields are added to the WPGraphQL Schema as a field with a [Union](https://graphql.org/learn/schema/#union-types) of Possible Types the field is configured to allow. + +Since Relationship fields can be configured to be limited to certain Post Types, the Union will represent those Types. + +For example, if the Post Object field is configured to allow Posts of the `post` and `page` types to be selected: + +![Relationship field Post Type Config](../img/relationship-field-post-type-config.png?raw=true) + +Then the Union type for the field will allow `Post` and `Page` types to be returned, as seen in the Schema via GraphiQL: + +![Relationship field Union Possible Types](../img/relationship-field-possible-types.png?raw=true) + +Here, we have a Relationship field named `relationship` on the Post Edit screen within the "ACF Docs" Field Group, and the value is set to "Hello World!" post, and the "Sample Page" page. + +![Relationship field in the Edit Post screen](../img/relationship-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + relationship { + __typename + ... on Post { + id + title + date + } + ... on Page { + id + title + } + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "relationship": [ + { + "__typename": "Post", + "id": "cG9zdDox", + "title": "Hello world!", + "date": "2020-02-20T23:12:21" + }, + { + "__typename": "Page", + "id": "cGFnZToy", + "title": "Sample Page" + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Range](./range.md) +- **Next Field:** [Repeater](./repeater.md) + diff --git a/docs/fields/repeater.md b/docs/fields/repeater.md new file mode 100644 index 0000000..f57d5bb --- /dev/null +++ b/docs/fields/repeater.md @@ -0,0 +1,68 @@ +# Repeater Field + +Repeater Fields are added to the Schema as a List Of the Type of group that makes up the fields. + +For example, we've created a Repeater Field that has a Text Field named `text_field_in_repeater` and an Image Field named `image_field_in_repeater`. + +Here, the Repeater Field is populated with 2 rows: +- Row 1: + - Text Field: Text Value 1 + - Image: 256 +- Row 2: + - Text Field: Text Value 2 + - Image: 255 + +![Repeater field in the Edit Post screen](../img/repeater-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + repeater { + textFieldInRepeater + imageFieldInRepeater { + databaseId + id + sourceUrl + } + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "repeater": [ + { + "textFieldInRepeater": "Text Value 1", + "imageFieldInRepeater": { + "id": "YXR0YWNobWVudDoyNTY=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth.jpg" + } + }, + { + "textFieldInRepeater": "Text Value 2", + "imageFieldInRepeater": { + "id": "YXR0YWNobWVudDoyNTU=", + "sourceUrl": "http://acf2.local/wp-content/uploads/2020/02/babe-ruth-baseball-scaled.jpg" + } + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Relationship](./relationship.md) +- **Next Field:** [Select](./select.md) diff --git a/docs/fields/select.md b/docs/fields/select.md new file mode 100644 index 0000000..cf3b566 --- /dev/null +++ b/docs/fields/select.md @@ -0,0 +1,40 @@ +# Select Field + +Select fields (when configured to _not_ allow mutliple selections) are added to the WPGraphQL Schema as a field with the Type `String`. + +Select fields, without multiple selections allowed, can be queried and a String will be returned. + +Here, we have a Select field named `select` on the Post Edit screen within the "ACF Docs" Field Group, and "Choice 1" is selected. + +![Select field in the Edit Post screen](../img/select-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + select + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "select": "choice_1" + } + } + } +} +``` + +---- + +- **Previous Field:** [Repeater](./repeater.md) +- **Next Field:** [Tab](./tab.md) diff --git a/docs/fields/tab.md b/docs/fields/tab.md new file mode 100644 index 0000000..481486b --- /dev/null +++ b/docs/fields/tab.md @@ -0,0 +1,8 @@ +# Tab Field + +Tab fields are not currently supported. + +---- + +- **Previous Field:** [Select](./select.md) +- **Next Field:** [Taxonomy](./taxonomy.md) diff --git a/docs/fields/taxonomy.md b/docs/fields/taxonomy.md new file mode 100644 index 0000000..69593c1 --- /dev/null +++ b/docs/fields/taxonomy.md @@ -0,0 +1,53 @@ +# Taxonomy Field + +The Taxonomy field is added to the GraphQL Schema as a List Of the Taxonomy Type. + +For example, if the field is configured to the "Category" taxonomy, then the field in the Schema will be a List of the Category type. + +![Taxonomy field Taxonomy Config](../img/taxonomy-field-taxonomy-config.png?raw=true) + +Here, we have a Taxonomy field named `taxonomy` on the Post Edit screen within the "ACF Docs" Field Group, configured with the Category "Test Category". + +![Taxonomy field in the Edit Post screen](../img/taxonomy-field-input.png?raw=true) + +This field can be queried like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + taxonomy { + __typename + id + name + } + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "taxonomy": [ + { + "__typename": "Category", + "id": "Y2F0ZWdvcnk6Mg==", + "name": "Test Category" + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Tab](./tab.md) +- **Next Field:** [Text](./text.md) + diff --git a/docs/fields/text-area.md b/docs/fields/text-area.md new file mode 100644 index 0000000..c3b20a5 --- /dev/null +++ b/docs/fields/text-area.md @@ -0,0 +1,41 @@ +# Text Area Field + +Text Area fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Text Area fields can be queried and a String will be returned. + +Here, we have a Text Area field named `text_area` on the Post Edit screen within the "ACF Docs" Field Group. + +![Text Area field in the Edit Post screen](../img/text-area-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + textArea + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "textArea": "Text value" + } + } + } +} +``` + +---- + +- **Previous Field:** [Text](./text.md) +- **Next Field:** [Time Picker](./time-picker.md) + diff --git a/docs/fields/text.md b/docs/fields/text.md new file mode 100644 index 0000000..5c437ec --- /dev/null +++ b/docs/fields/text.md @@ -0,0 +1,41 @@ +# Text Field + +Text fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Text fields can be queried and a String will be returned. + +Here, we have a Text field named `text` on the Post Edit screen within the "ACF Docs" Field Group. + +![Text field in the Edit Post screen](../img/text-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + text + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "text": "Text Value" + } + } + } +} +``` + +---- + +- **Previous Field:** [Taxonomy](./taxonomy.md) +- **Next Field:** [Text Area](./text-area.md) + diff --git a/docs/fields/time-picker.md b/docs/fields/time-picker.md new file mode 100644 index 0000000..4f9ada3 --- /dev/null +++ b/docs/fields/time-picker.md @@ -0,0 +1,41 @@ +# Time Picker Field + +The Time Picker field is added to the WPGraphQL Schema as field with the Type `String`. + +Time Picker fields can be queried and a String will be returned. + +Here, we have a Time Picker field named `time_picker` on the Post Edit screen within the "ACF Docs" Field Group, and "12:30 am" is the value. + +![Time Picker field in the Edit Post screen](../img/time-picker-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + timePicker + } + } +} +``` + +and the result of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "timePicker": "12:30 am" + } + } + } +} +``` + +---- + +- **Previous Field:** [Text Area](text-area.md) +- **Next Field:** [True/False](true-false.md) + diff --git a/docs/fields/true-false.md b/docs/fields/true-false.md new file mode 100644 index 0000000..887f556 --- /dev/null +++ b/docs/fields/true-false.md @@ -0,0 +1,41 @@ +# True/False Field + +True/False fields are added to the WPGraphQL Schema as a field with the Type `Boolean`. + +True/False fields can be queried and a Boolean will be returned. + +Here, we have a True/False field named `true_false` on the Post Edit screen within the "ACF Docs" Field Group, and "true" is selected. + +![True/False field in the Edit Post screen](../img/true-false-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + trueFalse + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "trueFalse": true + } + } + } +} +``` + +---- + +- **Previous Field:** [Time Picker](./time-picker.md) +- **Next Field:** [url](./url.md) + diff --git a/docs/fields/url.md b/docs/fields/url.md new file mode 100644 index 0000000..1290924 --- /dev/null +++ b/docs/fields/url.md @@ -0,0 +1,41 @@ +# URL Field + +Url fields are added to the WPGraphQL Schema as a field with the Type `String`. + +Url fields can be queried and a String will be returned. + +Here, we have a URL field named `url` on the Post Edit screen within the "ACF Docs" Field Group. + +![Url field in the Edit Post screen](../img/url-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + url + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "url": "https://wpgraphql.com" + } + } + } +} +``` + +---- + +- **Previous Field:** [True/False](./true-false.md) +- **Next Field:** [User](./user.md) + diff --git a/docs/fields/user.md b/docs/fields/user.md new file mode 100644 index 0000000..3fa00fe --- /dev/null +++ b/docs/fields/user.md @@ -0,0 +1,81 @@ +# User Field + +User fields are added to the WPGraphQL Schema as a field with a User type. + +Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" as the value. + +![User field in the Edit Post screen](../img/user-field-input.png?raw=true) + +This field can be queried in GraphQL like so: + +```graphql +{ + post(id: "acf-example-test", idType: URI) { + acfDocs { + user { + id + username + firstName + lastName + } + } + } +} +``` + +and the response would look like: + +```json +{ + "data": { + "post": { + "acfDocs": { + "user": { + "id": "dXNlcjox", + "username": "jasonbahl", + "firstName": "Jason", + "lastName": "Bahl" + } + } + } + } +} +``` + +If the field is configured to allow multiple selections, it's added to the Schema as a List Of the User type. + +Here, we have a User field named `user` on the Post Edit screen within the "ACF Docs" Field Group, set with the User "jasonbahl" and "WPGraphQL" as the value. + +![User field in the Edit Post screen](../img/user-field-input-multiple.png?raw=true) + +and the response to the same query would look like: + +```json +{ + "data": { + "post": { + "acfDocs": { + "user": [ + { + "id": "dXNlcjox", + "username": "jasonbahl", + "firstName": "Jason", + "lastName": "Bahl" + }, + { + "id": "dXNlcjoy", + "username": "WPGraphQL", + "firstName": "WP", + "lastName": "GraphQL" + } + ] + } + } + } +} +``` + +---- + +- **Previous Field:** [Url](./url.md) +- **Next Field:** [WYSIWYG](./wysiwyg.md) diff --git a/docs/fields/wysiwyg.md b/docs/fields/wysiwyg.md new file mode 100644 index 0000000..d9742fb --- /dev/null +++ b/docs/fields/wysiwyg.md @@ -0,0 +1,39 @@ +# WYSIWYG Editor Field + +WYSIWYG fields are added to the WPGraphQL Schema as a field with the Type `String`. + +WYSIWYG fields can be queried and a String will be returned. + +Here, we have a WYSIWYG field named `wysiwyg` on the Post Edit screen within the "ACF Docs" Field Group. + +![WYSIWYG field in the Edit Post screen](../img/wysiwyg-field-input.png?raw=true) + +This field can be Queried in GraphQL like so: + +```graphql +{ + post( id: "acf-example-test" idType: URI ) { + acfDocs { + wysiwyg + } + } +} +``` + +and the results of the query would be: + +```json +{ + "data": { + "post": { + "acfDocs": { + "wysiwyg": "

Some content in a WYSIWYG field

\n" + } + } + } +} +``` + +---- + +- **Previous Field:** [User](./user.md) From bb82f3f4d2261bd1101205f53d6a3551f873b861 Mon Sep 17 00:00:00 2001 From: Jason Bahl Date: Mon, 19 Jul 2021 10:15:47 -0600 Subject: [PATCH 12/12] - Updating composer dependencies - run `composer install --no-dev` --- composer.json | 34 +++++++++++++++++++++------ vendor/autoload.php | 2 +- vendor/composer/InstalledVersions.php | 4 ++-- vendor/composer/autoload_real.php | 8 +++---- vendor/composer/autoload_static.php | 8 +++---- vendor/composer/installed.php | 4 ++-- vendor/composer/platform_check.php | 4 ++-- 7 files changed, 42 insertions(+), 22 deletions(-) diff --git a/composer.json b/composer.json index 8d6ce47..04e77bd 100644 --- a/composer.json +++ b/composer.json @@ -23,15 +23,32 @@ { "type": "package", "package": { - "name": "wp-premium/advanced-custom-fields-pro", + "name": "wp-graphql/acf-pro", "version": "5.8.7", "type": "wordpress-plugin", "source": { - "url": "https://github.com/wp-premium/advanced-custom-fields-pro.git", + "url": "git@github.com:wp-graphql/acf-pro.git", "type": "git", "reference": "master" } } + }, + { + "type": "package", + "package": { + "name": "hoppinger/advanced-custom-fields-wpcli", + "version": "5.8.7", + "type": "wordpress-plugin", + "source": { + "url": "git@github.com:hoppinger/advanced-custom-fields-wpcli.git", + "type": "git", + "reference": "master" + } + } + }, + { + "type":"composer", + "url":"https://wpackagist.org" } ], "scripts": { @@ -60,7 +77,7 @@ "phpstan": ["phpstan analyze --ansi --memory-limit=1G"] }, "require": { - "php": "^7" + "php": "^7.1 || ^8.0" }, "require-dev": { "lucatume/wp-browser": "^2.4", @@ -72,17 +89,20 @@ "codeception/module-cli": "^1.0", "codeception/util-universalframework": "^1.0", "dealerdirect/phpcodesniffer-composer-installer": "^0.7.1", + "composer/installers": "~1.0", "wp-coding-standards/wpcs": "2.1.1", "phpcompatibility/phpcompatibility-wp": "2.1.0", - "squizlabs/php_codesniffer": "3.5.4", + "squizlabs/php_codesniffer": "^3.5.7", "phpstan/phpstan": "^0.12.64", "szepeviktor/phpstan-wordpress": "^0.7.1", "codeception/module-rest": "^1.2", - "wp-graphql/wp-graphql-testcase": "^2.0", + "wp-graphql/wp-graphql-testcase": "~2.1", + "phpunit/phpunit": "^8.5", "simpod/php-coveralls-mirror": "^3.0", "phpstan/extension-installer": "^1.1", - "wp-graphql/wp-graphql": "^v1.3.5", - "wp-premium/advanced-custom-fields-pro": "^5" + "wp-graphql/wp-graphql": "^v1.5", + "wp-graphql/acf-pro": "^5.8", + "hoppinger/advanced-custom-fields-wpcli": "^3.2" }, "extra": { "wordpress-install-dir": "local/public", diff --git a/vendor/autoload.php b/vendor/autoload.php index 3fd5d42..7be413a 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7::getLoader(); +return ComposerAutoloaderInit5d3b6ccf12f2a55d6ca3051497f869df::getLoader(); diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php index 27cb2c9..9a89755 100644 --- a/vendor/composer/InstalledVersions.php +++ b/vendor/composer/InstalledVersions.php @@ -30,7 +30,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', + 'reference' => 'aa697aecfa118c71373cb8a5610d95b476a8fe68', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -42,7 +42,7 @@ class InstalledVersions 'aliases' => array ( ), - 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', + 'reference' => 'aa697aecfa118c71373cb8a5610d95b476a8fe68', ), ), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index fbffd1b..222c33c 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7 +class ComposerAutoloaderInit5d3b6ccf12f2a55d6ca3051497f869df { private static $loader; @@ -24,15 +24,15 @@ public static function getLoader() require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit5d3b6ccf12f2a55d6ca3051497f869df', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); - spl_autoload_unregister(array('ComposerAutoloaderInit0a0c5bc70822171ca4c2b4ec332850b7', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit5d3b6ccf12f2a55d6ca3051497f869df', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit5d3b6ccf12f2a55d6ca3051497f869df::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 71a176d..5c09178 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7 +class ComposerStaticInit5d3b6ccf12f2a55d6ca3051497f869df { public static $prefixLengthsPsr4 = array ( 'W' => @@ -49,9 +49,9 @@ class ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit0a0c5bc70822171ca4c2b4ec332850b7::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit5d3b6ccf12f2a55d6ca3051497f869df::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit5d3b6ccf12f2a55d6ca3051497f869df::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit5d3b6ccf12f2a55d6ca3051497f869df::$classMap; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 789750d..fd591e0 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -6,7 +6,7 @@ 'aliases' => array ( ), - 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', + 'reference' => 'aa697aecfa118c71373cb8a5610d95b476a8fe68', 'name' => 'wp-graphql/wp-graphql-acf', ), 'versions' => @@ -18,7 +18,7 @@ 'aliases' => array ( ), - 'reference' => 'fcb8eff327217940b33752a4efe12c7b066da7eb', + 'reference' => 'aa697aecfa118c71373cb8a5610d95b476a8fe68', ), ), ); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php index f79e574..6d3407d 100644 --- a/vendor/composer/platform_check.php +++ b/vendor/composer/platform_check.php @@ -4,8 +4,8 @@ $issues = array(); -if (!(PHP_VERSION_ID >= 70000)) { - $issues[] = 'Your Composer dependencies require a PHP version ">= 7.0.0". You are running ' . PHP_VERSION . '.'; +if (!(PHP_VERSION_ID >= 70100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.0". You are running ' . PHP_VERSION . '.'; } if ($issues) {