diff --git a/.env.dist b/.env.dist index be67c60..6f08155 100644 --- a/.env.dist +++ b/.env.dist @@ -53,5 +53,4 @@ ACF_PRO=false # ACF Extended # ACF_EXTENDED_LICENSE_KEY="Your License Key" -ACF_EXTENDED_PRO=false ACF_EXTENDED_VERSION="latest" diff --git a/.github/workflows/testing-integration.yml b/.github/workflows/testing-integration.yml index 9108c24..8478b85 100644 --- a/.github/workflows/testing-integration.yml +++ b/.github/workflows/testing-integration.yml @@ -22,7 +22,6 @@ jobs: wordpress: [ '6.1', '5.9' ] acf_pro: [ true, false ] acf_version: [ 5.12.4, 6.1.6 ] - acf_extended_pro: [ true, false ] include: - php: '8.1' wordpress: '6.1' @@ -86,7 +85,6 @@ jobs: -e ACF_PRO=${{matrix.acf_pro }} \ -e ACF_LICENSE_KEY=${{secrets.ACF_LICENSE_KEY}} \ -e ACF_VERSION=${{matrix.ACF_VERSION}} \ - -e ACF_EXTENDED_PRO=${{matrix.acf_extended_pro}} \ -e ACF_EXTENDED_LICENSE_KEY=${{secrets.ACF_EXTENDED_LICENSE_KEY}} \ testing diff --git a/docker/app.setup.sh b/docker/app.setup.sh index f0746bb..49bb5f2 100644 --- a/docker/app.setup.sh +++ b/docker/app.setup.sh @@ -73,7 +73,7 @@ else fi # If ACF_EXTENDED_PRO is not true, or the license key is a default value, we'll be using the FREE version of ACF EXTENDED -if [[ true != ${ACF_EXTENDED_PRO} || '.' == ${ACF_EXTENDED_LICENSE_KEY} || 'Your License Key' == ${ACF_EXTENDED_LICENSE_KEY} ]]; then +if [[ true != ${ACF_PRO} || '.' == ${ACF_EXTENDED_LICENSE_KEY} || 'Your License Key' == ${ACF_EXTENDED_LICENSE_KEY} ]]; then echo "ACF EXTENDED Version: " ${ACF_EXTENDED_VERSION} ACF_EXTENDED_PLUGIN_SLUG="acf-extended/acf-extended.php" diff --git a/src/Admin/Settings.php b/src/Admin/Settings.php index 6448a68..14de7b2 100644 --- a/src/Admin/Settings.php +++ b/src/Admin/Settings.php @@ -136,6 +136,8 @@ public function graphql_types_ajax_callback(): void { if ( ! isset( $_POST['data'] ) ) { echo esc_html( __( 'No location rules were found', 'wp-graphql-acf' ) ); + + /** @noinspection ForgottenDebugOutputInspection */ wp_die(); } @@ -208,7 +210,7 @@ public function display_graphql_field_group_fields( $field_group ): void { 'type' => 'true_false', 'name' => 'show_in_graphql', 'prefix' => 'acf_field_group', - 'value' => isset( $field_group['show_in_graphql'] ) && (bool) $field_group['show_in_graphql'], + 'value' => isset( $field_group['show_in_graphql'] ) ? (bool) $field_group['show_in_graphql'] : 1, 'ui' => 1, ], 'div', diff --git a/src/Data/Loader/AcfOptionsPageLoader.php b/src/Data/Loader/AcfOptionsPageLoader.php new file mode 100644 index 0000000..bc0bc0b --- /dev/null +++ b/src/Data/Loader/AcfOptionsPageLoader.php @@ -0,0 +1,36 @@ +get_registry()->get_fields_for_field_group( $layout ); + $layout['fields'] = [ + 'fieldGroupName' => [ + 'type' => 'String', + 'description' => __( 'The name of the ACF Flex Field Layout', 'wp-graphql-acf' ), + 'deprecationReason' => __( 'Use __typename instead', 'wp-graphql-acf' ), + ], + ]; $layout['interfaces'] = $interfaces; $layouts[ $layout_name ] = $layout; } diff --git a/src/LocationRules.php b/src/LocationRules.php index 6f53342..18e8412 100644 --- a/src/LocationRules.php +++ b/src/LocationRules.php @@ -962,10 +962,11 @@ public function determine_options_rules( string $field_group_name, string $param $options_page = acf_get_options_page( $value ); - if ( ! isset( $options_page['show_in_graphql'] ) || false === (bool) $options_page['show_in_graphql'] ) { + if ( empty( $options_page ) || ! \WPGraphQLAcf\Utils::should_field_group_show_in_graphql( $options_page ) ) { 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'] ); + + $type_name = \WPGraphQLAcf\Utils::get_field_group_name( $options_page ); $this->set_graphql_type( $field_group_name, $type_name ); } diff --git a/src/Model/AcfOptionsPage.php b/src/Model/AcfOptionsPage.php new file mode 100644 index 0000000..5deb1fc --- /dev/null +++ b/src/Model/AcfOptionsPage.php @@ -0,0 +1,63 @@ +data = $options_page; + parent::__construct(); + } + + /** + * @return array + */ + public function get_data(): array { + return $this->data; + } + + /** + * @return void + */ + protected function init(): void { + if ( empty( $this->fields ) ) { + $this->fields = [ + 'slug' => $this->data['menu_slug'] ?? null, + 'pageTitle' => $this->data['page_title'] ?? null, + 'menuTitle' => $this->data['menu_title'] ?? null, + 'parentId' => function () { + $type_name = Utils::format_type_name( \WPGraphQLAcf\Utils::get_field_group_name( $this->data ) ); + return ! empty( $this->data['parent_slug'] ) ? Relay::toGlobalId( 'acf_options_page', $this->data['parent_slug'] ) : null; + }, + 'id' => function () { + return Relay::toGlobalId( 'acf_options_page', $this->data['menu_slug'] ); + }, + 'acfId' => 'options', + ]; + } + } +} diff --git a/src/Registry.php b/src/Registry.php index 65dd991..653710c 100644 --- a/src/Registry.php +++ b/src/Registry.php @@ -4,8 +4,12 @@ use Exception; use GraphQL\Error\Error; +use GraphQL\Type\Definition\ResolveInfo; +use WPGraphQL\AppContext; use WPGraphQL\Registry\TypeRegistry; use WPGraphQL\Utils\Utils; +use WPGraphQLAcf\Data\Loader\AcfOptionsPageLoader; +use WPGraphQLAcf\Model\AcfOptionsPage; class Registry { @@ -76,24 +80,7 @@ public function has_registered_field_group( string $key ): bool { * @return bool */ public function should_field_group_show_in_graphql( array $acf_field_group ): bool { - - $should = true; - - if ( isset( $acf_field_group['active'] ) && false === $acf_field_group['active'] ) { - $should = false; - } - - // if the field group was configured with no "show_in_graphql" value, default to the "show_in_rest" value - // to determine if the group should be available in an API - if ( ! isset( $acf_field_group['show_in_graphql'] ) ) { - $acf_field_group['show_in_graphql'] = $acf_field_group['show_in_rest'] ?? false; - } - - if ( isset( $acf_field_group['show_in_graphql'] ) && false === $acf_field_group['show_in_graphql'] ) { - $should = false; - } - - return (bool) apply_filters( 'graphql_acf_should_field_group_show_in_graphql', $should, $acf_field_group ); + return \WPGraphQLAcf\Utils::should_field_group_show_in_graphql( $acf_field_group ); } /** @@ -269,6 +256,26 @@ public function register_initial_graphql_types(): void { ], ], ] ); + + register_graphql_interface_type( 'AcfOptionsPage', [ + 'interfaces' => [ 'Node' ], + 'description' => __( 'Options Page registered by ACF', 'wp-graphql-acf' ), + 'fields' => [ + 'id' => [ + 'type' => [ 'non_null' => 'ID' ], + ], + 'pageTitle' => [ + 'type' => 'String', + ], + 'menuTitle' => [ + 'type' => 'String', + ], + 'parentId' => [ + 'type' => 'String', + ], + ], + ] ); + } /** @@ -277,6 +284,7 @@ public function register_initial_graphql_types(): void { * @param array $acf_field_group The ACF Field Group config * * @return array + * @throws Error */ public function get_field_group_interfaces( array $acf_field_group ): array { @@ -307,6 +315,76 @@ public function get_field_group_interfaces( array $acf_field_group ): array { } + /** + * @return void + * @throws Error + * @throws Exception + */ + public function register_options_pages():void { + + if ( ! function_exists( 'acf_get_options_pages' ) ) { + return; + } + + $graphql_options_pages = acf_get_options_pages(); + + if ( empty( $graphql_options_pages ) ) { + return; + } + + foreach ( $graphql_options_pages as $graphql_options_page ) { + if ( ! $this->should_field_group_show_in_graphql( $graphql_options_page ) ) { + continue; + } + + $type_name = $this->get_field_group_graphql_type_name( $graphql_options_page ); + + if ( empty( $type_name ) ) { + continue; + } + + register_graphql_object_type( $type_name, [ + 'interfaces' => [ 'AcfOptionsPage' ], + 'model' => AcfOptionsPage::class, + 'fields' => [ + 'id' => [ + 'type' => [ 'non_null' => 'ID' ], + ], + 'pageTitle' => [ + 'type' => 'String', + ], + ], + ] ); + + $field_name = Utils::format_field_name( $type_name ); + + $interface_name = 'WithAcfOptionsPage' . $type_name; + + register_graphql_interface_type( $interface_name, [ + 'description' => sprintf( __( 'Access point for the "%s" ACF Options Page', 'wp-graphql-acf' ), $type_name ), + 'fields' => [ + $field_name => [ + 'type' => $type_name, + 'resolve' => function ( $source, $args, AppContext $context, ResolveInfo $info ) use ( $graphql_options_page ) { + + $loader = $context->get_loader( 'acf_options_page' ); + + if ( ! $loader instanceof AcfOptionsPageLoader ) { + return 'null'; + } + + return $context->get_loader( 'acf_options_page' )->load_deferred( $graphql_options_page['menu_slug'] ); + }, + ], + ], + ]); + + register_graphql_interfaces_to_types( [ $interface_name ], [ 'RootQuery' ] ); + + } + + } + /** * @param array $acf_field_group * @@ -391,42 +469,7 @@ public function map_acf_field_to_graphql( array $acf_field, array $acf_field_gro * @throws \GraphQL\Error\Error */ public function get_field_group_name( array $field_group ): string { - - $field_group_name = ''; - - - - if ( ! empty( $field_group['graphql_field_name'] ) ) { - $field_group_name = $field_group['graphql_field_name']; - $field_group_name = preg_replace( '/[^0-9a-zA-Z_\s]/i', '', $field_group_name ); - } else { - if ( ! empty( $field_group['name'] ) ) { - $field_group_name = $field_group['name']; - } elseif ( ! empty( $field_group['title'] ) ) { - $field_group_name = $field_group['title']; - } elseif ( ! empty( $field_group['label'] ) ) { - $field_group_name = $field_group['label']; - } - $field_group_name = preg_replace( '/[^0-9a-zA-Z_\s]/i', ' ', $field_group_name ); - // if the graphql_field_name isn't explicitly defined, we'll format it without underscores - $field_group_name = Utils::format_field_name( $field_group_name, false ); - } - - if ( empty( $field_group_name ) ) { - return $field_group_name; - } - - $starts_with_string = is_numeric( substr( $field_group_name, 0, 1 ) ); - - if ( $starts_with_string ) { - graphql_debug( __( 'The ACF Field or Field Group could not be added to the schema. GraphQL Field and Type names cannot start with a number', 'wp-graphql-acf' ), [ - 'invalid' => $field_group, - ] ); - return ''; - } - - return $field_group_name; - + return \WPGraphQLAcf\Utils::get_field_group_name( $field_group ); } /** @@ -507,6 +550,10 @@ public function get_location_rules( array $acf_field_groups = [] ): array { */ public function get_graphql_locations_for_field_group( array $field_group, array $acf_field_groups ): array { + if ( ! $this->should_field_group_show_in_graphql( $field_group ) ) { + return []; + } + $graphql_types = $field_group['graphql_types'] ?? []; $field_group_name = $field_group['graphql_field_name'] ?? $field_group['title']; diff --git a/src/ThirdParty.php b/src/ThirdParty.php index c62fe45..44118ec 100644 --- a/src/ThirdParty.php +++ b/src/ThirdParty.php @@ -12,6 +12,10 @@ public function init(): void { $acfe = new ThirdParty\AcfExtended\AcfExtended(); $acfe->init(); + // Initialize support for WPGraphQL Smart Cache + $smart_cache = new ThirdParty\WPGraphQLSmartCache\WPGraphQLSmartCache(); + $smart_cache->init(); + } } diff --git a/src/ThirdParty/WPGraphQLSmartCache/WPGraphQLSmartCache.php b/src/ThirdParty/WPGraphQLSmartCache/WPGraphQLSmartCache.php new file mode 100644 index 0000000..dd41a0c --- /dev/null +++ b/src/ThirdParty/WPGraphQLSmartCache/WPGraphQLSmartCache.php @@ -0,0 +1,72 @@ +invalidation = $invalidation; + + add_action( 'updated_option', [ $this, 'updated_acf_option_cb' ], 10, 4 ); + + } + + /** + * Purge Cache after ACF Option Page is updated + * + * @param string $option The name of the option being updated + * @param mixed $value The value of the option being updated + * @param mixed $original_value The original / previous value of the option + * + * @return void + */ + public function updated_acf_option_cb( string $option, $value, $original_value ): void { + + // phpcs:ignore + if ( ! isset( $_POST['_acf_screen'] ) || 'options' !== $_POST['_acf_screen'] ) { + return; + } + + // phpcs:ignore + $options_page = $_GET['page'] ?? null; + + if ( empty( $options_page ) ) { + return; + } + + $id = \GraphQLRelay\Relay::toGlobalId( 'acf_options_page', $options_page ); + + // @phpstan-ignore-next-line + $this->invalidation->purge( $id, sprintf( 'update_acf_options_page ( "%s" )', $options_page ) ); + + } + +} diff --git a/src/Utils.php b/src/Utils.php index d263308..d1826e5 100644 --- a/src/Utils.php +++ b/src/Utils.php @@ -8,6 +8,7 @@ use WPGraphQL\Model\Post; use WPGraphQL\Model\Term; use WPGraphQL\Model\User; +use WPGraphQLAcf\Model\AcfOptionsPage; class Utils { @@ -49,6 +50,9 @@ public static function get_node_acf_id( $node ) { case is_array( $node ) && isset( $node['post_id'] ) && 'options' === $node['post_id']: $id = $node['post_id']; break; + case $node instanceof AcfOptionsPage: + $id = $node->acfId; + break; default: $id = 0; break; @@ -146,6 +150,10 @@ public static function get_all_graphql_types(): array { 'label' => __( 'Page Template', 'wp-graphql-acf' ), 'plural_label' => __( 'All Templates Assignable to Content', 'wp-graphql-acf' ), ], + 'AcfOptionsPage' => [ + 'label' => __( 'ACF Options Page', 'wp-graphql-acf' ), + 'plural_label' => __( 'All Options Pages registered by ACF', 'wp-graphql-acf' ), + ], ]; foreach ( $interfaces as $interface_name => $config ) { @@ -194,12 +202,8 @@ public static function get_all_graphql_types(): array { */ $graphql_types['User'] = __( 'User', 'wp-graphql-acf' ); - /** - * Add options pages to GraphQL types - */ - global $acf_options_page; + if ( function_exists( 'acf_get_options_pages' ) ) { - if ( isset( $acf_options_page ) && function_exists( 'acf_get_options_pages' ) ) { // Get a list of post types that have been registered to show in graphql $graphql_options_pages = acf_get_options_pages(); @@ -211,13 +215,13 @@ public static function get_all_graphql_types(): array { /** * Prepare type key prefix and label surfix */ - $label = ' (' . __( 'ACF Options Page', 'wp-graphql-acf' ) . ')'; + $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'] ) { + if ( isset( $options_page['show_in_graphql'] ) && ! $options_page['show_in_graphql'] ) { continue; } @@ -225,7 +229,7 @@ public static function get_all_graphql_types(): array { * Get options page properties. */ $page_title = $options_page['page_title']; - $type_label = $page_title . $label; + $type_label = $page_title . ' ' . $label; $type_name = isset( $options_page['graphql_field_name'] ) ? \WPGraphQL\Utils\Utils::format_type_name( $options_page['graphql_field_name'] ) : \WPGraphQL\Utils\Utils::format_type_name( $options_page['menu_slug'] ); $graphql_types[ $type_name ] = $type_label; @@ -236,6 +240,98 @@ public static function get_all_graphql_types(): array { return $graphql_types; } + /** + * Whether the ACF Field Group should show in the GraphQL Schema + * + * @param array $acf_field + * + * @return bool + */ + public static function should_field_show_in_graphql( array $acf_field ): bool { + return self::should_field_group_show_in_graphql( $acf_field ); + } + + /** + * Whether the ACF Field Group should show in the GraphQL Schema + * + * @param array $acf_field_group + * + * @return bool + */ + public static function should_field_group_show_in_graphql( array $acf_field_group ): bool { + + $should = true; + + if ( isset( $acf_field_group['active'] ) && false === $acf_field_group['active'] ) { + $should = false; + } + + $show_in_rest = $acf_field_group['show_in_rest'] ?? false; + + + // if the field group was configured with no "show_in_graphql" value, default to the "show_in_rest" value + // to determine if the group should be available in an API + if ( + ( isset( $acf_field_group['post_id'] ) && 'options' !== $acf_field_group['post_id'] ) && + ! isset( $acf_field_group['show_in_graphql'] ) ) { + $acf_field_group['show_in_graphql'] = $show_in_rest ?? false; + } + + if ( isset( $acf_field_group['show_in_graphql'] ) && false === $acf_field_group['show_in_graphql'] ) { + $should = false; + } + + return (bool) apply_filters( 'graphql_acf_should_field_group_show_in_graphql', $should, $acf_field_group ); + + } + + /** + * Given a field group config, return the name of the field group to be used in the GraphQL + * Schema + * + * @param array $field_group The field group config array + * + * @return string + */ + public static function get_field_group_name( array $field_group ): string { + + $field_group_name = ''; + + if ( ! empty( $field_group['graphql_field_name'] ) ) { + $field_group_name = $field_group['graphql_field_name']; + $field_group_name = preg_replace( '/[^0-9a-zA-Z_\s]/i', '', $field_group_name ); + } else { + if ( ! empty( $field_group['name'] ) ) { + $field_group_name = $field_group['name']; + } elseif ( ! empty( $field_group['title'] ) ) { + $field_group_name = $field_group['title']; + } elseif ( ! empty( $field_group['label'] ) ) { + $field_group_name = $field_group['label']; + } elseif ( ! empty( $field_group['page_title'] ) ) { + $field_group_name = $field_group['page_title']; + } + $field_group_name = preg_replace( '/[^0-9a-zA-Z_\s]/i', ' ', $field_group_name ); + // if the graphql_field_name isn't explicitly defined, we'll format it without underscores + $field_group_name = \WPGraphQL\Utils\Utils::format_field_name( $field_group_name, false ); + } + + if ( empty( $field_group_name ) ) { + return $field_group_name; + } + + $starts_with_string = is_numeric( substr( $field_group_name, 0, 1 ) ); + + if ( $starts_with_string ) { + graphql_debug( __( 'The ACF Field or Field Group could not be added to the schema. GraphQL Field and Type names cannot start with a number', 'wp-graphql-acf' ), [ + 'invalid' => $field_group, + ] ); + return ''; + } + + return $field_group_name; + + } + /** * Returns string of the items in the array list. Limit allows string to be limited length. * @@ -244,7 +340,7 @@ public static function get_all_graphql_types(): array { * * @return string */ - public static function array_list_by_limit( array $list, $limit = 5 ): string { + public static function array_list_by_limit( array $list, int $limit = 5 ): string { $flat_list = ''; $total = count( $list ); diff --git a/src/WPGraphQLAcf.php b/src/WPGraphQLAcf.php index 22818f4..0423704 100644 --- a/src/WPGraphQLAcf.php +++ b/src/WPGraphQLAcf.php @@ -37,6 +37,11 @@ public function init(): void { add_action( 'admin_init', [ $this, 'init_admin_settings' ] ); add_action( 'after_setup_theme', [ $this, 'cpt_tax_registration' ] ); add_action( 'graphql_register_types', [ $this, 'init_registry' ] ); + + add_filter( 'graphql_data_loaders', [ $this, 'register_loaders' ], 10, 2 ); + add_filter( 'graphql_resolve_node_type', [ $this, 'resolve_acf_options_page_node' ], 10, 2 ); + + do_action( 'graphql_acf_init' ); } @@ -85,6 +90,7 @@ public function init_registry( TypeRegistry $type_registry ): void { // of the specific fields and field groups registered by ACF $registry = new Registry( $type_registry ); $registry->register_initial_graphql_types(); + $registry->register_options_pages(); // Get the field groups that should be mapped to the Schema $acf_field_groups = $registry->get_acf_field_groups(); @@ -161,6 +167,31 @@ function () use ( $can_load_messages ) { ); } + /** + * @param mixed $type The GraphQL Type to return based on the resolving node + * @param mixed $node The Node being resolved + * + * @return mixed + */ + public function resolve_acf_options_page_node( $type, $node ) { + if ( $node instanceof \WPGraphQLAcf\Model\AcfOptionsPage ) { + return \WPGraphQLAcf\Utils::get_field_group_name( $node->get_data() ); + } + return $type; + } + + /** + * @param array $loaders + * @param \WPGraphQL\AppContext $context + * + * @return array + */ + public function register_loaders( array $loaders, \WPGraphQL\AppContext $context ): array { + $loaders['acf_options_page'] = new \WPGraphQLAcf\Data\Loader\AcfOptionsPageLoader( $context ); + return $loaders; + } + + /** * Output graphql debug messages if the plugin cannot load properly. * diff --git a/tests/_support/WPUnit/AcfFieldTestCase.php b/tests/_support/WPUnit/AcfFieldTestCase.php index cd20167..2625678 100644 --- a/tests/_support/WPUnit/AcfFieldTestCase.php +++ b/tests/_support/WPUnit/AcfFieldTestCase.php @@ -12,10 +12,13 @@ */ abstract class AcfFieldTestCase extends WPGraphQLAcfTestCase { + public $acf_plugin_version; + /** * @return void */ public function setUp(): void { + $this->acf_plugin_version = $_ENV['ACF_VERSION'] ?? 'latest'; $this->clearSchema(); parent::setUp(); } diff --git a/tests/_support/WPUnit/AcfeFieldTestCase.php b/tests/_support/WPUnit/AcfeFieldTestCase.php index aa65426..55fd98d 100644 --- a/tests/_support/WPUnit/AcfeFieldTestCase.php +++ b/tests/_support/WPUnit/AcfeFieldTestCase.php @@ -6,8 +6,8 @@ abstract class AcfeFieldTestCase extends \Tests\WPGraphQLAcf\WPUnit\AcfFieldTest public function _setUp() { // if the plugin version is before 6.1, we're not testing this functionality - if ( ! isset( $_ENV['ACF_PRO'] ) || true !== (bool) $_ENV['ACF_PRO'] || version_compare( $this->acf_plugin_version, '6.1', 'lt' ) ) { - $this->markTestSkipped( sprintf( 'Version "%s" does not include the ability to register custom post types, so we do not need to test the extensions of the feature', $this->acf_plugin_version ) ); + if ( ! class_exists( 'ACFE_PRO' ) ) { + $this->markTestSkipped( 'ACF Extended Pro is not active so this test will not run.' ); } parent::_setUp(); // TODO: Change the autogenerated stub diff --git a/tests/wpunit/FieldTypes/AcfeAdvancedLinkFieldTest.php b/tests/wpunit/FieldTypes/AcfeAdvancedLinkFieldTest.php index fc8ffd7..abb603a 100644 --- a/tests/wpunit/FieldTypes/AcfeAdvancedLinkFieldTest.php +++ b/tests/wpunit/FieldTypes/AcfeAdvancedLinkFieldTest.php @@ -6,12 +6,6 @@ class AcfeAdvancedLinkFieldTest extends \Tests\WPGraphQLAcf\WPUnit\AcfeFieldTest * @return void */ public function setUp(): void { - - // if the plugin version is before 6.1, we're not testing this functionality - if ( ! isset( $_ENV['ACF_PRO'] ) || true !== (bool) $_ENV['ACF_PRO'] || version_compare( $this->acf_plugin_version, '6.1', 'lt' ) ) { - $this->markTestSkipped( sprintf( 'Version "%s" does not include the ability to register custom post types, so we do not need to test the extensions of the feature', $this->acf_plugin_version ) ); - } - parent::setUp(); } diff --git a/tests/wpunit/OptionsPageTest.php b/tests/wpunit/OptionsPageTest.php new file mode 100644 index 0000000..52b76cf --- /dev/null +++ b/tests/wpunit/OptionsPageTest.php @@ -0,0 +1,290 @@ +markTestSkipped( 'ACF Options Pages are not available in this test environment' ); + } + + parent::setUp(); + } + + public function tearDown(): void { + parent::tearDown(); + } + + public function registerOptionsPage( $title = 'My Options Page', $config = [] ) { + + + // register options page + acf_add_options_page( + array_merge( [ + 'page_title' => $title, + 'menu_title' => __( 'My Options Page' ), + 'menu_slug' => 'my-options-page', + 'capability' => 'edit_posts', + // options pages will show in the Schema unless set to false + // 'show_in_graphql' => false, + ], $config ) + ); + } + + public function testAcfOptionsPageIsQueryableInSchema() { + + $this->registerOptionsPage(); + + $expected_value = uniqid( 'test', true ); + + // Save a value to the ACF Option Field + // see: https://www.advancedcustomfields.com/resources/update_field/#update-a-value-from-different-objects + if ( function_exists( 'update_field' ) ) { + update_field( 'text', $expected_value, 'option' ); + } + + $this->register_acf_field( [], [ + 'graphql_field_name' => 'OptionsFields', + 'location' => [ + [ + [ + 'param' => 'options_page', + 'operator' => '==', + 'value' => 'my-options-page', + ], + ], + ], + ]); + + $query = ' + { + myOptionsPage { + optionsFields { + text + } + } + } + '; + + $actual = $this->graphql([ + 'query' => $query, + ]); + + self::assertQuerySuccessful( $actual, [ + // the instructions should be used for the description + $this->expectedField( 'myOptionsPage.optionsFields.text', $expected_value ), + ] ); + + $query = ' + query GetType( $name: String! ) { + __type( name: $name ) { + name + } + } + '; + + $actual = $this->graphql( [ + 'query' => $query, + 'variables' => [ + 'name' => 'MyOptionsPage', + ] + ]); + + self::assertQuerySuccessful( $actual, [ + $this->expectedField( '__type.name', 'MyOptionsPage' ) + ]); + + } + + // @todo: + // - options page not in Schema if "show_in_graphql" set to false + /** + * @throws Exception + */ + public function testOptionsPageNotInSchemaIfShowInGraphqlIsFalse() { + + acf_add_options_page( + [ + 'page_title' => 'ShowInGraphQLFalse', + 'menu_title' => __( 'Show in GraphQL False' ), + 'menu_slug' => 'show-in-graphql-false', + 'capability' => 'edit_posts', + // options pages will show in the Schema unless set to false + 'show_in_graphql' => false, + ] + ); + + $options_pages = acf_get_options_pages(); + + codecept_debug( [ + '$pages' => $options_pages, + ]); + + $this->register_acf_field( [], [ + 'graphql_field_name' => 'showInGraphqlFields', + 'location' => [ + [ + [ + 'param' => 'options_page', + 'operator' => '==', + 'value' => 'show-in-graphql-false', + ], + ], + ], + ]); + + // Ensure the options page was registered to ACF Options Pages properly + $this->assertTrue( array_key_exists( 'show-in-graphql-false', $options_pages ) ); + + $query = ' + { + showInGraphQLFalse { + showInGraphqlFields { + text + } + } + } + '; + + $actual = $this->graphql([ + 'query' => $query, + ]); + + $this->assertArrayHasKey( 'errors', $actual ); + + $query = ' + query GetType( $name: String! ) { + __type( name: $name ) { + name + } + } + '; + + $actual = $this->graphql( [ + 'query' => $query, + 'variables' => [ + 'name' => 'ShowInGraphQLFalse', + ] + ]); + + $this->assertQueryError( $actual, [ + $this->expectedField( '__type', self::IS_NULL ) + ]); + + } + + // - options page shows with different name if "graphql_field_name" is set + public function testOptionsPageRespectsGraphqlFieldName() { + acf_add_options_page( + [ + 'page_title' => 'CustomGraphqlName', + 'menu_title' => __( 'Show in GraphQL False' ), + 'menu_slug' => 'custom-graphql-name', + 'capability' => 'edit_posts', + // options pages will show in the Schema unless set to false + 'graphql_field_name' => 'MyCustomOptionsName', + ] + ); + + $expected_value = uniqid( 'test', true ); + + // Save a value to the ACF Option Field + // see: https://www.advancedcustomfields.com/resources/update_field/#update-a-value-from-different-objects + if ( function_exists( 'update_field' ) ) { + update_field( 'text', $expected_value, 'option' ); + } + + $this->register_acf_field( [], [ + 'graphql_field_name' => 'OptionsFields', + 'location' => [ + [ + [ + 'param' => 'options_page', + 'operator' => '==', + 'value' => 'custom-graphql-name', + ], + ], + ], + ]); + + $query = ' + { + myCustomOptionsName { + optionsFields { + text + } + } + } + '; + + $actual = $this->graphql([ + 'query' => $query, + ]); + + self::assertQuerySuccessful( $actual, [ + // the instructions should be used for the description + $this->expectedField( 'myCustomOptionsName.optionsFields.text', $expected_value ), + ] ); + + $query = ' + query GetType( $name: String! ) { + __type( name: $name ) { + name + } + } + '; + + $actual = $this->graphql( [ + 'query' => $query, + 'variables' => [ + 'name' => 'MyCustomOptionsName', + ] + ]); + + self::assertQuerySuccessful( $actual, [ + $this->expectedField( '__type.name', 'MyCustomOptionsName' ) + ]); + } + + public function testQueryOptionsPageAsNode() { + + acf_add_options_page( + [ + 'page_title' => 'OptionsPageNode', + 'menu_title' => __( 'Options Page Node' ), + 'menu_slug' => 'options-page-node', + 'capability' => 'edit_posts', + // options pages will show in the Schema unless set to false + 'graphql_field_name' => 'OptionsPageNode', + ] + ); + + $query = ' + query GetOptionsPage($id: ID!) { + node(id:$id) { + id + __typename + ...on AcfOptionsPage { + menuTitle + } + } + } + '; + + $id = \GraphQLRelay\Relay::toGlobalId( 'acf_options_page', 'options-page-node' ); + + $actual = $this->graphql([ + 'query' => $query, + 'variables' => [ + 'id' => $id + ] + ]); + + self::assertQuerySuccessful( $actual, [ + $this->expectedField( 'node.__typename', 'OptionsPageNode' ), + $this->expectedField( 'node.id', $id ) + ]); + + } + +}