From f73c62286823fcfb9f6df9619a6a4874fc2bfbab Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 5 Feb 2025 11:19:04 -0300 Subject: [PATCH 1/9] Initial commit of the ACF Repeater feature --- elasticpress.php | 4 + .../Feature/AcfRepeater/AcfRepeater.php | 121 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 includes/classes/Feature/AcfRepeater/AcfRepeater.php diff --git a/elasticpress.php b/elasticpress.php index 3e5227ffd..4b0aa835b 100644 --- a/elasticpress.php +++ b/elasticpress.php @@ -190,6 +190,10 @@ function register_indexable_posts() { new Feature\Documents\Documents() ); + Features::factory()->register_feature( + new Feature\AcfRepeater\AcfRepeater() + ); + Features::factory()->register_feature( new Feature\Comments\Comments() ); diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php new file mode 100644 index 000000000..823ceb190 --- /dev/null +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -0,0 +1,121 @@ +slug = 'acf_repeater'; + + parent::__construct(); + } + + /** + * Sets i18n strings. + */ + public function set_i18n_strings(): void { + $this->title = esc_html__( 'ACF Repeater Field Compatibility', 'elasticpress' ); + + $this->short_title = esc_html__( 'ACF Repeater', 'elasticpress' ); + + $this->summary = '

' . __( 'Index your ACF Repeater fields as a single text field.', 'elasticpress' ) . '

'; + + $this->docs_url = __( 'https://www.elasticpress.io/documentation/article/configuring-elasticpress-via-the-plugin-dashboard/#autosuggest', 'elasticpress' ); + } + + /** + * Setup feature functionality + */ + public function setup() { + add_action( 'acf/render_field_settings', [ $this, 'render_field_settings' ] ); + add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'add_meta_keys' ], 10, 2 ); + } + + /** + * Render field in the ACF group admin screen + * + * @param array $field ACF Field array. + * @return void + */ + public function render_field_settings( $field ): void { + if ( ! function_exists( 'acf_render_field_setting' ) ) { + return; + } + + // We only want repeaters and fields that are not children of repeaters. + if ( 'repeater' !== $field['type'] || ! empty( $field['parent_repeater'] ) ) { + return; + } + + // Root level fields are children of the post object. + $post_parent = ! empty( $field['parent'] ) ? get_post( $field['parent'] ) : false; + if ( ! $post_parent ) { + return; + } + + \acf_render_field_setting( + $field, + [ + 'label' => esc_html__( 'Index in ElasticPress', 'elasticpress' ), + 'instructions' => esc_html__( 'Index this field as a single text field', 'elasticpress' ), + 'name' => 'ep_index_repeater_field', + 'type' => 'true_false', + 'ui' => 1, + ] + ); + } + + /** + * Add to the weighting dashboard all the ACF Repeater fields that were checked to be indexed. + * + * @param array $meta List of allowed meta keys + * @param \WP_Post $post The post object. + * @return array + */ + public function add_meta_keys( $meta, $post ) { + $field_groups = acf_get_field_groups( + array( + 'post_id' => $post->ID, + 'post_type' => $post->post_type, + ) + ); + + if ( empty( $field_groups ) ) { + return $meta; + } + + $ep_fields = []; + + foreach ( $field_groups as $field_group ) { + $fields = acf_get_fields( $field_group ); + foreach ( $fields as $field ) { + if ( empty( $field['ep_index_repeater_field'] ) ) { + continue; + } + + $ep_fields[] = $field['name']; + } + } + + $meta = array_unique( array_merge( $meta, $ep_fields ) ); + + return $meta; + } +} From 42d8298560552447e3a4989b7caca46fb8fbd519 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Wed, 5 Feb 2025 11:25:40 -0300 Subject: [PATCH 2/9] Check if the plugin is installed --- .../classes/Feature/AcfRepeater/AcfRepeater.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php index 823ceb190..62f4f268b 100644 --- a/includes/classes/Feature/AcfRepeater/AcfRepeater.php +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -9,6 +9,7 @@ namespace ElasticPress\Feature\AcfRepeater; use ElasticPress\Feature; +use ElasticPress\FeatureRequirementsStatus; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. @@ -40,6 +41,22 @@ public function set_i18n_strings(): void { $this->docs_url = __( 'https://www.elasticpress.io/documentation/article/configuring-elasticpress-via-the-plugin-dashboard/#autosuggest', 'elasticpress' ); } + /** + * Determine WC feature reqs status + * + * @return FeatureRequirementsStatus + */ + public function requirements_status() { + $status = new FeatureRequirementsStatus( 0 ); + + if ( ! function_exists( 'acf_get_field_groups' ) ) { + $status->code = 2; + $status->message = esc_html__( 'ACF Pro not installed.', 'elasticpress' ); + } + + return $status; + } + /** * Setup feature functionality */ From a917ec1fba51e504c9eaf07ad72cb613463f628f Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 6 Feb 2025 09:41:21 -0300 Subject: [PATCH 3/9] Index repeater fields as JSON objects --- .../Feature/AcfRepeater/AcfRepeater.php | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php index 62f4f268b..190eccc3d 100644 --- a/includes/classes/Feature/AcfRepeater/AcfRepeater.php +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -19,6 +19,19 @@ * ACF Repeater Field Compatibility feature class */ class AcfRepeater extends Feature { + /** + * List of ACF functions we use + * + * @var array + */ + protected $acf_functions = [ + 'acf_get_field_groups', + 'acf_render_field_setting', + 'acf_get_fields', + 'acf_get_field', + 'get_field', + ]; + /** * Initialize feature setting it's config */ @@ -49,9 +62,12 @@ public function set_i18n_strings(): void { public function requirements_status() { $status = new FeatureRequirementsStatus( 0 ); - if ( ! function_exists( 'acf_get_field_groups' ) ) { - $status->code = 2; - $status->message = esc_html__( 'ACF Pro not installed.', 'elasticpress' ); + foreach ( $this->acf_functions as $function ) { + if ( ! function_exists( $function ) ) { + $status->code = 2; + $status->message = esc_html__( 'ACF Pro not installed.', 'elasticpress' ); + break; + } } return $status; @@ -62,7 +78,8 @@ public function requirements_status() { */ public function setup() { add_action( 'acf/render_field_settings', [ $this, 'render_field_settings' ] ); - add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'add_meta_keys' ], 10, 2 ); + add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'allow_meta_keys' ], 10, 2 ); + add_filter( 'ep_prepare_meta_data', [ $this, 'add_meta_keys' ], 10, 2 ); } /** @@ -72,10 +89,6 @@ public function setup() { * @return void */ public function render_field_settings( $field ): void { - if ( ! function_exists( 'acf_render_field_setting' ) ) { - return; - } - // We only want repeaters and fields that are not children of repeaters. if ( 'repeater' !== $field['type'] || ! empty( $field['parent_repeater'] ) ) { return; @@ -106,7 +119,7 @@ public function render_field_settings( $field ): void { * @param \WP_Post $post The post object. * @return array */ - public function add_meta_keys( $meta, $post ) { + public function allow_meta_keys( $meta, $post ) { $field_groups = acf_get_field_groups( array( 'post_id' => $post->ID, @@ -135,4 +148,26 @@ public function add_meta_keys( $meta, $post ) { return $meta; } + + /** + * Add the ACF Repeater fields to the ES document meta data. + * + * @param array $meta All post meta data + * @param \WP_Post $post The post object + * @return array + */ + public function add_meta_keys( $meta, $post ) { + $meta_keys = array_keys( $meta ); + foreach ( $meta_keys as $key ) { + $field = acf_get_field( $key ); + + if ( ! $field || empty( $field['ep_index_repeater_field'] ) || 'repeater' !== $field['type'] ) { + continue; + } + + $meta[ $key ] = wp_json_encode( get_field( $key, $post->ID ) ); + } + + return $meta; + } } From fd88d02f8ee454001c3ccdd4f8646ebd03157d0c Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 6 Feb 2025 10:18:26 -0300 Subject: [PATCH 4/9] Updated copy and option name --- .../Feature/AcfRepeater/AcfRepeater.php | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php index 190eccc3d..b9364f60a 100644 --- a/includes/classes/Feature/AcfRepeater/AcfRepeater.php +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -10,6 +10,7 @@ use ElasticPress\Feature; use ElasticPress\FeatureRequirementsStatus; +use ElasticPress\Utils; if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly. @@ -47,9 +48,9 @@ public function __construct() { public function set_i18n_strings(): void { $this->title = esc_html__( 'ACF Repeater Field Compatibility', 'elasticpress' ); - $this->short_title = esc_html__( 'ACF Repeater', 'elasticpress' ); + $this->short_title = esc_html__( 'ACF Repeater Field', 'elasticpress' ); - $this->summary = '

' . __( 'Index your ACF Repeater fields as a single text field.', 'elasticpress' ) . '

'; + $this->summary = '

' . __( 'Index your ACF Repeater fields as a JSON object and, optionally, make it searchable in the Search Fields & Weighting dashboard.', 'elasticpress' ) . '

'; $this->docs_url = __( 'https://www.elasticpress.io/documentation/article/configuring-elasticpress-via-the-plugin-dashboard/#autosuggest', 'elasticpress' ); } @@ -100,12 +101,21 @@ public function render_field_settings( $field ): void { return; } + $instructions = wp_kses_post( + sprintf( + /* translators: %s: post type name */ + __( 'Index this field as a JSON object. If you want to make it searchable, do not forget to enable it under the related post types in the Search Fields & Weighting dashboard. To index existent content you can either manually save posts with this field or run a sync.', 'elasticpress' ), + esc_url( admin_url( 'admin.php?page=elasticpress-weighting' ) ), + Utils\get_sync_url() + ) + ); + \acf_render_field_setting( $field, [ 'label' => esc_html__( 'Index in ElasticPress', 'elasticpress' ), - 'instructions' => esc_html__( 'Index this field as a single text field', 'elasticpress' ), - 'name' => 'ep_index_repeater_field', + 'instructions' => $instructions, + 'name' => 'ep_acf_repeater_index_field', 'type' => 'true_false', 'ui' => 1, ] @@ -136,7 +146,7 @@ public function allow_meta_keys( $meta, $post ) { foreach ( $field_groups as $field_group ) { $fields = acf_get_fields( $field_group ); foreach ( $fields as $field ) { - if ( empty( $field['ep_index_repeater_field'] ) ) { + if ( empty( $field['ep_acf_repeater_index_field'] ) ) { continue; } @@ -161,7 +171,7 @@ public function add_meta_keys( $meta, $post ) { foreach ( $meta_keys as $key ) { $field = acf_get_field( $key ); - if ( ! $field || empty( $field['ep_index_repeater_field'] ) || 'repeater' !== $field['type'] ) { + if ( ! $field || empty( $field['ep_acf_repeater_index_field'] ) || 'repeater' !== $field['type'] ) { continue; } From e10ff925b6ab525ce13d58be619882bca76f15c0 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Mon, 10 Feb 2025 13:56:23 -0300 Subject: [PATCH 5/9] Unit Tests --- .../Feature/AcfRepeater/AcfRepeater.php | 29 ++- tests/php/bootstrap.php | 1 + tests/php/features/TestAcfRepeater.php | 182 ++++++++++++++++++ tests/php/includes/acf-pro-functions.php | 84 ++++++++ .../includes/classes/FunctionsCallCounter.php | 80 ++++++++ 5 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 tests/php/features/TestAcfRepeater.php create mode 100644 tests/php/includes/acf-pro-functions.php create mode 100644 tests/php/includes/classes/FunctionsCallCounter.php diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php index b9364f60a..38f8dee3a 100644 --- a/includes/classes/Feature/AcfRepeater/AcfRepeater.php +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -101,6 +101,19 @@ public function render_field_settings( $field ): void { return; } + /** + * Filter whether EP should or not display the field setting in ACF + * + * @hook ep_acf_repeater_should_display_field_setting + * @since 5.3.0 + * @param {bool} $should_display Whether should or not display the field setting in ACF + * @param {array} $field The ACF Field array + * @return {bool} New value of $should_display + */ + if ( ! apply_filters( 'ep_acf_repeater_should_display_field_setting', true, $field ) ) { + return; + } + $instructions = wp_kses_post( sprintf( /* translators: %s: post type name */ @@ -175,7 +188,21 @@ public function add_meta_keys( $meta, $post ) { continue; } - $meta[ $key ] = wp_json_encode( get_field( $key, $post->ID ) ); + $value_field = get_field( $key, $post->ID ); + $value_encoded = wp_json_encode( $value_field ); + + /** + * Filter the ACF Repeater field value before it is indexed + * + * @hook ep_acf_repeater_meta_value + * @since 5.3.0 + * @param {string} $value_encoded Repeater field value encoded + * @param {array} $value_field Original field value + * @param {string} $key The meta field key + * @param {WP_Post} $post The Post object + * @return {mixed} New value of $value_encoded + */ + $meta[ $key ] = apply_filters( 'ep_acf_repeater_meta_value', $value_encoded, $value_field, $key, $post ); } return $meta; diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php index 597b48f8c..0bf6c79d6 100644 --- a/tests/php/bootstrap.php +++ b/tests/php/bootstrap.php @@ -181,6 +181,7 @@ function drop_wc_order_table( $query ) { require_once __DIR__ . '/includes/classes/factory/ProductFactory.php'; require_once __DIR__ . '/includes/classes/BaseTestCase.php'; require_once __DIR__ . '/includes/classes/FeatureTest.php'; +require_once __DIR__ . '/includes/classes/FunctionsCallCounter.php'; require_once __DIR__ . '/includes/classes/mock/Global/Feature.php'; require_once __DIR__ . '/includes/classes/mock/class-wp-cli-command.php'; require_once __DIR__ . '/includes/classes/mock/class-wp-cli.php'; diff --git a/tests/php/features/TestAcfRepeater.php b/tests/php/features/TestAcfRepeater.php new file mode 100644 index 000000000..7c2e3e6c8 --- /dev/null +++ b/tests/php/features/TestAcfRepeater.php @@ -0,0 +1,182 @@ +feature = Features::factory()->get_registered_feature( 'acf_repeater' ); + \ElasticPressTest\FunctionsCallCounter::get_instance()->reset_all_counters(); + + parent::set_up(); + } + + /** + * Setup mock functions + */ + protected function setup_mock_functions() { + require_once __DIR__ . '/../includes/acf-pro-functions.php'; + } + + /** + * Test the `requirements_status` method + * + * @group acf-repeater + */ + public function test_requirements_status() { + $requirement_status = $this->feature->requirements_status(); + + $this->assertSame( 2, $requirement_status->code ); + $this->assertSame( 'ACF Pro not installed.', $requirement_status->message ); + + $this->setup_mock_functions(); + + $requirement_status = $this->feature->requirements_status(); + + $this->assertSame( 0, $requirement_status->code ); + $this->assertNull( $requirement_status->message ); + } + + /** + * Test the `render_field_settings` method + * + * @group acf-repeater + */ + public function test_render_field_settings() { + $field = [ + 'name' => 'test_field', + 'type' => 'not-repeater', + ]; + + $this->feature->render_field_settings( $field ); + $this->assertEquals( 0, \ElasticPressTest\FunctionsCallCounter::get_instance()->get_counter( 'acf_render_field_setting' ) ); + + $field['type'] = 'repeater'; + + $this->feature->render_field_settings( $field ); + $this->assertEquals( 0, \ElasticPressTest\FunctionsCallCounter::get_instance()->get_counter( 'acf_render_field_setting' ) ); + + $field['parent'] = $this->factory->post->create(); + + $this->feature->render_field_settings( $field ); + $this->assertEquals( 1, \ElasticPressTest\FunctionsCallCounter::get_instance()->get_counter( 'acf_render_field_setting' ) ); + } + + /** + * Test the `ep_acf_repeater_should_display_field_setting` filter method + * + * @group acf-repeater + */ + public function test_ep_acf_repeater_should_display_field_setting_filter() { + $field = [ + 'name' => 'test_field', + 'type' => 'repeater', + 'parent' => $this->factory->post->create(), + ]; + + add_filter( 'ep_acf_repeater_should_display_field_setting', '__return_false' ); + + $this->feature->render_field_settings( $field ); + $this->assertEquals( 0, \ElasticPressTest\FunctionsCallCounter::get_instance()->get_counter( 'acf_render_field_setting' ) ); + } + + /** + * Test the `allow_meta_keys` method + * + * @group acf-repeater + */ + public function test_allow_meta_keys() { + $initial_meta = [ 'meta_1', 'meta_2' ]; + + $this->assertSame( + $initial_meta, + $this->feature->allow_meta_keys( $initial_meta, new \WP_Post( (object) [ 'ID' => 0 ] ) ) + ); + + $this->assertSame( + [ 'meta_1', 'meta_2', 'should_be_added' ], + $this->feature->allow_meta_keys( $initial_meta, new \WP_Post( (object) [ 'ID' => 1 ] ) ) + ); + } + + /** + * Test the `add_meta_keys` method + * + * @group acf-repeater + */ + public function test_add_meta_keys() { + $initial_meta = [ + 'meta_1' => [ 1 ], + 'meta_2' => [ 2 ], + 'repeater_test' => [ '' ], + ]; + + $this->assertSame( + array_merge( + $initial_meta, + [ + 'repeater_test' => '{"child_1":"value_1","child_2":"value_2"}', + ] + ), + $this->feature->add_meta_keys( $initial_meta, new \WP_Post( (object) [] ) ) + ); + } + + /** + * Test the `ep_acf_repeater_meta_value` filter + * + * @group acf-repeater + */ + public function test_ep_acf_repeater_meta_value() { + $initial_meta = [ + 'meta_1' => [ 1 ], + 'meta_2' => [ 2 ], + 'repeater_test' => [ '' ], + ]; + + $callback = function ( $value_encoded, $value_field, $key, $post ) { + $this->assertSame( + [ + 'child_1' => 'value_1', + 'child_2' => 'value_2', + ], + $value_field + ); + $this->assertSame( $key, 'repeater_test' ); + $this->assertInstanceOf( '\WP_Post', $post ); + return $value_encoded . '_filtered'; + }; + add_filter( 'ep_acf_repeater_meta_value', $callback, 10, 4 ); + + $this->assertSame( + array_merge( + $initial_meta, + [ + 'repeater_test' => '{"child_1":"value_1","child_2":"value_2"}_filtered', + ] + ), + $this->feature->add_meta_keys( $initial_meta, new \WP_Post( (object) [] ) ) + ); + } +} diff --git a/tests/php/includes/acf-pro-functions.php b/tests/php/includes/acf-pro-functions.php new file mode 100644 index 000000000..12a2ab904 --- /dev/null +++ b/tests/php/includes/acf-pro-functions.php @@ -0,0 +1,84 @@ + 'test_group', // Not really used. See acf_get_fields. + ], + ]; +} + +/** + * Mock versino of acf_render_field_setting + * + * @return void + */ +function acf_render_field_setting() { + \ElasticPressTest\FunctionsCallCounter::get_instance()->update_counter( 'acf_render_field_setting' ); +} + +/** + * Mock version of acf_get_fields + * + * @return array + */ +function acf_get_fields() { + return [ + [ + 'ep_acf_repeater_index_field' => 1, + 'name' => 'should_be_added', + ], + [ + 'ep_acf_repeater_index_field' => 0, + 'name' => 'should_not_be_added', + ], + [ + 'name' => 'should_not_be_added', + ], + ]; +} + +/** + * Mock version of acf_get_field + * + * @param string $key Field key + * @return bool + */ +function acf_get_field( $key ) { + $fields = [ + 'repeater_test' => [ + 'ep_acf_repeater_index_field' => 1, + 'type' => 'repeater', + ], + ]; + + return $fields[ $key ] ?? false; +} + +/** + * Mock version of get_field + * + * @return array + */ +function get_field() { + return [ + 'child_1' => 'value_1', + 'child_2' => 'value_2', + ]; +} diff --git a/tests/php/includes/classes/FunctionsCallCounter.php b/tests/php/includes/classes/FunctionsCallCounter.php new file mode 100644 index 000000000..b9383f181 --- /dev/null +++ b/tests/php/includes/classes/FunctionsCallCounter.php @@ -0,0 +1,80 @@ +counters[ $key ] ?? 0; + } + + /** + * Update the counter + * + * @param string $key The key to get the counter + * @return void + */ + public function update_counter( $key ) { + if ( ! isset( $this->counters[ $key ] ) ) { + $this->counters[ $key ] = 0; + } + ++$this->counters[ $key ]; + } + + /** + * Reset the counter + * + * @param string $key The key to get the counter + * @return void + */ + public function reset_counter( $key ) { + $this->counters[ $key ] = 0; + } + + /** + * Reset all counters + */ + public function reset_all_counters() { + $this->counters = []; + } + + /** + * Singleton. Get the class instance + * + * @return self + */ + public static function get_instance() { + if ( ! self::$instance ) { + self::$instance = new self(); + } + return self::$instance; + } +} From b3f96cfe7b62ad81a94567756bd22d3c39793210 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 11 Feb 2025 09:47:38 -0300 Subject: [PATCH 6/9] e2e tests --- .github/workflows/cypress-tests.yml | 4 +- .wp-env.json | 1 + bin/setup-cypress-env.sh | 33 +++- .../fixtures/acf-repeater-field-test.json | 186 ++++++++++++++++++ .../integration/features/acf-repeater.cy.js | 72 +++++++ tests/cypress/support/index.js | 3 + tests/cypress/wordpress-files/composer.json | 33 ++++ .../test-docs/content-example.xml | 133 +++++++++++++ 8 files changed, 453 insertions(+), 12 deletions(-) create mode 100644 tests/cypress/fixtures/acf-repeater-field-test.json create mode 100644 tests/cypress/integration/features/acf-repeater.cy.js create mode 100644 tests/cypress/wordpress-files/composer.json diff --git a/.github/workflows/cypress-tests.yml b/.github/workflows/cypress-tests.yml index 9ec2943e7..328783f40 100644 --- a/.github/workflows/cypress-tests.yml +++ b/.github/workflows/cypress-tests.yml @@ -79,7 +79,7 @@ jobs: run: npm run build - name: Set up database - run: npm run cypress:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} + run: npm run cypress:setup -- --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}' - name: Test run: npm run cypress:run -- --env grepTags=${{ matrix.testGroup }} @@ -166,7 +166,7 @@ jobs: run: npm run build - name: Set up database - run: npm run cypress:setup -- --ep-host=${{ secrets.EPIO_HOST }} --es-shield='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} + run: npm run cypress:setup -- --ep-host=${{ secrets.EPIO_HOST }} --es-shield='${{ secrets.EPIO_SHIELD }}' --ep-index-prefix=${{ secrets.EPIO_INDEX_PREFIX }} --wp-version=${{ matrix.core.version }} --wc-version=${{ matrix.core.wcVersion }} --acf-pro-license='${{ secrets.ACF_PRO_LICENSE_KEY }}' - name: Test run: npm run cypress:run -- --env grepTags=${{ matrix.testGroup }} diff --git a/.wp-env.json b/.wp-env.json index d56970e7d..65868db28 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -18,6 +18,7 @@ ], "mappings": { ".htaccess": "./tests/cypress/wordpress-files/.htaccess", + "wp-content/composer.json": "./tests/cypress/wordpress-files/composer.json", "wp-content/mu-plugins/disable-welcome-guide.php": "./tests/cypress/wordpress-files/test-mu-plugins/disable-welcome-guide.php", "wp-content/mu-plugins/skip-wp-lookup.php": "./tests/cypress/wordpress-files/test-mu-plugins/skip-wp-lookup.php", "wp-content/mu-plugins/unique-index-name.php": "./tests/cypress/wordpress-files/test-mu-plugins/unique-index-name.php", diff --git a/bin/setup-cypress-env.sh b/bin/setup-cypress-env.sh index 8332cb9b7..1fcb2f955 100755 --- a/bin/setup-cypress-env.sh +++ b/bin/setup-cypress-env.sh @@ -2,22 +2,26 @@ # cat ./bin/2022-02-15-12-49.sql | ./bin/wp-env-cli tests-wordpress "wp --allow-root db import -" +ACF_PRO_LICENSE_KEY="" +DISPLAY_HELP=0 EP_HOST="" ES_SHIELD="" EP_INDEX_PREFIX="" WP_VERSION="" WC_VERSION="" -DISPLAY_HELP=0 for opt in "$@"; do case $opt in - -h=*|--ep-host=*) + --acf-pro-license=*) + ACF_PRO_LICENSE_KEY="${opt#*=}" + ;; + -H=*|--ep-host=*) EP_HOST="${opt#*=}" ;; - -s=*|--es-shield=*) + -S=*|--es-shield=*) ES_SHIELD="${opt#*=}" ;; - -u=*|--ep-index-prefix=*) + -p=*|--ep-index-prefix=*) EP_INDEX_PREFIX="${opt#*=}" ;; -wp=*|--wp-version=*) @@ -39,12 +43,13 @@ if [ $DISPLAY_HELP -eq 1 ]; then echo "Usage: ${0##*/} [OPTIONS...]" echo echo "Optional parameters:" - echo "-h=*, --ep-host=* The remote Elasticsearch Host URL." - echo "-s=*, --es-shield=* The Elasticsearch credentials, used in the ES_SHIELD constant." - echo "-u=*, --ep-index-prefix=* The Elasticsearch credentials, used in the EP_INDEX_PREFIX constant." - echo "-W=*, --wp-version=* WordPress Core version." - echo "-w=*, --wc-version=* WooCommerce version." - echo "-h|--help Display this help screen" + echo "--acf-pro-license=* ACF Pro License Key." + echo "-H=*, --ep-host=* The remote Elasticsearch Host URL." + echo "-S=*, --es-shield=* The Elasticsearch credentials, used in the ES_SHIELD constant." + echo "-p=*, --ep-index-prefix=* The Elasticsearch credentials, used in the EP_INDEX_PREFIX constant." + echo "-W=*, --wp-version=* WordPress Core version." + echo "-w=*, --wc-version=* WooCommerce version." + echo "-h|--help Display this help screen" exit fi @@ -90,6 +95,14 @@ if [ ! -z $EP_INDEX_PREFIX ]; then ./bin/wp-env-cli tests-wordpress "wp --allow-root config set EP_INDEX_PREFIX ${EP_INDEX_PREFIX}" fi +if [ ! -z $ACF_PRO_LICENSE_KEY ]; then + ./bin/wp-env-cli tests-wordpress "composer --working-dir=./wp-content config http-basic.connect.advancedcustomfields.com ${ACF_PRO_LICENSE_KEY} https://elasticpress.test" + ./bin/wp-env-cli tests-wordpress "composer --working-dir=./wp-content install" + ./bin/wp-env-cli tests-wordpress "rm wp-content/auth.json" + ./bin/wp-env-cli tests-wordpress "wp --allow-root plugin activate advanced-custom-fields-pro" + ./bin/wp-env-cli tests-wordpress "wp --allow-root config set ACF_PRO_LICENSE ${ACF_PRO_LICENSE_KEY}" +fi + ./bin/wp-env-cli tests-wordpress "wp --allow-root core multisite-convert" SITES_COUNT=$(./bin/wp-env-cli tests-wordpress "wp --allow-root site list --format=count") diff --git a/tests/cypress/fixtures/acf-repeater-field-test.json b/tests/cypress/fixtures/acf-repeater-field-test.json new file mode 100644 index 000000000..5455ffada --- /dev/null +++ b/tests/cypress/fixtures/acf-repeater-field-test.json @@ -0,0 +1,186 @@ +[ + { + "key": "group_67ab2a1de23c3", + "title": "Repeater Test", + "fields": [ + { + "key": "field_67ab2a1e4d2c2", + "label": "Repeater Test 1", + "name": "repeater_test_1", + "aria-label": "", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "layout": "table", + "pagination": 0, + "min": 0, + "max": 0, + "collapsed": "", + "button_label": "Add Row", + "rows_per_page": 20, + "sub_fields": [ + { + "key": "field_67ab2a314d2c3", + "label": "Child 1", + "name": "child_1", + "aria-label": "", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "placeholder": "", + "prepend": "", + "append": "", + "parent_repeater": "field_67ab2a1e4d2c2" + }, + { + "key": "field_67ab2a374d2c4", + "label": "Child Repeater", + "name": "child_repeater", + "aria-label": "", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "layout": "table", + "pagination": 0, + "min": 0, + "max": 0, + "collapsed": "", + "button_label": "Add Row", + "rows_per_page": 20, + "sub_fields": [ + { + "key": "field_67ab2a444d2c5", + "label": "Grandchild 1", + "name": "grandchild_1", + "aria-label": "", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "placeholder": "", + "prepend": "", + "append": "", + "parent_repeater": "field_67ab2a374d2c4" + } + ], + "parent_repeater": "field_67ab2a1e4d2c2" + } + ] + }, + { + "key": "field_67ab2a514d2c7", + "label": "Root Level Text", + "name": "root_level_text", + "aria-label": "", + "type": "text", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "placeholder": "", + "prepend": "", + "append": "" + }, + { + "key": "field_67ab2a5b4d2c8", + "label": "Repeater Test 2", + "name": "repeater_test_2", + "aria-label": "", + "type": "repeater", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "layout": "table", + "pagination": 0, + "min": 0, + "max": 0, + "collapsed": "", + "button_label": "Add Row", + "rows_per_page": 20, + "sub_fields": [ + { + "key": "field_67ab2a6e4d2c9", + "label": "Textarea", + "name": "textarea", + "aria-label": "", + "type": "textarea", + "instructions": "", + "required": 0, + "conditional_logic": 0, + "wrapper": { + "width": "", + "class": "", + "id": "" + }, + "default_value": "", + "maxlength": "", + "allow_in_bindings": 0, + "rows": "", + "placeholder": "", + "new_lines": "", + "parent_repeater": "field_67ab2a5b4d2c8" + } + ] + } + ], + "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_rest": 0 + } +] diff --git a/tests/cypress/integration/features/acf-repeater.cy.js b/tests/cypress/integration/features/acf-repeater.cy.js new file mode 100644 index 000000000..437275b2b --- /dev/null +++ b/tests/cypress/integration/features/acf-repeater.cy.js @@ -0,0 +1,72 @@ +describe('ACF Repeater Field Compatibility Feature', () => { + before(() => { + cy.visitAdminPage('edit.php?post_type=acf-field-group&page=acf-tools'); + cy.fixture('acf-repeater-field-test.json', 'utf8').then((fileContent) => { + cy.log(fileContent); + cy.get('#acf_import_file').attachFile({ + fileContent, + fileName: 'acf-repeater-field-test.json', + mimeType: 'application/json', + encoding: 'utf8', + lastModified: new Date().getTime(), + }); + }); + cy.contains('button', 'Import JSON').click(); + }); + + it('Can index an ACF Repeater Field', () => { + // Check ElasticPress controls in the ACF group edit screen + cy.visitAdminPage('edit.php?post_type=acf-field-group'); + cy.get('a[aria-label="Edit “Repeater Test”"]').click({ force: true }); + + cy.get('.edit-field').click({ multiple: true, force: true }); + cy.get('.acf-field-object-repeater').should('have.length', 3); + cy.get('.acf-field-setting-ep_acf_repeater_index_field').should('have.length', 2); // We have 3 repeaters, but nested repeats do not get a toggle + + cy.get('.acf-field-setting-ep_acf_repeater_index_field') + .first() + .find('input[type="checkbox"]') + .then(($el) => { + if (!$el.is(':checked')) { + cy.wrap($el).check({ force: true }); + } + }); + cy.get('button.acf-publish').click(); + + // Make the field searchable + cy.visitAdminPage('admin.php?page=elasticpress-weighting'); + cy.contains('h2', 'Posts').closest('.ep-weighting-post-type').as('postBox'); + cy.get('@postBox') + .contains('button.components-panel__body-toggle', 'Metadata') + .then(($el) => { + if (!$el.prop('aria-expanded')) { + cy.wrap($el).click(); + } + }); + cy.contains('.ep-weighting-field__name', 'repeater_test_1').should('exist'); + cy.contains('.ep-weighting-field__name', 'repeater_test_1') + .closest('fieldset') + .find('input[type="checkbox"]') + .check(); + cy.get('button[type="submit"]').click(); + + // Search using the field + cy.visit('/?s=Grandchild%201.1'); + cy.contains('.site-content article h2', 'Post with ACF Repeater Field').should('exist'); + cy.get('.site-content article a').first().click(); + + cy.get('#wpadminbar li#wp-admin-bar-debug-bar').click(); + cy.get('#debug-menu-link-EP_Debug_Bar_ElasticPress').click(); + cy.contains('a', 'Reload and retrieve raw ES document').click(); + + cy.get('#wpadminbar li#wp-admin-bar-debug-bar').click(); + cy.get('#debug-menu-link-EP_Debug_Bar_ElasticPress').click(); + cy.get('.query-results') + .first() + .should( + 'contain.text', + '[{\\"child_1\\":\\"Repeater Child 1\\",\\"child_repeater\\":[{\\"grandchild_1\\":\\"Grandchild 1\\"},{\\"grandchild_1\\":\\"Grandchild 2\\"}]},{\\"child_1\\":\\"Repeater Child 2\\",\\"child_repeater\\":[{\\"grandchild_1\\":\\"Grandchild 1.1\\"}]}]', + ) + .should('not.contain.text', 'Repeater Test 2 Textarea'); + }); +}); diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js index c451af6a5..d5792ef88 100644 --- a/tests/cypress/support/index.js +++ b/tests/cypress/support/index.js @@ -52,6 +52,9 @@ cy.elasticPress = { protected_content: { active: false, }, + acf_repeater: { + active: true, + }, }, }; diff --git a/tests/cypress/wordpress-files/composer.json b/tests/cypress/wordpress-files/composer.json new file mode 100644 index 000000000..58991b896 --- /dev/null +++ b/tests/cypress/wordpress-files/composer.json @@ -0,0 +1,33 @@ +{ + "name": "10up/elasticpress-test-env", + "authors": [ + { + "name": "10up" + } + ], + "repositories": [ + { + "type": "composer", + "url": "https://connect.advancedcustomfields.com" + } + ], + "extra": { + "installer-paths": { + "mu-plugins/{$name}": [ + "type:wordpress-muplugin" + ], + "plugins/{$name}": [ + "type:wordpress-plugin" + ] + } + }, + "config": { + "allow-plugins": { + "composer/installers": true, + "dealerdirect/phpcodesniffer-composer-installer": true + } + }, + "require": { + "wpengine/advanced-custom-fields-pro": "*" + } +} diff --git a/tests/cypress/wordpress-files/test-docs/content-example.xml b/tests/cypress/wordpress-files/test-docs/content-example.xml index 5a377bafb..15bb334b2 100644 --- a/tests/cypress/wordpress-files/test-docs/content-example.xml +++ b/tests/cypress/wordpress-files/test-docs/content-example.xml @@ -35708,5 +35708,138 @@ Incidunt repellendus voluptatem laudantium et aut delectus rem. Et velit ullam o 0 + + <![CDATA[Post with ACF Repeater Field]]> + http://localhost:8889/blog/2025/02/11/post-with-acf-repeater-field/ + Tue, 11 Feb 2025 11:47:24 +0000 + + http://localhost:8889/?p=2310 + + + + 2310 + + + + + + + + + 0 + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From bd007942ec5a1373dbd92bdfd8a3cbdc40e332c0 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 11 Feb 2025 10:10:35 -0300 Subject: [PATCH 7/9] Save the post, so the repeated field is indexed --- tests/cypress/integration/features/acf-repeater.cy.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/cypress/integration/features/acf-repeater.cy.js b/tests/cypress/integration/features/acf-repeater.cy.js index 437275b2b..c4d95e4e5 100644 --- a/tests/cypress/integration/features/acf-repeater.cy.js +++ b/tests/cypress/integration/features/acf-repeater.cy.js @@ -33,6 +33,12 @@ describe('ACF Repeater Field Compatibility Feature', () => { }); cy.get('button.acf-publish').click(); + // Save the example post, so the repeater field is indexed + cy.visitAdminPage('edit.php?s=Post+with+ACF+Repeater+Field'); + cy.get('span.edit a').click({ force: true }); + cy.get('.editor-post-publish-button__button').click(); + cy.wait(2000); // eslint-disable-line + // Make the field searchable cy.visitAdminPage('admin.php?page=elasticpress-weighting'); cy.contains('h2', 'Posts').closest('.ep-weighting-post-type').as('postBox'); From 401bec08a5a4814027cd77a1e39e050114350900 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 11 Feb 2025 10:30:26 -0300 Subject: [PATCH 8/9] Close the welcome modal --- tests/cypress/integration/features/acf-repeater.cy.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/cypress/integration/features/acf-repeater.cy.js b/tests/cypress/integration/features/acf-repeater.cy.js index c4d95e4e5..11deaa400 100644 --- a/tests/cypress/integration/features/acf-repeater.cy.js +++ b/tests/cypress/integration/features/acf-repeater.cy.js @@ -36,6 +36,14 @@ describe('ACF Repeater Field Compatibility Feature', () => { // Save the example post, so the repeater field is indexed cy.visitAdminPage('edit.php?s=Post+with+ACF+Repeater+Field'); cy.get('span.edit a').click({ force: true }); + cy.get('body').then(($body) => { + const welcomeGuide = $body.find( + '.edit-post-welcome-guide .components-modal__header button', + ); + if (welcomeGuide.length) { + welcomeGuide.click(); + } + }); cy.get('.editor-post-publish-button__button').click(); cy.wait(2000); // eslint-disable-line From b5f9e0adf2f76c8501c1d5d4d30906a64c0790bb Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 11 Feb 2025 10:54:19 -0300 Subject: [PATCH 9/9] Update the doc URL --- includes/classes/Feature/AcfRepeater/AcfRepeater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/classes/Feature/AcfRepeater/AcfRepeater.php b/includes/classes/Feature/AcfRepeater/AcfRepeater.php index 38f8dee3a..e3bf78575 100644 --- a/includes/classes/Feature/AcfRepeater/AcfRepeater.php +++ b/includes/classes/Feature/AcfRepeater/AcfRepeater.php @@ -52,7 +52,7 @@ public function set_i18n_strings(): void { $this->summary = '

' . __( 'Index your ACF Repeater fields as a JSON object and, optionally, make it searchable in the Search Fields & Weighting dashboard.', 'elasticpress' ) . '

'; - $this->docs_url = __( 'https://www.elasticpress.io/documentation/article/configuring-elasticpress-via-the-plugin-dashboard/#autosuggest', 'elasticpress' ); + $this->docs_url = __( 'https://www.elasticpress.io/documentation/article/acf-repeater-field-compatibility-feature/', 'elasticpress' ); } /**