Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change how the New tab is generated on the Pull screen for external connections #811

Merged
merged 5 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions includes/classes/ExternalConnections/WordPressExternalConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,19 @@ 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'] ) ) {
$query_args['post_type'] = isset( $post_type ) ? $post_type : 'post';
$query_args['per_page'] = isset( $posts_per_page ) ? $posts_per_page : 20;

$posts_response = $this->remote_post(
untrailingslashit( $this->base_url ) . '/' . self::$namespace . '/distributor/list-pull-content',
$query_args
);

return $posts_response;
}

static $types_urls;
$types_urls = array();

Expand Down Expand Up @@ -370,6 +383,110 @@ public function remote_get( $args = array() ) {
}
}

/**
* Make a remote_post request.
*
* @param string $url Endpoint URL.
* @param array $args Query arguments
* @return array|\WP_Error
*/
public function remote_post( $url = '', $args = array() ) {
if ( ! $url ) {
return new \WP_Error( 'endpoint-error', esc_html__( 'Endpoint URL must be set', 'distributor' ) );
}

$request = wp_remote_post(
$url,
$this->auth_handler->format_post_args(
array(
/**
* Filter the timeout used when calling `remote_post`
*
* @since 1.6.7
* @hook dt_remote_post_timeout
*
* @param int $timeout The timeout to use for the remote post. Default `45`.
* @param array $args The request arguments
*
* @return int The timeout to use for the remote_post call.
*/
'timeout' => apply_filters( 'dt_remote_post_timeout', 45, $args ),
/**
* Filter the remote_post query arguments
*
* @since 1.6.7
* @hook dt_remote_post_query_args
*
* @param {array} $args The request arguments.
* @param {WordPressExternalConnection} $this The current connection object.
*
* @return {array} The query arguments.
*/
'body' => apply_filters( 'dt_remote_post_query_args', $args, $this ),
)
)
);

if ( is_wp_error( $request ) ) {
return $request;
}

$response_code = wp_remote_retrieve_response_code( $request );

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( $request ), 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( $request );
$response_headers = wp_remote_retrieve_headers( $request );

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();

foreach ( $posts as $post ) {
$post['full_connection'] = ! empty( $response_headers['X-Distributor'] );

$formatted_posts[] = $this->to_wp_post( $post );
}

$total_posts = ! empty( $response_headers['X-WP-Total'] ) ? $response_headers['X-WP-Total'] : count( $formatted_posts );

/**
* Filter the items returned when using `WordPressExternalConnection::remote_post`
*
* @since 1.6.7
* @hook dt_remote_post
*
* @param {array} $items The items returned from the POST request.
* @param {array} $args The arguments used in the POST request.
* @param {WordPressExternalConnection} $this The current connection object.
*
* @return {array} The items returned from a remote POST request.
*/
return apply_filters(
'dt_remote_post',
dkotter marked this conversation as resolved.
Show resolved Hide resolved
[
'items' => $formatted_posts,
'total_items' => $total_posts,
],
$args,
$this
);
}

