From a0250c7b7e19900fd32a777c47104b3c2e70de22 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Thu, 29 Feb 2024 11:08:02 -0300 Subject: [PATCH] Fixes, better docs, more filters, and unit tests Co-authored-by: Burhan Nasir --- includes/classes/Feature/ExternalContent.php | 85 +++++++++++++++---- tests/phpunit/feature/TestExternalContent.php | 80 ++++++++++++----- 2 files changed, 127 insertions(+), 38 deletions(-) diff --git a/includes/classes/Feature/ExternalContent.php b/includes/classes/Feature/ExternalContent.php index 8f6aa5a..6f59f9c 100644 --- a/includes/classes/Feature/ExternalContent.php +++ b/includes/classes/Feature/ExternalContent.php @@ -35,6 +35,11 @@ public function __construct() { $this->title = esc_html__( 'External Content', 'elasticpress-labs' ); + $this->summary = __( + 'List meta keys containing a path or a URL, and ElasticPress will index the content of those path or URL. For example, for a meta key called meta_key with https://wordpress.org/news/wp-json/wp/v2/posts/16837 as its value, the JSON returned by that REST API endpoint will be indexed in a meta key called ep_external_content_meta_key.', + 'elasticpress-labs' + ); + parent::__construct(); } @@ -44,7 +49,7 @@ public function __construct() { * @return void */ public function setup() { - add_filter( 'ep_prepare_meta_data', [ $this, 'append_external_content' ] ); + add_filter( 'ep_prepare_meta_data', [ $this, 'append_external_content' ], 10, 2 ); add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'allow_meta_keys' ], 10, 2 ); /** @@ -71,10 +76,23 @@ public function requirements_status() { * Set the `settings_schema` attribute */ public function set_settings_schema() { + $weighting_dashboard_url = ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) ? + admin_url( 'admin.php?page=elasticpress-weighting' ) : + admin_url( 'admin.php?page=elasticpress' ); + + $help_text = sprintf( + /* translators: Search Fields & Weighting Dashboard URL */ + __( + 'Add one field per line. Visit the Search Fields & Weighting Dashboard if you want to make their ep_external_content_* version searchable.', + 'elasticpress-labs' + ), + $weighting_dashboard_url + ); + $this->settings_schema = [ [ 'default' => '', - 'help' => '

' . __( 'Add one field per line', 'elasticpress-labs' ) . '

', + 'help' => '

' . $help_text . '

', 'key' => 'meta_fields', 'label' => __( 'Meta fields with external URLs', 'elasticpress-labs' ), 'type' => 'textarea', @@ -85,15 +103,21 @@ public function set_settings_schema() { /** * Append external content to the document meta data * - * @param array $post_meta Document's meta data + * @param array $post_meta Document's meta data + * @param \WP_Post|null $post Post object * @return array */ - public function append_external_content( $post_meta ) { + public function append_external_content( $post_meta, $post = null ) { global $wp_filesystem; require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); + $post_indexable = \ElasticPress\Indexables::factory()->get( 'post' ); + $test_meta_value = method_exists( $post_indexable, 'get_test_meta_value' ) ? + $post_indexable->get_test_meta_value() : + 'test-value'; + $meta_keys = $this->get_meta_keys(); foreach ( $meta_keys as $meta_key ) { if ( ! isset( $post_meta[ $meta_key ] ) ) { @@ -103,6 +127,23 @@ public function append_external_content( $post_meta ) { $meta_value = (array) $post_meta[ $meta_key ]; $meta_value = reset( $meta_value ); + $should_skip = empty( $meta_value ) || $test_meta_value === $meta_value; + + /** + * Filter if the meta value should be skipped + * + * @since 2.3.0 + * @hook ep_external_content_should_skip + * @param {bool} $should_skip Whether the meta value should be skipped + * @param {mixed} $meta_value Meta value being analyzed + * @param {string} $meta_key Meta key being analyzed + * @param {WP_Post|null} $post Current post object + * @return {bool} Whether the meta value should be skipped + */ + if ( apply_filters( 'ep_external_content_should_skip', $should_skip, $meta_value, $meta_key, $post ) ) { + continue; + } + /** * The field value can either be a simple string or a JSON array with a list of URLs. */ @@ -134,6 +175,10 @@ public function append_external_content( $post_meta ) { */ $request_url = apply_filters( 'ep_external_content_remote_request_url', $external_path_and_url ); + if ( ! filter_var( $request_url, FILTER_VALIDATE_URL ) ) { + continue; + } + /** * Filter the arguments of the remote request * @@ -149,7 +194,8 @@ public function append_external_content( $post_meta ) { $post_meta, $meta_key, wp_remote_retrieve_body( $remote_get ), - $external_path_and_url + $external_path_and_url, + $remote_get ); } @@ -218,8 +264,13 @@ public function get_stored_meta_key( $meta_key ) { * @return array */ public function allow_meta_keys( $meta_keys ) { + $external_meta_keys = $this->get_meta_keys(); + if ( empty( $external_meta_keys ) ) { + return $meta_keys; + } + $stored_meta_keys = array_reduce( - $this->get_meta_keys(), + $external_meta_keys, function ( $acc, $meta_key ) { $acc[] = $this->get_stored_meta_key( $meta_key ); return $acc; @@ -304,25 +355,27 @@ public function maybe_parse_js( $content, $path_or_url ) { /** * Add the content of external sources to the post meta array * - * @param array $post_meta Array of all post meta - * @param string $meta_key Meta key - * @param string $content Contents of the external source - * @param string $path_or_url Path or URL of the external source + * @param array $post_meta Array of all post meta + * @param string $meta_key Meta key + * @param string $content Contents of the external source + * @param string $path_or_url Path or URL of the external source + * @param string $additional_data Additional data. Contains the HTTP response if the content was fetched remotely * @return array */ - protected function add_external_content_to_post_meta( $post_meta, $meta_key, $content, $path_or_url ) { + protected function add_external_content_to_post_meta( $post_meta, $meta_key, $content, $path_or_url, $additional_data = [] ) { /** * Filter the content. * * @since 2.3.0 * @hook ep_external_content_file_content - * @param {string} $content Content being processed - * @param {string} $path_or_url Path or URL - * @param {string} $meta_key The meta key that contains the path or URL - * @param {array} $post_meta Post meta + * @param {string} $content Content being processed + * @param {string} $path_or_url Path or URL + * @param {string} $meta_key The meta key that contains the path or URL + * @param {array} $post_meta Post meta + * @param {array} $additional_data Additional data. Contains the HTTP response if the content was fetched remotely * @return {string} New $content */ - $content = apply_filters( 'ep_external_content_file_content', $content, $path_or_url, $meta_key, $post_meta ); + $content = apply_filters( 'ep_external_content_file_content', $content, $path_or_url, $meta_key, $post_meta, $additional_data ); if ( empty( $content ) ) { return $post_meta; diff --git a/tests/phpunit/feature/TestExternalContent.php b/tests/phpunit/feature/TestExternalContent.php index bd2fe56..4a5bef2 100644 --- a/tests/phpunit/feature/TestExternalContent.php +++ b/tests/phpunit/feature/TestExternalContent.php @@ -27,6 +27,8 @@ class TestExternalContent extends \WP_UnitTestCase { * Setup each test */ public function set_up() { + parent::set_up(); + $instance = new ExternalContent(); Features::factory()->register_feature( $instance ); Features::factory()->activate_feature( 'external_content' ); @@ -36,14 +38,6 @@ public function set_up() { add_filter( 'pre_http_request', [ $this, 'force_http_response' ] ); } - /** - * Clean up after each test - */ - public function tear_down() { - remove_filter( 'pre_option_ep_feature_settings', [ $this, 'set_settings' ] ); - remove_filter( 'pre_http_request', [ $this, 'force_http_response' ] ); - } - /** * Get External Content feature * @@ -86,7 +80,7 @@ public function test_set_settings_schema() { $expected_schema = [ 'default' => '', - 'help' => '

Add one field per line

', + 'help' => '

Add one field per line. Visit the Search Fields & Weighting Dashboard if you want to make their ep_external_content_* version searchable.

', 'key' => 'meta_fields', 'label' => 'Meta fields with external URLs', 'type' => 'textarea', @@ -111,11 +105,12 @@ public function test_append_external_content() { $this->assertSame( $post_meta['ep_external_content_meta_key_1'], ' {"id":123,"content":"Lorem ipsum"}' ); - $change_via_filter = function ( $content ) { + $change_via_filter = function ( $content, $path_or_url, $meta_key, $post_meta, $additional_data ) { $this->assertSame( $content, '{"id":123,"content":"Lorem ipsum"}' ); + $this->assertSame( $additional_data['code'], 123 ); return 'Something different'; }; - add_filter( 'ep_external_content_file_content', $change_via_filter ); + add_filter( 'ep_external_content_file_content', $change_via_filter, 10, 5 ); $post_meta = $this->get_feature()->append_external_content( $original_post_meta ); $this->assertSame( $post_meta['ep_external_content_meta_key_1'], ' Something different' ); @@ -173,10 +168,49 @@ public function test_append_external_content_remote_request_filters() { ]; $this->get_feature()->append_external_content( $original_post_meta ); + } - remove_filter( 'ep_external_content_remote_request_url', $change_url ); - remove_filter( 'ep_external_content_remote_request_args', $change_args ); - remove_filter( 'pre_http_request', $check ); + /** + * Test the ep_external_content_should_skip filter + * + * @group external-content + */ + public function test_ep_external_content_should_skip() { + $original_post_meta = [ + 'meta_key_1' => 'https://example.org/news/wp-json/wp/v2/posts/1', + ]; + $this->get_feature()->append_external_content( $original_post_meta ); + + $this->assertSame( 1, did_action( 'ep_external_content_item_processed' ) ); + + $skip_meta_value = function ( $should_skip, $meta_value, $meta_key, $post ) { + $this->assertFalse( $should_skip ); + $this->assertSame( $meta_value, 'https://example.org/news/wp-json/wp/v2/posts/1' ); + $this->assertSame( $meta_key, 'meta_key_1' ); + $this->assertNull( $post ); + return true; + }; + add_filter( 'ep_external_content_should_skip', $skip_meta_value, 10, 4 ); + $this->get_feature()->append_external_content( $original_post_meta ); + + $this->assertSame( 1, did_action( 'ep_external_content_item_processed' ) ); + } + + /** + * Test the append_external_content method with an invalid URL + * + * @group external-content + */ + public function test_append_external_content_invalid_url() { + $original_post_meta = [ + 'meta_key_1' => '/news/wp-json/wp/v2/posts/1', + ]; + $this->get_feature()->append_external_content( $original_post_meta ); + + $this->assertSame( 1, did_filter( 'ep_external_content_remote_request_url' ) ); + + // It will not even try to filter the args, as a request will not be sent. + $this->assertSame( 0, did_filter( 'ep_external_content_remote_request_args' ) ); } /** @@ -196,8 +230,6 @@ public function test_get_meta_keys() { add_filter( 'ep_external_content_meta_keys', $change_via_filter ); $this->assertSame( $this->get_feature()->get_meta_keys(), array_merge( $expected, [ 'meta_key_3' ] ) ); - - remove_filter( 'ep_external_content_meta_keys', $change_via_filter ); } /** @@ -216,8 +248,6 @@ public function test_get_stored_meta_key() { add_filter( 'ep_external_content_stored_meta_key', $change_via_filter, 10, 2 ); $this->assertSame( $this->get_feature()->get_stored_meta_key( 'meta_key' ), 'ep_external_content_meta_keychanged' ); - - remove_filter( 'ep_external_content_stored_meta_key', $change_via_filter ); } /** @@ -231,8 +261,13 @@ public function test_allow_meta_keys() { 'ep_external_content_meta_key_1', 'ep_external_content_meta_key_2', ]; - $this->assertSame( $this->get_feature()->allow_meta_keys( [ 'some_other_key' ] ), $expected ); + + $expected = [ + 'ep_external_content_meta_key_1', + 'ep_external_content_meta_key_2', + ]; + $this->assertSame( $this->get_feature()->allow_meta_keys( [] ), $expected ); } /** @@ -288,8 +323,6 @@ public function test_maybe_limit_size_filter_max_size() { $post_meta = $this->get_feature()->append_external_content( $original_post_meta ); $this->assertStringNotContainsString( ' (trimmed)', $post_meta['ep_external_content_meta_key_1'] ); - - remove_filter( 'ep_external_content_max_size', $change_size ); } /** @@ -352,7 +385,10 @@ public function set_settings() { * @return array */ public function force_http_response() { - return [ 'body' => $this->html_return ]; + return [ + 'code' => 123, + 'body' => $this->html_return, + ]; } /**