diff --git a/.github/release-pull-request-template.md b/.github/release-pull-request-template.md index 84efa24f2..8dd1bdbec 100644 --- a/.github/release-pull-request-template.md +++ b/.github/release-pull-request-template.md @@ -1,5 +1,5 @@ - [x] Branch: Starting from `develop`, create a release branch named `release/X.Y.Z` for your changes. -- [ ] Version bump: Bump the version number in `distributor.php`, `package.json`, and `readme.txt` if it does not already reflect the version being released. In `distributor.php` update both the plugin "Version:" property and the plugin `DT_VERSION` constant. +- [ ] Version bump: Bump the version number in `distributor.php`, `package.json`, `readme.txt` and `tests/php/bootstrap.php` if it does not already reflect the version being released. In `distributor.php` update both the plugin "Version:" property and the plugin `DT_VERSION` constant. - [ ] New files: Ensure any new files, especially in the vendor folder, are correctly included in `webpack.config.release.js`. - [ ] Changelog: Add/update the changelog in `CHANGELOG.md`. - [ ] Props: Update `CREDITS.md` file with any new contributors, confirm maintainers are accurate. diff --git a/includes/bootstrap.php b/includes/bootstrap.php index 1accb41a2..43cab6c6f 100644 --- a/includes/bootstrap.php +++ b/includes/bootstrap.php @@ -54,6 +54,7 @@ function() { 'rest_post_dispatch', function( $response ) { $response->header( 'X-Distributor', 'yes' ); + $response->header( 'X-Distributor-Version', DT_VERSION ); return $response; } diff --git a/includes/classes/Authentication.php b/includes/classes/Authentication.php index ec7043455..315ea0302 100644 --- a/includes/classes/Authentication.php +++ b/includes/classes/Authentication.php @@ -47,6 +47,11 @@ public function __construct( $args ) { * @return array */ public function format_get_args( $args = array(), $context = array() ) { + if ( ! isset( $args['headers'] ) ) { + $args['headers'] = array(); + } + $args['headers']['X-Distributor-Version'] = DT_VERSION; + /** * Format request args for a GET request so auth occurs. * @@ -71,6 +76,11 @@ public function format_get_args( $args = array(), $context = array() ) { * @return array */ public function format_post_args( $args, $context = array() ) { + if ( ! isset( $args['headers'] ) ) { + $args['headers'] = array(); + } + $args['headers']['X-Distributor-Version'] = DT_VERSION; + /** * Format request args for a POST request so auth occurs * diff --git a/includes/classes/DistributorPost.php b/includes/classes/DistributorPost.php index e1e1400d3..73278d7a8 100644 --- a/includes/classes/DistributorPost.php +++ b/includes/classes/DistributorPost.php @@ -44,6 +44,7 @@ * @method array get_media() * @method array post_data() * @method array to_insert( array $args = [] ) + * @method array to_pull_list( array $args = [] ) * @method array to_rest( array $args = [] ) */ class DistributorPost { @@ -816,6 +817,40 @@ protected function to_insert( $args = array() ) { return $insert; } + /** + * Get the post data in a format suitable for the pull screen. + * + * This is a wrapper for the ::to_insert() method that includes extra + * data required for the pull screen. + * + * @since 2.0.0 + * + * @param mixed $args { + * Optional. Array of push arguments + * @see ::to_insert() for arguments. + * } + * @return array Post data formatted for the pull screen. + */ + protected function to_pull_list( $args = array() ) { + $display_data = $this->to_insert( $args ); + + // Additional information required for pull screen. + $display_data['ID'] = $this->post->ID; + $display_data['post_date'] = $this->post->post_date; + $display_data['post_date_gmt'] = $this->post->post_date_gmt; + $display_data['post_modified'] = $this->post->post_modified; + $display_data['post_modified_gmt'] = $this->post->post_modified_gmt; + $display_data['post_password'] = $this->post->post_password; + $display_data['guid'] = $this->post->guid; + $display_data['comment_status'] = $this->post->comment_status; + $display_data['ping_status'] = $this->post->ping_status; + $display_data['link'] = $this->get_permalink(); + $display_data['distributor_original_site_name'] = $this->source_site['name']; + $display_data['distributor_original_site_url'] = $this->source_site['home_url']; + + return $display_data; + } + /** * Get the post data in a format suitable for the distributor REST API endpoint. * diff --git a/includes/classes/ExternalConnections/WordPressExternalConnection.php b/includes/classes/ExternalConnections/WordPressExternalConnection.php index 9cecedf6c..01c5d9389 100644 --- a/includes/classes/ExternalConnections/WordPressExternalConnection.php +++ b/includes/classes/ExternalConnections/WordPressExternalConnection.php @@ -167,8 +167,8 @@ public function remote_get( $args = array() ) { } } - // When running a query for the Pull screen with excluded items, make a POST request instead - if ( empty( $id ) && isset( $args['post__not_in'] ) && isset( $args['dt_pull_list'] ) ) { + // When running a query for the Pull screen, make a POST request instead + if ( empty( $id ) ) { $query_args['post_type'] = isset( $post_type ) ? $post_type : 'post'; $query_args['posts_per_page'] = isset( $posts_per_page ) ? $posts_per_page : 20; @@ -180,193 +180,20 @@ public function remote_get( $args = array() ) { return $posts_response; } - static $types_urls; - $types_urls = array(); - - if ( empty( $types_urls[ $post_type ] ) ) { - /** - * First let's get the actual route if not cached. We don't know the "plural" of our post type - */ - - /** - * Todo: This should be cached in a transient - */ - - $path = self::$namespace; - - $types_path = untrailingslashit( $this->base_url ) . '/' . $path . '/types'; - - $types_response = Utils\remote_http_request( - $types_path, - $this->auth_handler->format_get_args( array( 'timeout' => self::$timeout ) ) - ); - - if ( is_wp_error( $types_response ) ) { - return $types_response; - } - - if ( 404 === wp_remote_retrieve_response_code( $types_response ) ) { - return new \WP_Error( 'bad-endpoint', esc_html__( 'Could not connect to API endpoint.', 'distributor' ) ); - } - - $types_body = wp_remote_retrieve_body( $types_response ); - - if ( empty( $types_body ) ) { - return new \WP_Error( 'no-response-body', esc_html__( 'Response body is empty.', 'distributor' ) ); - } - - $types_body_array = json_decode( $types_body, true ); - - if ( empty( $types_body_array ) || empty( $types_body_array[ $post_type ] ) ) { - return new \WP_Error( 'no-pull-post-type', esc_html__( 'Could not determine remote post type endpoint.', 'distributor' ) ); - } - - $types_urls[ $post_type ] = $this->parse_type_items_link( $types_body_array[ $post_type ] ); - - if ( empty( $types_urls[ $post_type ] ) ) { - return new \WP_Error( 'no-pull-post-type', esc_html__( 'Could not determine remote post type endpoint.', 'distributor' ) ); - } - } - - $args_str = ''; - - if ( ! empty( $posts_per_page ) ) { - $args_str .= 'per_page=' . (int) $posts_per_page; - } - - /** - * Filter the remote_get query arguments - * - * @since 1.0 - * @hook dt_remote_get_query_args - * - * @param {array} $query_args The existing query arguments. - * @param {array} $args The arguments originally passed to `remote_get`. - * @param {object} $this The authentication class. - * - * @return {array} The existing query arguments. - */ - $query_args = apply_filters( 'dt_remote_get_query_args', $query_args, $args, $this ); - - foreach ( $query_args as $arg_key => $arg_value ) { - if ( is_array( $arg_value ) ) { - foreach ( $arg_value as $arg_value_value ) { - if ( ! empty( $args_str ) ) { - $args_str .= '&'; - } - - $args_str .= $arg_key . '[]=' . $arg_value_value; - } - } else { - if ( ! empty( $args_str ) ) { - $args_str .= '&'; - } - - $args_str .= $arg_key . '=' . $arg_value; - } - } - - $context = 'view'; - - $prelim_get_args = $this->auth_handler->format_get_args(); - - /** - * See if we are trying to authenticate - */ - if ( ! empty( $prelim_get_args ) && ! empty( $prelim_get_args['headers'] ) && ! empty( $prelim_get_args['headers']['Authorization'] ) ) { - $context = 'edit'; - - if ( ! empty( $args_str ) ) { - $args_str .= '&'; - } - - $args_str .= 'context=edit'; - } - - if ( ! empty( $id ) ) { - $posts_url = untrailingslashit( $types_urls[ $post_type ] ) . '/' . $id . '/?context=' . $context; - } else { - $posts_url = untrailingslashit( $types_urls[ $post_type ] ) . '/?' . $args_str; - } - - // Add request parameter to specify Distributor request - $posts_url = add_query_arg( 'distributor_request', '1', $posts_url ); - - $posts_response = Utils\remote_http_request( - /** - * Filter the URL that remote_get will use - * - * @since 1.0 - * @hook dt_remote_get_url - * - * @param {string} $posts_url The posts URL - * @param {string} $args The arguments originally passed to `remote_get`. - * @param {object} $this The authentication class. - * - * @return {string} The posts URL. - */ - apply_filters( 'dt_remote_get_url', $posts_url, $args, $this ), - // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout -- false positive, shorter on VIP. - $this->auth_handler->format_get_args( array( 'timeout' => 45 ) ) + $query_args = array( + 'include' => absint( $id ), + 'post_type' => isset( $args['post_type'] ) ? $args['post_type'] : 'any', + ); + $posts_response = $this->remote_post( + untrailingslashit( $this->base_url ) . '/wp/v2/distributor/list-pull-content', + $query_args ); if ( is_wp_error( $posts_response ) ) { return $posts_response; } - $response_code = wp_remote_retrieve_response_code( $posts_response ); - - if ( 200 !== $response_code ) { - - if ( 404 === $response_code ) { - return new \WP_Error( 'bad-endpoint', esc_html__( 'Could not connect to API endpoint.', 'distributor' ) ); - } - - $posts_body = json_decode( wp_remote_retrieve_body( $posts_response ), true ); - - $code = empty( $posts_body['code'] ) ? 'endpoint-error' : esc_html( $posts_body['code'] ); - $message = empty( $posts_body['message'] ) ? esc_html__( 'API endpoint error.', 'distributor' ) : esc_html( $posts_body['message'] ); - - return new \WP_Error( $code, $message ); - } - - $posts_body = wp_remote_retrieve_body( $posts_response ); - - if ( empty( $posts_body ) ) { - return new \WP_Error( 'no-response-body', esc_html__( 'Response body is empty.', 'distributor' ) ); - } - - $posts = json_decode( $posts_body, true ); - $formatted_posts = array(); - - $response_headers = wp_remote_retrieve_headers( $posts_response ); - - if ( empty( $id ) ) { - foreach ( $posts as $post ) { - $post['full_connection'] = ( ! empty( $response_headers['X-Distributor'] ) ); - - $formatted_posts[] = $this->to_wp_post( $post ); - } - - $total_posts = wp_remote_retrieve_header( $posts_response, 'X-WP-Total' ); - if ( empty( $total_posts ) ) { - $total_posts = count( $formatted_posts ); - } - - // Filter documented above. - return apply_filters( - 'dt_remote_get', - [ - 'items' => $formatted_posts, - 'total_items' => $total_posts, - ], - $args, - $this - ); - } else { - // Filter documented above. - return apply_filters( 'dt_remote_get', $this->to_wp_post( $posts ), $args, $this ); - } + return $posts_response['items'][0]; } /** @@ -444,6 +271,26 @@ public function remote_post( $url = '', $args = array() ) { return new \WP_Error( 'no-response-body', esc_html__( 'Response body is empty.', 'distributor' ) ); } + if ( + false === Utils\is_development_version() + && isset( $response_headers['x-distributor'] ) + && ( + ! isset( $response_headers['x-distributor-version'] ) + || version_compare( $response_headers['x-distributor-version'], '2.0.0', '<' ) + ) + ) { + $version_error = new \WP_Error(); + $version_error->add( + 'old-distributor-version', + esc_html__( 'Pulling content from external connections requires Distributor version 2.0.0 or later.', 'distributor' ) + ); + $version_error->add( + 'old-distributor-version', + esc_html__( 'Please update Distributor on the site you are pulling content from.', 'distributor' ) + ); + return $version_error; + } + $posts = json_decode( $posts_body, true ); $formatted_posts = array(); @@ -489,18 +336,35 @@ public function remote_post( $url = '', $args = array() ) { public function pull( $items ) { $created_posts = array(); + $remote_post_args = array( + 'include' => array(), + 'post_type' => array(), + ); foreach ( $items as $item_array ) { - $post = $this->remote_get( - [ - 'id' => $item_array['remote_post_id'], - 'post_type' => $item_array['post_type'], - ] - ); + $remote_post_args['include'][] = $item_array['remote_post_id']; + $remote_post_args['post_type'][] = $item_array['post_type']; + } + $remote_post_args['include'] = array_unique( $remote_post_args['include'] ); + $remote_post_args['post_type'] = array_unique( $remote_post_args['post_type'] ); + $remote_post_args['posts_per_page'] = count( $remote_post_args['include'] ); - if ( is_wp_error( $post ) ) { - $created_posts[] = $post; + // Get all remote posts in a single request. + $remote_posts = $this->remote_post( + untrailingslashit( $this->base_url ) . '/' . self::$namespace . '/distributor/list-pull-content', + $remote_post_args + ); + + if ( is_wp_error( $remote_posts ) ) { + return $remote_posts; + } + + foreach ( $items as $item_array ) { + $post = wp_list_filter( $remote_posts['items'], array( 'ID' => $item_array['remote_post_id'] ) ); + if ( empty( $post ) ) { + $created_posts[] = new \WP_Error( 'no-post', esc_html__( 'No post found.', 'distributor' ) ); continue; } + $post = reset( $post ); $post_props = get_object_vars( $post ); $post_array = array(); @@ -509,26 +373,24 @@ public function pull( $items ) { $post_array[ $key ] = $value; } + $update = false; + // Unset data from remote site. + unset( $post_array['ID'] ); + unset( $post_array['post_parent'] ); + unset( $post_array['post_date'] ); + unset( $post_array['post_date_gmt'] ); + unset( $post_array['post_modified'] ); + unset( $post_array['post_modified_gmt'] ); + if ( ! empty( $item_array['post_id'] ) ) { + $update = true; $post_array['ID'] = $item_array['post_id']; - } else { - unset( $post_array['ID'] ); - } - - if ( isset( $post_array['post_parent'] ) ) { - unset( $post_array['post_parent'] ); } if ( ! empty( $item_array['post_status'] ) ) { $post_array['post_status'] = $item_array['post_status']; } - // Remove date stuff - unset( $post_array['post_date'] ); - unset( $post_array['post_date_gmt'] ); - unset( $post_array['post_modified'] ); - unset( $post_array['post_modified_gmt'] ); - /** * Filter the arguments passed into wp_insert_post during a pull. * @@ -543,7 +405,11 @@ public function pull( $items ) { * @return {array} The post data to be inserted. */ $new_post_args = Utils\post_args_allow_list( apply_filters( 'dt_pull_post_args', $post_array, $item_array['remote_post_id'], $post, $this ) ); - $new_post = wp_insert_post( wp_slash( $new_post_args ) ); + if ( $update ) { + $new_post = wp_update_post( wp_slash( $new_post_args ) ); + } else { + $new_post = wp_insert_post( wp_slash( $new_post_args ) ); + } update_post_meta( $new_post, 'dt_original_post_id', (int) $item_array['remote_post_id'] ); update_post_meta( $new_post, 'dt_original_source_id', (int) $this->id ); @@ -940,55 +806,18 @@ private function not_distributor_internal_post_type( $post_type ) { * @return \WP_Post */ private function to_wp_post( $post ) { - $obj = new \stdClass(); - - $obj->ID = $post['id']; - $obj->post_title = $post['title']['rendered']; - - if ( isset( $post['excerpt']['raw'] ) ) { - $obj->post_excerpt = $post['excerpt']['raw']; - } elseif ( isset( $post['excerpt']['rendered'] ) ) { - $obj->post_excerpt = $post['excerpt']['rendered']; - } else { - $obj->post_excerpt = ''; - } + $obj = (object) $post; - $obj->post_status = 'draft'; - $obj->post_author = get_current_user_id(); - - $obj->post_password = $post['password']; - $obj->post_date = $post['date']; - $obj->post_date_gmt = $post['date_gmt']; - $obj->guid = $post['guid']['rendered']; - $obj->post_modified = $post['modified']; - $obj->post_modified_gmt = $post['modified_gmt']; - $obj->post_type = $post['type']; - $obj->link = $post['link']; - $obj->comment_status = $post['comment_status']; - $obj->ping_status = $post['ping_status']; - - if ( isset( $post['content']['raw'] ) ) { - // Use raw content if remote post uses Gutenberg and the local post type is compatible with it. - $obj->post_content = Utils\dt_use_block_editor_for_post_type( $obj->post_type ) && isset( $post['is_using_gutenberg'] ) ? - $post['content']['raw'] : - Utils\get_processed_content( $post['content']['raw'] ); - } elseif ( isset( $post['content']['rendered'] ) ) { - $obj->post_content = $post['content']['rendered']; - } else { - $obj->post_content = ''; - } - - - /** + /* * These will only be set if Distributor is active on the other side */ - $obj->meta = ( ! empty( $post['distributor_meta'] ) ) ? $post['distributor_meta'] : []; - $obj->terms = ( ! empty( $post['distributor_terms'] ) ) ? $post['distributor_terms'] : []; - $obj->media = ( ! empty( $post['distributor_media'] ) ) ? $post['distributor_media'] : []; $obj->original_site_name = ( ! empty( $post['distributor_original_site_name'] ) ) ? $post['distributor_original_site_name'] : null; $obj->original_site_url = ( ! empty( $post['distributor_original_site_url'] ) ) ? $post['distributor_original_site_url'] : null; - $obj->full_connection = ( ! empty( $post['full_connection'] ) ); + // Unset these as they are renamed above. + unset( $obj->distributor_original_site_name ); + unset( $obj->distributor_original_site_url ); + /** * Filter the post item. @@ -1001,7 +830,9 @@ private function to_wp_post( $post ) { * * @return {WP_Post} The WP_Post that is being pushed. */ - return apply_filters( 'dt_item_mapping', new \WP_Post( $obj ), $post, $this ); + $post_object = apply_filters( 'dt_item_mapping', new \WP_Post( $obj ), $post, $this ); + + return $post_object; } /** diff --git a/includes/classes/InternalConnections/NetworkSiteConnection.php b/includes/classes/InternalConnections/NetworkSiteConnection.php index 857eb9a95..7a9861636 100644 --- a/includes/classes/InternalConnections/NetworkSiteConnection.php +++ b/includes/classes/InternalConnections/NetworkSiteConnection.php @@ -161,8 +161,8 @@ public function push( $post, $args = array() ) { $output['id'] = $new_post_id; - update_post_meta( $new_post_id, 'dt_original_blog_id', (int) $original_blog_id ); - update_post_meta( $new_post_id, 'dt_syndicate_time', time() ); + update_post_meta( $new_post_id, 'dt_original_blog_id', absint( $original_blog_id ) ); + update_post_meta( $new_post_id, 'dt_syndicate_time', absint( time() ) ); /** * Allow bypassing of all meta processing. @@ -256,15 +256,25 @@ public function pull( $items ) { $created_posts = array(); foreach ( $items as $item_array ) { - $post = $this->remote_get( [ 'id' => $item_array['remote_post_id'] ] ); + $update = false; + $insert_args = array(); + if ( ! empty( $item_array['post_status'] ) ) { + $insert_args['post_status'] = $item_array['post_status']; + } + if ( ! empty( $item_array['post_id'] ) ) { + $insert_args['remote_post_id'] = $item_array['post_id']; + $update = true; + } + + $post = $this->remote_get( [ 'id' => $item_array['remote_post_id'] ], $insert_args ); if ( is_wp_error( $post ) ) { $created_posts[] = $post; continue; } - $post_props = get_object_vars( $post ); - $post_array = array(); + $post_props = $post; + $post_array = $post_props; $current_blog_id = get_current_blog_id(); if ( ! empty( $post_props['meta']['dt_connection_map'] ) ) { @@ -278,41 +288,22 @@ public function pull( $items ) { } } - foreach ( $post_props as $key => $value ) { - $post_array[ $key ] = $value; - } - - if ( ! empty( $item_array['post_id'] ) ) { - $post_array['ID'] = $item_array['post_id']; - } else { - unset( $post_array['ID'] ); - } - - if ( isset( $post_array['post_parent'] ) ) { - unset( $post_array['post_parent'] ); - } - - if ( ! empty( $item_array['post_status'] ) ) { - $post_array['post_status'] = $item_array['post_status']; - } - add_filter( 'wp_insert_post_data', array( '\Distributor\InternalConnections\NetworkSiteConnection', 'maybe_set_modified_date' ), 10, 2 ); // Filter documented in includes/classes/ExternalConnections/WordPressExternalConnection.php $new_post_args = Utils\post_args_allow_list( apply_filters( 'dt_pull_post_args', $post_array, $item_array['remote_post_id'], $post, $this ) ); - $new_post_id = wp_insert_post( wp_slash( $new_post_args ) ); - + if ( $update ) { + $new_post_id = wp_update_post( wp_slash( $new_post_args ) ); + } else { + $new_post_id = wp_insert_post( wp_slash( $new_post_args ) ); + } remove_filter( 'wp_insert_post_data', array( '\Distributor\InternalConnections\NetworkSiteConnection', 'maybe_set_modified_date' ), 10, 2 ); if ( ! is_wp_error( $new_post_id ) ) { - update_post_meta( $new_post_id, 'dt_original_post_id', (int) $item_array['remote_post_id'] ); - update_post_meta( $new_post_id, 'dt_original_blog_id', (int) $this->site->blog_id ); - update_post_meta( $new_post_id, 'dt_syndicate_time', time() ); - update_post_meta( $new_post_id, 'dt_original_post_url', esc_url_raw( $post->link ) ); - - if ( ! empty( $post->post_parent ) ) { - update_post_meta( $new_post_id, 'dt_original_post_parent', (int) $post->post_parent ); - } + update_post_meta( $new_post_id, 'dt_original_blog_id', absint( $this->site->blog_id ) ); + update_post_meta( $new_post_id, 'dt_syndicate_time', absint( time() ) ); + update_post_meta( $new_post_id, 'dt_original_post_id', absint( $new_post_args['meta_input']['dt_original_post_id'] ) ); + update_post_meta( $new_post_id, 'dt_original_post_url', wp_slash( sanitize_url( $new_post_args['meta_input']['dt_original_post_url'] ) ) ); /** * Allow bypassing of all meta processing. @@ -321,15 +312,15 @@ public function pull( $items ) { * * @param {bool} true If Distributor should set the post meta. * @param {int} $new_post_id The newly created post ID. - * @param {array} $post->meta List of meta items attached to the post, formatted by {@link \Distributor\Utils\prepare_meta()}. + * @param {array} $post_meta List of meta items attached to the post, formatted by {@link \Distributor\Utils\prepare_meta()}. * @param {int} $remote_post_id The original post ID. * @param {array} $post_array The arguments passed into wp_insert_post. * @param {NetworkSiteConnection} $this The Distributor connection being pulled from. * * @return {bool} If Distributor should set the post meta. */ - if ( apply_filters( 'dt_pull_post_meta', true, $new_post_id, $post->meta, $item_array['remote_post_id'], $post_array, $this ) ) { - \Distributor\Utils\set_meta( $new_post_id, $post->meta ); + if ( apply_filters( 'dt_pull_post_meta', true, $new_post_id, $post['meta'], $item_array['remote_post_id'], $post_array, $this ) ) { + \Distributor\Utils\set_meta( $new_post_id, $post['meta'] ); } /** @@ -339,15 +330,15 @@ public function pull( $items ) { * * @param {bool} true If Distributor should set the post terms. * @param {int} $new_post_id The newly created post ID. - * @param {array} $post->terms List of terms items attached to the post, formatted by {@link \Distributor\Utils\prepare_taxonomy_terms()}. + * @param {array} $post_terms List of terms items attached to the post, formatted by {@link \Distributor\Utils\prepare_taxonomy_terms()}. * @param {int} $remote_post_id The original post ID. * @param {array} $post_array The arguments passed into wp_insert_post. * @param {NetworkSiteConnection} $this The Distributor connection being pulled from. * * @return {bool} If Distributor should set the post terms. */ - if ( apply_filters( 'dt_pull_post_terms', true, $new_post_id, $post->terms, $item_array['remote_post_id'], $post_array, $this ) ) { - \Distributor\Utils\set_taxonomy_terms( $new_post_id, $post->terms ); + if ( apply_filters( 'dt_pull_post_terms', true, $new_post_id, $post['terms'], $item_array['remote_post_id'], $post_array, $this ) ) { + \Distributor\Utils\set_taxonomy_terms( $new_post_id, $post['terms'] ); } /** @@ -357,15 +348,15 @@ public function pull( $items ) { * * @param {bool} true If Distributor should set the post media. * @param {int} $new_post_id The newly created post ID. - * @param {array} $post->media List of media items attached to the post, formatted by {@link \Distributor\Utils\prepare_media()}. + * @param {array} $post_media List of media items attached to the post, formatted by {@link \Distributor\Utils\prepare_media()}. * @param {int} $remote_post_id The original post ID. * @param {array} $post_array The arguments passed into wp_insert_post. * @param {NetworkSiteConnection} $this The Distributor connection being pulled from. * * @return {bool} If Distributor should set the post media. */ - if ( apply_filters( 'dt_pull_post_media', true, $new_post_id, $post->media, $item_array['remote_post_id'], $post_array, $this ) ) { - \Distributor\Utils\set_media( $new_post_id, $post->media, [ 'use_filesystem' => true ] ); + if ( apply_filters( 'dt_pull_post_media', true, $new_post_id, $post['media'], $item_array['remote_post_id'], $post_array, $this ) ) { + \Distributor\Utils\set_media( $new_post_id, $post['media'], [ 'use_filesystem' => true ] ); }; } @@ -504,11 +495,18 @@ public function get_post_types() { /** * Remotely get posts so we can list them for pulling * - * @param array $args Array of args for getting. * @since 0.8 + * @since 2.0.0 Added $new_post_args parameter. + * + * @param array $args Array of args for getting. + * @param array $new_post_args { + * Array of args for creating new post. + * + * @type string $post_status Post status for new post. + * } * @return array|WP_Post|bool */ - public function remote_get( $args = array() ) { + public function remote_get( $args = array(), $new_post_args = array() ) { $id = ( empty( $args['id'] ) ) ? false : $args['id']; @@ -593,7 +591,10 @@ public function remote_get( $args = array() ) { if ( empty( $post ) ) { $formatted_post = false; } else { - $formatted_post = Utils\prepare_post( $post ); + $dt_post = new DistributorPost( $post ); + $formatted_post = $dt_post->to_insert( $new_post_args ); + // The pull method requires the connection map despite it being on the deny list. + $formatted_post['meta']['dt_connection_map'] = get_post_meta( $id, 'dt_connection_map', true ); } restore_current_blog(); diff --git a/includes/pull-ui.php b/includes/pull-ui.php index 14226b813..f56c02a73 100644 --- a/includes/pull-ui.php +++ b/includes/pull-ui.php @@ -529,16 +529,18 @@ function dashboard() { prepare_items(); ?> pull_error ) ) : ?> -

- +
+

+ +
views(); ?> diff --git a/includes/rest-api.php b/includes/rest-api.php index c5d40f3c8..2445df0a5 100644 --- a/includes/rest-api.php +++ b/includes/rest-api.php @@ -7,7 +7,9 @@ namespace Distributor\RestApi; +use Distributor\DistributorPost; use Distributor\Utils; +use WP_Error; /** * Setup actions and filters @@ -149,7 +151,7 @@ function register_rest_routes() { 'distributor/list-pull-content', array( 'methods' => 'POST', - 'callback' => __NAMESPACE__ . '\get_pull_content', + 'callback' => __NAMESPACE__ . '\\get_pull_content_list', 'permission_callback' => __NAMESPACE__ . '\\get_pull_content_permissions', 'args' => get_pull_content_list_args(), ) @@ -157,7 +159,9 @@ function register_rest_routes() { } /** - * Set the accepted arguments for the pull content list endpoint + * Set the accepted arguments for the pull content list endpoint. + * + * @since 2.0.0 Introduced the include, order and orderby arguments. * * @return array */ @@ -172,6 +176,21 @@ function get_pull_content_list_args() { ), 'default' => array(), ), + 'include' => array( + 'description' => esc_html__( 'Ensure result set includes specific IDs.', 'distributor' ), + 'type' => array( 'array', 'integer' ), + 'items' => array( + 'type' => 'integer', + ), + 'default' => array(), + 'sanitize_callback' => function( $param ) { + if ( ! is_array( $param ) ) { + $param = array( $param ); + } + + return wp_parse_id_list( $param ); + }, + ), 'page' => array( 'description' => esc_html__( 'Current page of the collection.', 'distributor' ), 'type' => 'integer', @@ -190,10 +209,71 @@ function get_pull_content_list_args() { ), 'post_type' => array( 'description' => esc_html__( 'Limit results to content matching a certain type.', 'distributor' ), - 'type' => 'string', - 'default' => 'post', - 'sanitize_callback' => 'sanitize_text_field', - 'validate_callback' => 'rest_validate_request_arg', + 'type' => array( 'array', 'string' ), + 'items' => array( + 'type' => 'string', + ), + 'default' => array( 'post' ), + 'validate_callback' => function( $param ) { + if ( is_string( $param ) ) { + return sanitize_key( $param ) === $param; + } + + foreach ( $param as $post_type ) { + if ( sanitize_key( $post_type ) !== $post_type ) { + return false; + } + } + + return true; + }, + 'sanitize_callback' => function( $param ) { + if ( is_string( $param ) ) { + $param = array( $param ); + } + + $allowed_post_types = array_keys( + get_post_types( + array( + 'show_in_rest' => true, + ) + ) + ); + + /* + * Only post types viewable on the front end should be allowed. + * + * Some post types may be visible in the REST API but not intended + * to be viewed on the front end. This removes any such posts from the + * list of allowed post types. + * + * `is_post_type_viewable()` is used to filter the results as + * WordPress applies different rules for custom and built in post + * types to determine whether they are viewable on the front end. + */ + $allowed_post_types = array_filter( $allowed_post_types, 'is_post_type_viewable' ); + + if ( in_array( 'any', $param, true ) ) { + $param = $allowed_post_types; + } else { + $param = array_intersect( $param, $allowed_post_types ); + } + + $param = array_filter( + $param, + function( $post_type ) { + $post_type_object = get_post_type_object( $post_type ); + return current_user_can( $post_type_object->cap->edit_posts ); + } + ); + + if ( empty( $param ) ) { + // This will cause the parameter to fall back to the default. + $param = null; + } + + return $param; + }, ), 'search' => array( 'description' => esc_html__( 'Limit results to those matching a string.', 'distributor' ), @@ -201,13 +281,74 @@ function get_pull_content_list_args() { 'validate_callback' => 'rest_validate_request_arg', ), 'post_status' => array( - 'default' => 'publish', - 'description' => esc_html__( 'Limit result set to content assigned one or more statuses.', 'distributor' ), - 'type' => 'array', - 'items' => array( - 'enum' => array_merge( array_keys( get_post_stati() ), array( 'any' ) ), + 'default' => array( 'publish' ), + 'description' => esc_html__( 'Limit result set to content assigned one or more statuses.', 'distributor' ), + 'type' => array( 'array', 'string' ), + 'items' => array( 'type' => 'string', ), + 'validate_callback' => function( $param ) { + if ( is_string( $param ) ) { + return sanitize_key( $param ) === $param; + } + + foreach ( $param as $post_status ) { + if ( sanitize_key( $post_status ) !== $post_status ) { + return false; + } + } + + return true; + }, + 'sanitize_callback' => function( $param ) { + if ( is_string( $param ) ) { + $param = array( $param ); + } + + /* + * Only show viewable post statues. + * + * `is_post_status_viewable()` is used to filter the results as + * WordPress applies a complex set of rules to determine if a post + * status is viewable. + */ + $allowed_statues = array_keys( array_filter( get_post_stati(), 'is_post_status_viewable' ) ); + + if ( in_array( 'any', $param, true ) ) { + return $allowed_statues; + } + + $param = array_intersect( $param, $allowed_statues ); + + if ( empty( $param ) ) { + // This will cause the parameter to fall back to the default. + $param = null; + } + + return $param; + }, + ), + 'order' => array( + 'description' => esc_html__( 'Order sort attribute ascending or descending.', 'distributor' ), + 'type' => 'string', + 'default' => 'desc', + 'enum' => array( 'asc', 'desc' ), + ), + 'orderby' => array( + 'description' => esc_html__( 'Sort collection by object attribute.', 'distributor' ), + 'type' => 'string', + 'default' => 'date', + 'enum' => array( + 'author', + 'date', + 'id', + 'include', + 'modified', + 'parent', + 'relevance', + 'slug', + 'title', + ), ), ); } @@ -223,17 +364,54 @@ function get_pull_content_list_args() { * @return bool Whether the current user has permission to pull content. */ function get_pull_content_permissions( $request ) { - $post_type = $request->get_param( 'post_type' ); - if ( ! $post_type ) { - return false; + /* + * Ensure Distributor requests are coming from a supported version. + * + * Changes to this endpoint in Distributor 2.0.0 require both the source and remote + * sites use a 2.x release of Distributor. This check ensures that the remote site + * is running a version of Distributor that supports the new endpoint. + * + * Development versions of the plugin and Non-Distributor requests are allowed + * to pass through this check. + */ + if ( + false === Utils\is_development_version() + && null !== $request->get_param( 'distributor_request' ) + && ( + null === $request->get_header( 'X-Distributor-Version' ) + || version_compare( $request->get_header( 'X-Distributor-Version' ), '2.0.0', '<' ) + ) + ) { + return new \WP_Error( + 'distributor_pull_content_permissions', + esc_html__( 'Pulling content from external connections requires Distributor version 2.0.0 or later.', 'distributor' ), + array( 'status' => 403 ) + ); + } - $post_type_object = get_post_type_object( $post_type ); - if ( ! $post_type_object ) { + $post_types = $request->get_param( 'post_type' ); + if ( empty( $post_types ) ) { return false; } - return current_user_can( $post_type_object->cap->edit_posts ); + if ( is_string( $post_types ) ) { + $post_types = array( $post_types ); + } + + foreach ( $post_types as $post_type ) { + $post_type_object = get_post_type_object( $post_type ); + if ( ! $post_type_object ) { + return false; + } + + if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) { + return false; + } + } + + // User can edit all post types. + return true; } /** @@ -433,23 +611,61 @@ function check_post_types_permissions() { /** * Get a list of content to show on the Pull screen * + * @since 2.0.0 Renamed from get_pull_content() to get_pull_content_list(). + * * @param \WP_Rest_Request $request API request arguments * @return \WP_REST_Response|\WP_Error */ -function get_pull_content( $request ) { +function get_pull_content_list( $request ) { $args = [ 'posts_per_page' => isset( $request['posts_per_page'] ) ? $request['posts_per_page'] : 20, 'paged' => isset( $request['page'] ) ? $request['page'] : 1, 'post_type' => isset( $request['post_type'] ) ? $request['post_type'] : 'post', 'post_status' => isset( $request['post_status'] ) ? $request['post_status'] : array( 'any' ), + 'order' => ! empty( $request['order'] ) ? strtoupper( $request['order'] ) : 'DESC', ]; if ( ! empty( $request['search'] ) ) { - $args['s'] = rawurldecode( $request['search'] ); + $args['s'] = rawurldecode( $request['search'] ); + $args['orderby'] = 'relevance'; } - if ( ! empty( $request['exclude'] ) ) { + if ( ! empty( $request['exclude'] ) && ! empty( $request['include'] ) ) { + /* + * Use only `post__in` if both `include` and `exclude` are populated. + * + * Excluded posts take priority over included posts, if the same post is + * included in both arrays, it will be excluded. + */ + $args['post__in'] = array_diff( $request['include'], $request['exclude'] ); + } elseif ( ! empty( $request['exclude'] ) ) { $args['post__not_in'] = $request['exclude']; + } elseif ( ! empty( $request['include'] ) ) { + $args['post__in'] = $request['include']; + } + + if ( ! empty( $request['orderby'] ) ) { + $args['orderby'] = $request['orderby']; + + if ( 'id' === $request['orderby'] ) { + // Flip the case to uppercase for WP_Query. + $args['orderby'] = 'ID'; + } elseif ( 'slug' === $request['orderby'] ) { + $args['orderby'] = 'name'; + } elseif ( 'relevance' === $request['orderby'] ) { + $args['orderby'] = 'relevance'; + + // If ordering by relevance, a search term must be defined. + if ( empty( $request['search'] ) ) { + return new WP_Error( + 'rest_no_search_term_defined', + __( 'You need to define a search term to order by relevance.', 'distributor' ), + array( 'status' => 400 ) + ); + } + } elseif ( 'include' === $request['orderby'] ) { + $args['orderby'] = 'post__in'; + } } /** @@ -493,22 +709,8 @@ function get_pull_content( $request ) { continue; } - $formatted_posts[] = array( - 'id' => $post->ID, - 'title' => array( 'rendered' => $post->post_title ), - 'excerpt' => array( 'rendered' => $post->post_excerpt ), - 'content' => array( 'raw' => $post->post_content ), - 'password' => $post->post_password, - 'date' => $post->post_date, - 'date_gmt' => $post->post_date_gmt, - 'guid' => array( 'rendered' => $post->guid ), - 'modified' => $post->post_modified, - 'modified_gmt' => $post->post_modified_gmt, - 'type' => $post->post_type, - 'link' => get_the_permalink( $post ), - 'comment_status' => $post->comment_status, - 'ping_status' => $post->ping_status, - ); + $dt_post = new DistributorPost( $post->ID ); + $formatted_posts[] = $dt_post->to_pull_list(); } $response = rest_ensure_response( $formatted_posts ); @@ -519,6 +721,19 @@ function get_pull_content( $request ) { return $response; } +/** + * Get a list of content to show on the Pull screen + * + * @since 2.0.0 Deprecated in favour of get_pull_content_list(). + * + * @param array ...$args Arguments. + * @return \WP_REST_Response|\WP_Error + */ +function get_pull_content( ...$args ) { + _deprecated_function( __FUNCTION__, '2.0.0', __NAMESPACE__ . '\\get_pull_content_list' ); + return get_pull_content_list( ...$args ); +} + /** * Checks if a post can be read. * diff --git a/includes/settings.php b/includes/settings.php index fcf544acb..3cf6705a6 100644 --- a/includes/settings.php +++ b/includes/settings.php @@ -126,7 +126,7 @@ function update_notice( $plugin_file, $plugin_data, $status ) { */ function maybe_notice() { if ( 0 === strpos( get_current_screen()->parent_base, 'distributor' ) ) { - if ( file_exists( DT_PLUGIN_PATH . 'composer.lock' ) ) { + if ( Utils\is_development_version() ) { ?>
diff --git a/includes/subscriptions.php b/includes/subscriptions.php index 2fd92df1d..242c2109f 100644 --- a/includes/subscriptions.php +++ b/includes/subscriptions.php @@ -219,6 +219,9 @@ function delete_subscriptions( $post_id ) { // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout 'timeout' => 5, 'blocking' => \Distributor\Utils\is_dt_debug(), + 'headers' => array( + 'X-Distributor-Version' => DT_VERSION, + ), 'body' => [ 'post_id' => $remote_post_id, 'signature' => $signature, @@ -313,7 +316,8 @@ function send_notifications( $post ) { 'timeout' => $request_timeout, 'body' => wp_json_encode( $post_body ), 'headers' => [ - 'Content-Type' => 'application/json', + 'Content-Type' => 'application/json', + 'X-Distributor-Version' => DT_VERSION, ], ]; diff --git a/includes/utils.php b/includes/utils.php index 843e942f0..50f5e8228 100644 --- a/includes/utils.php +++ b/includes/utils.php @@ -9,6 +9,17 @@ use Distributor\DistributorPost; +/** + * Determine if this is a development install of Distributor. + * + * @since 2.0.0 + * + * @return bool True if this is a development install, false otherwise. + */ +function is_development_version() { + return file_exists( DT_PLUGIN_PATH . 'composer.lock' ); +} + /** * Determine if we are on VIP * @@ -108,6 +119,9 @@ function check_license_key( $email, $license_key ) { [ // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout 'timeout' => 10, + 'headers' => [ + 'X-Distributor-Version' => DT_VERSION, + ], 'body' => [ 'license_key' => $license_key, 'email' => $email, diff --git a/phpcs.xml b/phpcs.xml index 3288dc58d..df4ee2967 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -1,6 +1,9 @@ - + + + + diff --git a/tests/php/NetworkSiteConnectionsTest.php b/tests/php/NetworkSiteConnectionsTest.php index 14e3f5aa8..6cef652c8 100644 --- a/tests/php/NetworkSiteConnectionsTest.php +++ b/tests/php/NetworkSiteConnectionsTest.php @@ -237,6 +237,24 @@ public function test_push() { * @runInSeparateProcess */ public function test_pull() { + $this->setup_post_meta_mock( array( + 'dt_connection_map' => array( array() ) + ) ); + \WP_Mock::userFunction( + 'get_bloginfo', + array( + 'return' => function( $info ) { + switch ( $info ) { + case 'charset': + return 'UTF-8'; + case 'name': + return 'Test Internal Origin'; + default: + return ''; + } + }, + ) + ); $this->connection_obj->site->blog_id = 2; @@ -247,16 +265,50 @@ public function test_pull() { \WP_Mock::userFunction( 'get_current_blog_id' ); \WP_Mock::userFunction( 'remove_filter' ); \WP_Mock::passthruFunction( 'wp_slash' ); + \WP_Mock::passthruFunction( 'sanitize_url' ); \WP_Mock::userFunction( 'get_post', [ 'return' => (object) [ 'ID' => 111, - 'post_tite' => 'My post title', + 'post_title' => 'My post title', + 'post_name' => 'my-post-title', + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_content' => 'My post content', + 'post_excerpt' => 'My post excerpt', 'meta' => [], ], ] ); + \WP_Mock::userFunction( + 'get_the_title', [ + 'return' => 'My post title', + ] + ); + \WP_Mock::userFunction( + 'has_blocks', + array( + 'return' => false, + ) + ); + \WP_Mock::userFunction( + 'get_attached_media', + array( + 'return' => array(), + ) + ); + \WP_Mock::userFunction( + 'get_post_thumbnail_id', + array( + 'return' => false, + ) + ); + \WP_Mock::userFunction( + 'get_current_user_id', [ + 'return' => 1, + ] + ); \WP_Mock::userFunction( 'get_permalink', [ @@ -273,7 +325,7 @@ public function test_pull() { \WP_Mock::userFunction( 'update_post_meta', [ 'times' => 1, - 'args' => [ \WP_Mock\Functions::type( 'int' ), 'dt_original_post_id', 2 ], + 'args' => [ \WP_Mock\Functions::type( 'int' ), 'dt_original_post_id', 111 ], 'return' => [], ] ); @@ -302,14 +354,6 @@ public function test_pull() { ] ); - \WP_Mock::userFunction( - 'get_post_meta', [ - 'times' => 1, - 'args' => [ \WP_Mock\Functions::type( 'int' ), 'dt_connection_map', true ], - 'return' => [], - ] - ); - \WP_Mock::userFunction( 'update_post_meta', [ 'times' => 1, @@ -380,10 +424,66 @@ public function test_remote_get() { 'return' => (object) [ 'ID' => 111, 'post_title' => 'my title', + 'post_name' => 'my-title', + 'post_type' => 'post', + 'post_status' => 'publish', + 'post_content' => 'My post content', + 'post_excerpt' => 'My post excerpt', ], ] ); + \WP_Mock::userFunction( + 'get_bloginfo', + array( + 'return' => function( $info ) { + switch ( $info ) { + case 'charset': + return 'UTF-8'; + case 'name': + return 'Test Internal Origin'; + default: + return ''; + } + }, + ) + ); + + \WP_Mock::userFunction( + 'get_current_blog_id', [ + 'return' => 1, + ] + ); + + \WP_Mock::userFunction( + 'get_the_title', [ + 'return' => 'my title', + ] + ); + \WP_Mock::userFunction( + 'has_blocks', + array( + 'return' => false, + ) + ); + \WP_Mock::userFunction( + 'get_attached_media', + array( + 'return' => array(), + ) + ); + \WP_Mock::userFunction( + 'get_post_thumbnail_id', + array( + 'return' => false, + ) + ); + \WP_Mock::userFunction( + 'get_current_user_id', [ + 'return' => 1, + ] + ); + $this->assertArrayHasKey( 'post_title', (array) $this->connection_obj->remote_get( [ diff --git a/tests/php/SubscriptionsTest.php b/tests/php/SubscriptionsTest.php index e46f25e37..089599def 100644 --- a/tests/php/SubscriptionsTest.php +++ b/tests/php/SubscriptionsTest.php @@ -98,6 +98,9 @@ public function test_delete_subscribed_post() { [ 'timeout' => 5, 'blocking' => \Distributor\Utils\is_dt_debug(), + 'headers' => [ + 'X-Distributor-Version' => DT_VERSION, + ], 'body' => [ 'post_id' => $remote_post_id, 'signature' => $signature, @@ -375,6 +378,7 @@ public function test_send_notifications_no_remote_post() { ] ), 'headers' => [ 'Content-Type' => 'application/json', + 'X-Distributor-Version' => DT_VERSION, ], ], ], @@ -566,6 +570,7 @@ public function test_send_notifications_remote_post_exists() { ] ), 'headers' => [ 'Content-Type' => 'application/json', + 'X-Distributor-Version' => DT_VERSION, ] ], ], @@ -761,6 +766,9 @@ public function test_create_remote_subscription() { [ 'timeout' => 5, 'blocking' => \Distributor\Utils\is_dt_debug(), + 'headers' => [ + 'X-Distributor-Version' => DT_VERSION, + ], 'body' => [ 'post_id' => $remote_post_id, 'remote_post_id' => $post_id, diff --git a/tests/php/WordPressExternalConnectionTest.php b/tests/php/WordPressExternalConnectionTest.php index b74d1bc48..6c8b587fa 100644 --- a/tests/php/WordPressExternalConnectionTest.php +++ b/tests/php/WordPressExternalConnectionTest.php @@ -254,17 +254,27 @@ public function test_push() { * @runInSeparateProcess */ public function test_pull() { + $this->setup_post_meta_mock( array() ); $post_id = 123; - \WP_Mock::userFunction( 'wp_remote_retrieve_response_code' ); \WP_Mock::userFunction( 'untrailingslashit' ); \WP_Mock::userFunction( 'sanitize_text_field' ); remote_get_setup(); + \WP_Mock::passthruFunction( 'wp_slash' ); + \WP_Mock::passthruFunction( 'update_post_meta' ); \WP_Mock::userFunction( 'get_current_user_id' ); \WP_Mock::userFunction( 'delete_post_meta' ); + \WP_Mock::userFunction( + 'wp_remote_retrieve_headers', [ + 'return' => [ + 'X-Distributor' => 'yes', + ], + ] + ); + \WP_Mock::userFunction( 'wp_insert_post', [ 'return' => 2, @@ -283,24 +293,16 @@ public function test_pull() { ] ); - \WP_Mock::userFunction( - 'add_query_arg', [ - 'times' => 1, + $pull_actual = $this->connection->pull( + [ + [ + 'remote_post_id' => $post_id, + 'post_type' => 'post', + ], ] ); - $this->assertTrue( - is_array( - $this->connection->pull( - [ - [ - 'remote_post_id' => $post_id, - 'post_type' => 'post', - ], - ] - ) - ) - ); + $this->assertIsArray( $pull_actual ); } /** @@ -319,7 +321,6 @@ public function test_remote_get() { \WP_Mock::userFunction( 'wp_remote_retrieve_response_code', [ - 'times' => 2, 'return' => 200, ] ); @@ -333,12 +334,6 @@ public function test_remote_get() { ] ); - \WP_Mock::userFunction( - 'add_query_arg', [ - 'times' => 1, - ] - ); - \WP_Mock::userFunction( 'post_type_exists', [ 'args' => [ '' ], @@ -360,15 +355,14 @@ public function test_remote_get() { ] ); - $this->assertInstanceOf( - \WP_Post::class, $this->connection->remote_get( - [ - 'id' => 111, - 'post_type' => 'post', - ] - ) + $actual = $this->connection->remote_get( + [ + 'id' => 111, + 'post_type' => 'post', + ] ); + $this->assertInstanceOf( \WP_Post::class, $actual ); } /** diff --git a/tests/php/bootstrap.php b/tests/php/bootstrap.php index 16602dd84..aa1585f2b 100644 --- a/tests/php/bootstrap.php +++ b/tests/php/bootstrap.php @@ -10,6 +10,7 @@ WP_Mock::bootstrap(); define( 'DT_PLUGIN_PATH', dirname( __DIR__, 2 ) ); +define( 'DT_VERSION', '1.9.1' ); require_once __DIR__ . '/includes/common.php'; require_once __DIR__ . '/includes/TestCase.php'; diff --git a/tests/php/includes/common.php b/tests/php/includes/common.php index d0c59f04f..e5100d37d 100644 --- a/tests/php/includes/common.php +++ b/tests/php/includes/common.php @@ -157,50 +157,70 @@ function remote_get_setup() { \WP_Mock::userFunction( 'get_option' ); - $post_type = 'post'; - $links = [ - '_links' => [ - 'wp:items' => [ - [ 'href' => 'http://url.com' ], + $rest_response = [ + [ + 'post_title' => 'My post title', + 'post_name' => 'my-post-title', + 'post_type' => 'post', + 'post_content' => '', + 'post_excerpt' => '', + 'post_status' => 'publish', + 'terms' => [], + 'meta' => [], + 'media' => [], + 'post_author' => 1, + 'meta_input' => [ + 'dt_original_post_id' => 123, + 'dt_original_post_url' => 'http://example.com/2023/04/11/my-post-title/', ], + 'ID' => 123, + 'post_date' => '2023-04-11 05:40:43', + 'post_date_gmt' => '2023-04-11 05:40:43', + 'post_modified' => '2023-04-11 05:40:43', + 'post_modified_gmt' => '2023-04-11 05:40:43', + 'post_password' => '', + 'guid' => 'http://example.com/?p=123', + 'comment_status' => 'open', + 'ping_status' => 'open', + 'link' => 'http://example.com/2023/04/11/my-post-title/', + 'distributor_original_site_name' => 'My site name', + 'distributor_original_site_url' => 'http://example.com/', ], ]; + $post_response = $rest_response[0]; + $post_response['original_site_name'] = $post_response['distributor_original_site_name']; + $post_response['original_site_url'] = $post_response['distributor_original_site_url']; + unset( $post_response['distributor_original_site_name'] ); + unset( $post_response['distributor_original_site_url'] ); + + \WP_Mock::userFunction( + 'wp_remote_post', [ + 'return' => new stdClass(), + ] + ); + \WP_Mock::userFunction( 'wp_remote_request', [ - 'return' => json_encode( - [ - $post_type => $links, - ] - ), + 'return' => new stdClass(), ] ); - // todo: the response is missing post_type \WP_Mock::userFunction( 'wp_remote_retrieve_body', [ - 'return' => json_encode( - [ - 'id' => 123, - 'title' => [ 'rendered' => 'My post title' ], - 'content' => [ 'rendered' => '', 'raw' => '' ], - 'excerpt' => [ 'rendered' => '' ], - 'date' => '', - 'date_gmt' => '', - 'guid' => [ 'rendered' => '' ], - 'modified' => '', - 'modified_gmt' => '', - 'type' => '', - 'link' => '', - 'distributor_meta' => [], - 'distributor_terms' => [], - 'distributor_media' => [], - $post_type => $links, - 'comment_status' => 'open', - 'ping_status' => 'open', - 'password' => '', - ] - ), + 'return' => json_encode( $rest_response ), + ] + ); + + \WP_Mock::userFunction( + 'wp_remote_retrieve_response_code', [ + 'return' => 200, + ] + ); + + \WP_Mock::userFunction( + 'wp_list_filter', [ + 'return' => [ new WP_Post( (object) $post_response ) ], ] ); } @@ -276,6 +296,20 @@ function wp_parse_args( $settings, $defaults ) { return array_merge( $defaults, $settings ); } +/** + * Mock absint() function. + * + * Copied from WordPress core. + * + * @since 2.0.0 + * + * @param mixed $maybeint Data you wish to have converted to a non-negative integer. + * @return int A non-negative integer. + */ +function absint( $maybeint ) { + return abs( (int) $maybeint ); +} + /** * Stub for remove_filter to avoid failure in test_remote_get() *