/**
* Pull items. Pass array of posts, each post should look like:
* [ 'remote_post_id' => POST ID TO GET, 'post_id' (optional) => POST ID TO MAP TO ]
Expand Down
15 changes: 5 additions & 10 deletions includes/classes/PullListTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -441,8 +441,7 @@ public function prepare_items() {
'posts_per_page' => $per_page,
'paged' => $current_page,
'post_type' => $post_type,
'orderby' => 'ID', // this is because of include/exclude truncation
'order' => 'DESC', // default but specifying to be safe
'dt_pull_list' => true, // custom argument used to only run code on this screen
];

if ( ! empty( $_GET['s'] ) ) { // @codingStandardsIgnoreLine Nonce isn't required.
Expand Down Expand Up @@ -478,15 +477,11 @@ public function prepare_items() {
}

if ( empty( $_GET['status'] ) || 'new' === $_GET['status'] ) { // @codingStandardsIgnoreLine Nonce not required.
// Sort from highest ID (newest) to low so the slice only affects later pagination.
rsort( $skipped, SORT_NUMERIC );
rsort( $syndicated, SORT_NUMERIC );
$post_ids = array_merge( $skipped, $syndicated );

// This is somewhat arbitrarily set to 200 and should probably be made filterable eventually.
// IDs can get rather large and 400 easily exceeds typical header size limits.
$post_ids = array_slice( array_merge( $skipped, $syndicated ), 0, 200, true );

$remote_get_args['post__not_in'] = $post_ids;
if ( ! empty( $post_ids ) ) {
$remote_get_args['post__not_in'] = $post_ids;
}

$remote_get_args['meta_query'] = [
[
Expand Down
185 changes: 185 additions & 0 deletions includes/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,73 @@ function register_rest_routes() {
'permission_callback' => '__return_true',
)
);

register_rest_route(
'wp/v2',
'distributor/list-pull-content',
array(
'methods' => 'POST',
'callback' => __NAMESPACE__ . '\get_pull_content',
'permission_callback' => 'is_user_logged_in',
'args' => get_pull_content_list_args(),
)
);
}

/**
* Set the accepted arguments for the pull content list endpoint
*
* @return array
*/
function get_pull_content_list_args() {
return array(
'exclude' => array(
'description' => esc_html__( 'Ensure result set excludes specific IDs.', 'distributor' ),
'type' => 'array',
'items' => array(
'type' => 'integer',
),
'default' => array(),
),
'page' => array(
'description' => esc_html__( 'Current page of the collection.', 'distributor' ),
'type' => 'integer',
'default' => 1,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
'minimum' => 1,
),
'per_page' => array(
'description' => esc_html__( 'Maximum number of items to be returned in result set.', 'distributor' ),
'type' => 'integer',
'default' => 10,
'minimum' => 1,
'maximum' => 100,
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
),
'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',
),
'search' => array(
'description' => esc_html__( 'Limit results to those matching a string.', 'distributor' ),
'type' => 'string',
'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' ) ),
'type' => 'string',
),
),
);
}

/**
Expand Down Expand Up @@ -338,6 +405,124 @@ function check_post_types_permissions() {
return $response;
}

/**
* Get a list of content to show on the Pull screen
*
* @param \WP_Rest_Request $request API request arguments
* @return \WP_REST_Response|\WP_Error
*/
function get_pull_content( $request ) {
$args = [
'posts_per_page' => isset( $request['per_page'] ) ? $request['per_page'] : 10,
'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' ),
];

if ( ! empty( $request['search'] ) ) {
$args['s'] = urldecode( $request['search'] );
}

if ( ! empty( $request['exclude'] ) ) {
$args['post__not_in'] = $request['exclude'];
}

$query = new \WP_Query( $args, $request );

if ( empty( $query->posts ) ) {
return rest_ensure_response( array() );
}

$page = (int) $args['paged'];
$total_posts = $query->found_posts;

$max_pages = ceil( $total_posts / (int) $query->query_vars['posts_per_page'] );

if ( $page > $max_pages && $total_posts > 0 ) {
return new \WP_Error(
'rest_post_invalid_page_number',
esc_html__( 'The page number requested is larger than the number of pages available.', 'distributor' ),
array( 'status' => 400 )
);
}

$formatted_posts = array();
foreach ( $query->posts as $post ) {
if ( ! check_read_permission( $post ) ) {
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,
);
}

$response = rest_ensure_response( $formatted_posts );

$response->header( 'X-WP-Total', (int) $total_posts );
$response->header( 'X-WP-TotalPages', (int) $max_pages );

return $response;
}

/**
* Checks if a post can be read.
*
* Copied from WordPress core.
*
* @param \WP_Post $post Post object.
* @return bool
*/
function check_read_permission( $post ) {
// Validate the post type.
$post_type = \get_post_type_object( $post->post_type );

if ( empty( $post_type ) || empty( $post_type->show_in_rest ) ) {
return false;
}

// Is the post readable?
if ( 'publish' === $post->post_status || \current_user_can( 'read_post', $post->ID ) ) {
return true;
}

$post_status_obj = \get_post_status_object( $post->post_status );
if ( $post_status_obj && $post_status_obj->public ) {
return true;
}

// Can we read the parent if we're inheriting?
if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
$parent = \get_post( $post->post_parent );
if ( $parent ) {
return check_read_permission( $parent );
}
}

/*
* If there isn't a parent, but the status is set to inherit, assume
* it's published (as per get_post_status()).
*/
if ( 'inherit' === $post->post_status ) {
return true;
}

return false;
}

/**
* Register push errors field so we can send errors over the REST API.
*/
Expand Down