From d77de763343878abe2b42fc4af75bcae1e97e68e Mon Sep 17 00:00:00 2001 From: Kirtan Gajjar <8456197+kirtangajjar@users.noreply.github.com> Date: Wed, 10 Jul 2024 03:17:42 +0530 Subject: [PATCH 1/2] Add method to get categories in connections --- includes/classes/Connection.php | 8 +++++ .../WordPressExternalConnection.php | 35 +++++++++++++++++++ .../NetworkSiteConnection.php | 14 ++++++++ 3 files changed, 57 insertions(+) diff --git a/includes/classes/Connection.php b/includes/classes/Connection.php index 9e22f5441..e40dcef52 100644 --- a/includes/classes/Connection.php +++ b/includes/classes/Connection.php @@ -65,6 +65,14 @@ abstract public function get_sync_log( $id ); */ abstract public function get_post_types(); + /** + * Get available post types from a connection + * + * @since 1.3 + * @return array|\WP_Error + */ + abstract public function get_post_categories(); + /** * This method is called on every page load. It's helpful for canonicalization * diff --git a/includes/classes/ExternalConnections/WordPressExternalConnection.php b/includes/classes/ExternalConnections/WordPressExternalConnection.php index 86cb2ef23..93c95f95d 100644 --- a/includes/classes/ExternalConnections/WordPressExternalConnection.php +++ b/includes/classes/ExternalConnections/WordPressExternalConnection.php @@ -665,6 +665,41 @@ public function get_post_types() { return $types_body_array; } + /** + * Get the available post categories. + * + * @since 1.3 + * @return array|\WP_Error + */ + public function get_post_categories() { + $path = self::$namespace; + + $categories_path = untrailingslashit( $this->base_url ) . '/' . $path . '/categories'; + + $categories_response = Utils\remote_http_request( + $categories_path, + $this->auth_handler->format_get_args( array( 'timeout' => self::$timeout ) ) + ); + + if ( is_wp_error( $categories_response ) ) { + return $categories_response; + } + + if ( 404 === wp_remote_retrieve_response_code( $categories_response ) ) { + return new \WP_Error( 'bad-endpoint', esc_html__( 'Could not connect to API endpoint.', 'distributor' ) ); + } + + $categories_body = wp_remote_retrieve_body( $categories_response ); + + if ( empty( $categories_body ) ) { + return new \WP_Error( 'no-response-body', esc_html__( 'Response body is empty.', 'distributor' ) ); + } + + $categories_body_array = json_decode( $categories_body, true ); + + return $categories_body_array; + } + /** * Check what we can do with a given external connection (push or pull) * diff --git a/includes/classes/InternalConnections/NetworkSiteConnection.php b/includes/classes/InternalConnections/NetworkSiteConnection.php index 0428347f7..1e2d02663 100644 --- a/includes/classes/InternalConnections/NetworkSiteConnection.php +++ b/includes/classes/InternalConnections/NetworkSiteConnection.php @@ -494,6 +494,20 @@ public function get_post_types() { return $post_types; } + /** + * Get the available post categories. + * + * @since 1.3 + * @return array + */ + public function get_post_categories() { + switch_to_blog( $this->site->blog_id ); + $post_categories = Utils\distributable_categories(); + restore_current_blog(); + + return $post_categories; + } + /** * Remotely get posts so we can list them for pulling * From d47454b11962c2497e4ce70a06772cdeb7ab53ae Mon Sep 17 00:00:00 2001 From: Kirtan Gajjar <8456197+kirtangajjar@users.noreply.github.com> Date: Wed, 10 Jul 2024 03:20:12 +0530 Subject: [PATCH 2/2] FIlter posts by category --- assets/js/admin-pull.js | 7 +- .../WordPressExternalConnection.php | 18 ++++ .../NetworkSiteConnection.php | 18 ++++ includes/classes/PullListTable.php | 34 +++++++- includes/pull-ui.php | 28 ++++-- includes/rest-api.php | 4 + includes/utils.php | 87 +++++++++++++++++++ 7 files changed, 186 insertions(+), 10 deletions(-) diff --git a/assets/js/admin-pull.js b/assets/js/admin-pull.js index 3de9f9255..35445020b 100755 --- a/assets/js/admin-pull.js +++ b/assets/js/admin-pull.js @@ -9,6 +9,7 @@ const { document } = window; const chooseConnection = document.getElementById( 'pull_connections' ); const choosePostType = document.getElementById( 'pull_post_type' ); const choosePostTypeBtn = document.getElementById( 'pull_post_type_submit' ); +const choosePostCategory = document.getElementById( 'pull_post_category' ); const searchField = document.getElementById( 'post-search-input' ); const searchBtn = document.getElementById( 'search-submit' ); const form = document.getElementById( 'posts-filter' ); @@ -24,7 +25,7 @@ jQuery( chooseConnection ).on( 'change', ( event ) => { document.body.className += ' ' + 'dt-loading'; } ); -if ( chooseConnection && choosePostType && form ) { +if ( chooseConnection && ( choosePostType || choosePostCategory ) && form ) { if ( choosePostTypeBtn ) { jQuery( choosePostTypeBtn ).on( 'click', ( event ) => { event.preventDefault(); @@ -84,6 +85,8 @@ if ( chooseConnection && choosePostType && form ) { const getURL = () => { const postType = choosePostType.options[ choosePostType.selectedIndex ].value; + const postCategory = + choosePostCategory.options[ choosePostCategory.selectedIndex ].value; const baseURL = chooseConnection.options[ chooseConnection.selectedIndex ].getAttribute( 'data-pull-url' @@ -96,5 +99,5 @@ const getURL = () => { status = 'pulled'; } - return `${ baseURL }&pull_post_type=${ postType }&status=${ status }`; + return `${ baseURL }&pull_post_type=${ postType }&pull_post_category=${ postCategory }&status=${ status }`; }; diff --git a/includes/classes/ExternalConnections/WordPressExternalConnection.php b/includes/classes/ExternalConnections/WordPressExternalConnection.php index 93c95f95d..47e6c20c6 100644 --- a/includes/classes/ExternalConnections/WordPressExternalConnection.php +++ b/includes/classes/ExternalConnections/WordPressExternalConnection.php @@ -71,6 +71,20 @@ class WordPressExternalConnection extends ExternalConnection { */ public $pull_post_types; + /** + * Default posts category to pull. + * + * @var string + */ + public $category; + + /** + * Default posts categories to show in filter. + * + * @var string + */ + public $categories; + /** * This is a utility function for parsing annoying API link headers returned by the types endpoint * @@ -167,6 +181,10 @@ public function remote_get( $args = array() ) { } } + if ( isset( $args['tax_query'] ) ) { + $query_args['tax_query'] = $args['tax_query']; + } + // 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'; diff --git a/includes/classes/InternalConnections/NetworkSiteConnection.php b/includes/classes/InternalConnections/NetworkSiteConnection.php index 1e2d02663..da8e2df93 100644 --- a/includes/classes/InternalConnections/NetworkSiteConnection.php +++ b/includes/classes/InternalConnections/NetworkSiteConnection.php @@ -45,6 +45,20 @@ class NetworkSiteConnection extends Connection { */ public $pull_post_types; + /** + * Default posts category to pull. + * + * @var string + */ + public $pull_post_category; + + /** + * Default posts categories to show in filter. + * + * @var string + */ + public $pull_post_categories; + /** * Set up network site connection * @@ -555,6 +569,10 @@ public function remote_get( $args = array(), $new_post_args = array() ) { $query_args['post__not_in'] = $args['post__not_in']; } + if ( isset( $args['tax_query'] ) ) { + $query_args['tax_query'] = $args['tax_query']; + } + $query_args['post_type'] = ( empty( $args['post_type'] ) ) ? 'post' : $args['post_type']; $query_args['post_status'] = ( empty( $args['post_status'] ) ) ? [ 'publish', 'draft', 'private', 'pending', 'future' ] : $args['post_status']; $query_args['posts_per_page'] = ( empty( $args['posts_per_page'] ) ) ? get_option( 'posts_per_page' ) : $args['posts_per_page']; diff --git a/includes/classes/PullListTable.php b/includes/classes/PullListTable.php index 57f206438..388e22415 100644 --- a/includes/classes/PullListTable.php +++ b/includes/classes/PullListTable.php @@ -462,8 +462,15 @@ public function prepare_items() { } else { $post_type = $connection_now->pull_post_type; } + + if ( empty( $connection_now->pull_post_category ) || 'all' === $connection_now->pull_post_category ) { + $post_category = wp_list_pluck( $connection_now->pull_post_post_categories, 'slug' ); + } else { + $post_category = $connection_now->pull_post_category; + } } else { $post_type = $connection_now->pull_post_type ? $connection_now->pull_post_type : 'post'; + $post_category = $connection_now->pull_post_category ? $connection_now->pull_post_category : ''; } $remote_get_args = [ @@ -477,6 +484,16 @@ public function prepare_items() { $remote_get_args['s'] = rawurlencode( $_GET['s'] ); // @codingStandardsIgnoreLine Nonce isn't required. } + if ( ! empty( $post_category ) && 'all' !== $post_category ) { + $remote_get_args['tax_query'] = [ + [ + 'taxonomy' => 'category', + 'field' => 'slug', + 'terms' => $post_category, + ], + ]; + } + if ( is_a( $connection_now, '\Distributor\ExternalConnection' ) ) { $this->sync_log = get_post_meta( $connection_now->id, 'dt_sync_log', true ); } else { @@ -626,15 +643,15 @@ public function extra_tablenav( $which ) { $connection_type = 'external'; } - if ( $connection_now && $connection_now->pull_post_types && $connection_now->pull_post_type ) : + if ( $connection_now && $connection_now->pull_post_types && $connection_now->pull_post_type && $connection_now->pull_post_categories ) : ?>
- + + + $remote_post_id, 'post_type' => $post_type, 'post_status' => $post_status, + 'post_category' => 'all' === $post_category ? '' : $post_category, ]; }, $posts @@ -473,12 +475,17 @@ function dashboard() { pull_post_types = \Distributor\Utils\available_pull_post_types( $connection_now, $connection_type ); + $connection_now->pull_post_categories = \Distributor\Utils\available_pull_post_categories( $connection_now, $connection_type ); // Ensure we have at least one post type to pull. $connection_now->pull_post_type = ''; + $connection_now->pull_post_category = ''; if ( ! empty( $connection_now->pull_post_types ) ) { $connection_now->pull_post_type = ( 'internal' === $connection_type ) ? 'all' : $connection_now->pull_post_types[0]['slug']; } + if ( ! empty( $connection_now->pull_post_categories ) ) { + $connection_now->pull_post_category = 'all'; + } // Set the post type we want to pull (if any) // This is either from a query param, "post" post type, or the first in the list @@ -495,6 +502,17 @@ function dashboard() { } } } + + foreach ( $connection_now->pull_post_categories as $post_category ) { + if ( isset( $_GET['pull_post_category'] ) ) { // @codingStandardsIgnoreLine No nonce needed here. + if ( $_GET['pull_post_category'] === $post_category['slug'] ) { // @codingStandardsIgnoreLine Comparing values, no nonce needed. + $connection_now->pull_post_category = $post_category['slug']; + break; + } + } else { + $connection_now->pull_post_category = $post_category['slug']; + } + } ?> diff --git a/includes/rest-api.php b/includes/rest-api.php index 41dceed42..a41a824a5 100644 --- a/includes/rest-api.php +++ b/includes/rest-api.php @@ -636,6 +636,10 @@ function get_pull_content_list( $request ) { $args['orderby'] = 'relevance'; } + if ( isset( $request['tax_query'] ) ) { + $args['tax_query'] = $request['tax_query']; + } + if ( ! empty( $request['exclude'] ) && ! empty( $request['include'] ) ) { /* * Use only `post__in` if both `include` and `exclude` are populated. diff --git a/includes/utils.php b/includes/utils.php index 642c02bdc..ec0c1da8c 100644 --- a/includes/utils.php +++ b/includes/utils.php @@ -285,6 +285,64 @@ function available_pull_post_types( $connection, $type ) { return $post_types; } +/** + * Get post categories available for pulling. + * + * This will compare the public post categories from a remote site + * against the public post categories from the origin site and return + * an array of categories supported on both. + * + * @param \Distributor\Connection $connection Connection object + * @param string $type Connection type + * @return array + */ +function available_pull_post_categories( $connection, $type ) { + $categories = array(); + $local_categories = array(); + $remote_categories = $connection->get_post_categories(); + $distributable_categories = distributable_categories( $remote_categories ); + + // Return empty array if the source site is not distributing any categories + if ( empty( $remote_categories ) || is_wp_error( $remote_categories ) ) { + return []; + } + + $local_categories = get_categories( [ 'hide_empty' => false ] ); + + if ( ! empty( $remote_categories ) ) { + foreach ( $remote_categories as $category ) { + $categories[] = array( + 'name' => 'external' === $type ? $category['name'] : $category->name, + 'slug' => 'external' === $type ? $category['slug'] : $category->slug, + ); + } + } + + /** + * Filter the categories that should be available for pull. + * + * @param array $categories Categories available for pull with name and slug. + * @param array $remote_categories Categories available from the remote connection. + * @param array $local_categories Categories registered on the local site. + * @param Connection $connection Distributor connection object. + * @param string $type Distributor connection type. + * + * @return array Categories available for pull with name and slug. + */ + $pull_categories = apply_filters( 'dt_available_pull_categories', $categories, $remote_categories, $local_categories, $connection, $type ); + + if ( ! empty( $pull_categories ) ) { + $categories = array(); + foreach ( $pull_categories as $category ) { + if ( in_array( $category['slug'], $distributable_categories, true ) ) { + $categories[] = $category; + } + } + } + + return $categories; +} + /** * Return post types that are allowed to be distributed * @@ -332,6 +390,35 @@ function distributable_post_types( $output = 'names' ) { return $post_types; } +/** + * Return categories that are allowed to be distributed + * + * @param string $output Optional. The type of output to return. + * Accepts category 'names' or 'objects'. Default 'names'. + * + * @return array + */ +function distributable_categories( $categories = [] ) { + if ( empty( $categories ) ) { + $categories = get_categories( [ 'hide_empty' => false ] ); + } else { + $categories = wp_list_pluck( $categories, 'slug' ); + } + + /** + * Filter categories that are distributable. + * + * @hook distributable_categories + * + * @param {array} $categories Categories that are distributable. + * + * @return {array} Categories that are distributable. + */ + $categories = apply_filters( 'distributable_categories', $categories ); + + return $categories; +} + /** * Return post statuses that are allowed to be distributed. *