diff --git a/admin/apple-actions/class-api-action.php b/admin/apple-actions/class-api-action.php index 9c90a216..fce96fbe 100644 --- a/admin/apple-actions/class-api-action.php +++ b/admin/apple-actions/class-api-action.php @@ -88,4 +88,17 @@ protected function is_api_configuration_valid() { return true; } + + /** + * Resets the API postmeta for a given post ID. + * + * @param int $post_id The post ID to reset. + */ + protected function delete_post_meta( $post_id ): void { + delete_post_meta( $post_id, 'apple_news_api_id' ); + delete_post_meta( $post_id, 'apple_news_api_revision' ); + delete_post_meta( $post_id, 'apple_news_api_created_at' ); + delete_post_meta( $post_id, 'apple_news_api_modified_at' ); + delete_post_meta( $post_id, 'apple_news_api_share_url' ); + } } diff --git a/admin/apple-actions/index/class-delete.php b/admin/apple-actions/index/class-delete.php index fcb7f44a..688e2419 100644 --- a/admin/apple-actions/index/class-delete.php +++ b/admin/apple-actions/index/class-delete.php @@ -79,14 +79,11 @@ private function delete() { * @param int $post_id The post ID from WordPress. */ do_action( 'apple_news_before_delete', $remote_id, $this->id ); + $this->get_api()->delete_article( $remote_id ); // Delete the API references and mark as deleted. - delete_post_meta( $this->id, 'apple_news_api_id' ); - delete_post_meta( $this->id, 'apple_news_api_revision' ); - delete_post_meta( $this->id, 'apple_news_api_created_at' ); - delete_post_meta( $this->id, 'apple_news_api_modified_at' ); - delete_post_meta( $this->id, 'apple_news_api_share_url' ); + $this->delete_post_meta( $this->id ); update_post_meta( $this->id, 'apple_news_api_deleted', time() ); // Clear the cache for post status. diff --git a/admin/apple-actions/index/class-get.php b/admin/apple-actions/index/class-get.php index ffdc06b2..e1c64895 100644 --- a/admin/apple-actions/index/class-get.php +++ b/admin/apple-actions/index/class-get.php @@ -21,10 +21,9 @@ class Get extends API_Action { /** - * Current content ID being retrieved. + * Post ID of the content being retrieved. * * @var int - * @access private */ private $id; @@ -32,8 +31,7 @@ class Get extends API_Action { * Constructor. * * @param \Apple_Exporter\Settings $settings Settings in use during this run. - * @param int $id Current content ID being retrieved. - * @access public + * @param int $id Post ID of the content being retrieved. */ public function __construct( $settings, $id ) { parent::__construct( $settings ); @@ -43,18 +41,28 @@ public function __construct( $settings, $id ) { /** * Get the post data from Apple News. * - * @access public - * @return object + * @return object|null */ public function perform() { // Ensure we have a valid ID. $apple_id = get_post_meta( $this->id, 'apple_news_api_id', true ); + if ( empty( $apple_id ) ) { return null; } // Get the article from the API. - $article = $this->get_api()->get_article( $apple_id ); + try { + $article = $this->get_api()->get_article( $apple_id ); + } catch ( \Apple_Push_API\Request\Request_Exception $e ) { + $article = $e->getMessage(); + + // Reset the API postmeta if the article is deleted in Apple News. + if ( is_string( $article ) && str_contains( $article, 'NOT_FOUND (keyPath articleId)' ) ) { + $this->delete_post_meta( $this->id ); + } + } + if ( empty( $article->data ) ) { return null; } @@ -67,7 +75,6 @@ public function perform() { * * @param string $key The key to look up in the data. * @param string $default Optional. The default value to fall back to. Defaults to null. - * @access public * @return mixed */ public function get_data( $key, $default = null ) { diff --git a/includes/REST/apple-news-get-published-state.php b/includes/REST/apple-news-get-published-state.php index 7e21725e..35ef147e 100644 --- a/includes/REST/apple-news-get-published-state.php +++ b/includes/REST/apple-news-get-published-state.php @@ -7,24 +7,10 @@ namespace Apple_News\REST; -/** - * Get API response. - * - * @param array $data data from query args. - * @return array updated response. - */ -function get_published_state_response( $data ) { - $response = []; - - // Ensure Apple News is first initialized. - \Apple_News::has_uninitialized_error(); - - if ( ! empty( get_current_user_id() ) ) { - $response['publishState'] = \Admin_Apple_News::get_post_status( $data['id'] ); - } - - return $response; -} +use WP_Error; +use WP_REST_Request; +use WP_REST_Response; +use WP_REST_Server; /** * Initialize this REST Endpoint. @@ -32,15 +18,49 @@ function get_published_state_response( $data ) { add_action( 'rest_api_init', function () { - // Register route count argument. register_rest_route( 'apple-news/v1', '/get-published-state/(?P\d+)', [ - 'methods' => 'GET', + 'methods' => WP_REST_Server::READABLE, 'callback' => __NAMESPACE__ . '\get_published_state_response', 'permission_callback' => '__return_true', - ] + 'schema' => [ + 'description' => __( 'Get the published state of a post.', 'apple-news' ), + 'type' => 'object', + 'properties' => [ + 'publishState' => [ + 'type' => 'string', + 'description' => __( 'The published state of the post.', 'apple-news' ), + ], + ], + ], + ], ); } ); + +/** + * Get the published state of a post. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ +function get_published_state_response( $request ): WP_REST_Response|WP_Error { + $id = $request->get_param( 'id' ); + + // Ensure Apple News is first initialized. + $retval = \Apple_News::has_uninitialized_error(); + + if ( is_wp_error( $retval ) ) { + return $retval; + } + + $response = []; + + if ( ! empty( get_current_user_id() ) ) { + $response['publishState'] = \Admin_Apple_News::get_post_status( $id ); + } + + return rest_ensure_response( $response ); +} diff --git a/includes/apple-push-api/request/class-request.php b/includes/apple-push-api/request/class-request.php index b09af893..1c29a018 100644 --- a/includes/apple-push-api/request/class-request.php +++ b/includes/apple-push-api/request/class-request.php @@ -240,6 +240,7 @@ private function parse_response( $response, $json = true, $type = 'post', $meta if ( json_last_error() !== JSON_ERROR_NONE ) { throw new Request_Exception( esc_html( __( 'Unable to decode JSON from the response:', 'apple-news' ) ) ); } + if ( ! empty( $response_decoded->errors ) && is_array( $response_decoded->errors ) ) { $message = ''; $messages = []; @@ -408,7 +409,7 @@ private function request( $verb, $url, $data = [] ) { } // Parse the response. - $response = $this->parse_response( + return $this->parse_response( $response, true, strtolower( $verb ), @@ -417,8 +418,6 @@ private function request( $verb, $url, $data = [] ) { ! empty( $data['article'] ) ? $data['article'] : '', 'POST' === $verb ? $this->mime_builder->get_debug_content( $args ) : '' ); - - return $response; } /** diff --git a/includes/class-apple-news.php b/includes/class-apple-news.php index c19955f5..b99e13a0 100644 --- a/includes/class-apple-news.php +++ b/includes/class-apple-news.php @@ -340,6 +340,7 @@ public static function is_initialized(): bool { self::$is_initialized = $has_api_settings || $has_api_config; } + return self::$is_initialized; } diff --git a/tests/admin/apple-actions/index/test-class-delete.php b/tests/admin/apple-actions/index/test-class-delete.php index b86c4198..ae647bc5 100644 --- a/tests/admin/apple-actions/index/test-class-delete.php +++ b/tests/admin/apple-actions/index/test-class-delete.php @@ -6,8 +6,6 @@ * @subpackage Tests */ -use Apple_Actions\Index\Delete; - /** * A class to test the functionality of the Apple_Actions\Index\Delete class. * diff --git a/tests/admin/apple-actions/index/test-class-get.php b/tests/admin/apple-actions/index/test-class-get.php new file mode 100644 index 00000000..5164e95f --- /dev/null +++ b/tests/admin/apple-actions/index/test-class-get.php @@ -0,0 +1,99 @@ +post->create(); + $action = new Apple_Actions\Index\Get( $this->settings, $post_id ); + + $this->assertNull( $action->perform() ); + } + + /** + * Test the behavior of the get action with an API ID assigned to the post. + */ + public function test_get_action(): void { + $api_id = 'def456'; + $post_id = self::factory()->post + ->with_meta( [ 'apple_news_api_id' => $api_id ] ) + ->create(); + + $response = $this->fake_article_response( [ 'id' => $api_id ] ); + + // Fake the API response for the GET request. + $this->add_http_response( + verb: 'GET', + url: 'https://news-api.apple.com/articles/' . $api_id, + body: wp_json_encode( $response ), + response: [ + 'code' => 200, + 'message' => 'OK', + ] + ); + + $action = new Apple_Actions\Index\Get( $this->settings, $post_id ); + $data = $action->perform(); + + $this->assertNotEmpty( $data ); + $this->assertSame( $api_id, $data->data->id ); + $this->assertSame( $response['data']['createdAt'], $data->data->createdAt ); + $this->assertSame( $response['data']['modifiedAt'], $data->data->modifiedAt ); + $this->assertSame( $response['data']['shareUrl'], $data->data->shareUrl ); + $this->assertSame( $response['data']['revision'], $data->data->revision ); + $this->assertSame( $response['data']['type'], $data->data->type ); + } + + /** + * Test the behavior of the get action with a deleted Apple News article assigned to the post. + */ + public function test_get_deleted_article(): void { + $api_id = 'def456'; + $post_id = self::factory()->post->create(); + $action = new Apple_Actions\Index\Get( $this->settings, $post_id ); + + $this->assertNull( $action->perform() ); + + add_post_meta( $post_id, 'apple_news_api_id', $api_id ); + + // Fake the API response for the GET request. + $this->add_http_response( + verb: 'GET', + url: 'https://news-api.apple.com/articles/' . $api_id, + body: wp_json_encode( + [ + 'errors' => [ + [ + 'code' => 'NOT_FOUND', + 'keyPath' => [ 'articleId' ], + 'value' => $api_id, + ], + ], + ] + ), + response: [ + 'code' => 404, + 'message' => 'Not Found', + ] + ); + + $action = new Apple_Actions\Index\Get( $this->settings, $post_id ); + + $this->assertNull( $action->perform() ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_id', true ) ); + } +} diff --git a/tests/admin/apple-actions/index/test-class-push.php b/tests/admin/apple-actions/index/test-class-push.php index e25f3819..cf6593f4 100644 --- a/tests/admin/apple-actions/index/test-class-push.php +++ b/tests/admin/apple-actions/index/test-class-push.php @@ -291,7 +291,6 @@ public function test_update() { $this->assertEquals( null, get_post_meta( $post->ID, 'apple_news_api_deleted', true ) ); // Try to sync the post again, and verify that it bails out before attempting the sync. - $exception = false; try { $this->get_request_for_post( $post->ID ); } catch ( Action_Exception $e ) { diff --git a/tests/admin/test-class-admin-apple-index-page.php b/tests/admin/test-class-admin-apple-index-page.php index 62f2f1a9..d2d9b794 100644 --- a/tests/admin/test-class-admin-apple-index-page.php +++ b/tests/admin/test-class-admin-apple-index-page.php @@ -40,10 +40,10 @@ public function test_reset() { $index_page->page_router(); // Ensure values were deleted. - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_api_pending', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_api_async_in_progress', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_api_bundle', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_api_json', true ) ); - $this->assertEquals( false, get_post_meta( $post_id, 'apple_news_api_errors', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_pending', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_async_in_progress', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_bundle', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_json', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_errors', true ) ); } } diff --git a/tests/admin/test-class-admin-apple-meta-boxes.php b/tests/admin/test-class-admin-apple-meta-boxes.php index 81475aa3..f8b561dc 100644 --- a/tests/admin/test-class-admin-apple-meta-boxes.php +++ b/tests/admin/test-class-admin-apple-meta-boxes.php @@ -21,8 +21,8 @@ class Apple_News_Admin_Apple_Meta_Boxes_Test extends Apple_News_Testcase { */ public function test_save_no_auto_sync() { // Set API settings to not auto sync and to enable the meta box. - $this->settings->set( 'api_autosync', 'no' ); - $this->settings->set( 'show_metabox', 'yes' ); + $this->settings->__set( 'api_autosync', 'no' ); + $this->settings->__set( 'show_metabox', 'yes' ); // Create post. $post_id = $this->factory->post->create(); @@ -46,7 +46,7 @@ public function test_save_no_auto_sync() { // Create the meta box class and simulate a save. $meta_box = new Admin_Apple_Meta_Boxes( $this->settings ); - if ( 'yes' === $this->settings->get( 'show_metabox' ) ) { + if ( 'yes' === $this->settings->__get( 'show_metabox' ) ) { $meta_box->do_publish( $post_id, get_post( $post_id ) ); } @@ -66,8 +66,8 @@ public function test_save_no_auto_sync() { */ public function test_save_with_auto_sync() { // Set API settings to not auto sync and to enable the meta box. - $this->settings->set( 'api_autosync', 'yes' ); - $this->settings->set( 'show_metabox', 'yes' ); + $this->settings->__set( 'api_autosync', 'yes' ); + $this->settings->__set( 'show_metabox', 'yes' ); // Create post. $post_id = $this->factory->post->create(); @@ -91,7 +91,7 @@ public function test_save_with_auto_sync() { // Create the meta box class and simulate a save. $meta_box = new Admin_Apple_Meta_Boxes( $this->settings ); - if ( 'yes' === $this->settings->get( 'show_metabox' ) ) { + if ( 'yes' === $this->settings->__get( 'show_metabox' ) ) { $meta_box->do_publish( $post_id, get_post( $post_id ) ); } diff --git a/tests/admin/test-class-admin-apple-themes.php b/tests/admin/test-class-admin-apple-themes.php index 05224b40..eb01bdf9 100644 --- a/tests/admin/test-class-admin-apple-themes.php +++ b/tests/admin/test-class-admin-apple-themes.php @@ -8,8 +8,6 @@ * @subpackage Tests */ -use Apple_Exporter\Exporter; -use Apple_Exporter\Exporter_Content; use Apple_Exporter\Theme; /** diff --git a/tests/class-apple-news-testcase.php b/tests/class-apple-news-testcase.php index 5f2808b8..ea6bd0bd 100644 --- a/tests/class-apple-news-testcase.php +++ b/tests/class-apple-news-testcase.php @@ -285,12 +285,14 @@ protected function add_http_response( * user ID to the new user. Useful when testing functionality that requires * an administrator's credentials, such as adding unfiltered HTML to a post. */ - protected function become_admin() { - $user_id = self::factory()->user->create( [ 'role' => 'administrator' ] ); + protected function become_admin(): void { + $user = $this->create_user_with_role( 'administrator' ); + if ( function_exists( 'grant_super_admin' ) ) { - grant_super_admin( $user_id ); + grant_super_admin( $user->ID ); } - wp_set_current_user( $user_id ); + + wp_set_current_user( $user->ID ); } /** diff --git a/tests/admin/test-class-admin-rest.php b/tests/rest/test-class-rest-admin.php similarity index 81% rename from tests/admin/test-class-admin-rest.php rename to tests/rest/test-class-rest-admin.php index 51cf18ab..1e0ca0e8 100644 --- a/tests/admin/test-class-admin-rest.php +++ b/tests/rest/test-class-rest-admin.php @@ -1,6 +1,6 @@ actingAs( $this->create_user_with_role( 'editor' ) ); + $this->assertAuthenticated(); + // Create a test post and give it sample data for the API postmeta. - $user_id = self::factory()->user->create( [ 'role' => 'editor' ] ); - wp_set_current_user( $user_id ); $post_id = self::factory()->post->create(); add_post_meta( $post_id, 'apple_news_api_created_at', 'abc123' ); - add_post_meta( $post_id, 'apple_news_api_id', 'def456' ); + add_post_meta( $post_id, 'apple_news_api_id', 'def456666' ); add_post_meta( $post_id, 'apple_news_api_modified_at', 'ghi789' ); add_post_meta( $post_id, 'apple_news_api_revision', 'jkl123' ); add_post_meta( $post_id, 'apple_news_api_share_url', 'mno456' ); @@ -49,7 +51,7 @@ public function test_rest_overwrite_api_data() { // Ensure that the API postmeta was _not_ reset by the REST request. $this->assertEquals( 'abc123', get_post_meta( $post_id, 'apple_news_api_created_at', true ) ); - $this->assertEquals( 'def456', get_post_meta( $post_id, 'apple_news_api_id', true ) ); + $this->assertEquals( 'def456666', get_post_meta( $post_id, 'apple_news_api_id', true ) ); $this->assertEquals( 'ghi789', get_post_meta( $post_id, 'apple_news_api_modified_at', true ) ); $this->assertEquals( 'jkl123', get_post_meta( $post_id, 'apple_news_api_revision', true ) ); $this->assertEquals( 'mno456', get_post_meta( $post_id, 'apple_news_api_share_url', true ) ); diff --git a/tests/rest/test-class-rest-post-published-state.php b/tests/rest/test-class-rest-post-published-state.php new file mode 100644 index 00000000..183dcff3 --- /dev/null +++ b/tests/rest/test-class-rest-post-published-state.php @@ -0,0 +1,104 @@ +assertFalse( Apple_News::is_initialized() ); + + $post_id = self::factory()->post->create(); + + $this->get( rest_url( '/apple-news/v1/get-published-state/' . $post_id ) ) + ->assertStatus( 400 ) + ->assertJsonPath( 'message', 'You must enter your API information on the settings page before using Publish to Apple News.' ); + } + + /** + * Test the REST endpoint for getting the published state of a post when not authenticated. + */ + public function test_get_post_published_state_unauthenticated(): void { + $api_id = 'def456'; + + // Create a test post and give it sample data for the API postmeta. + $post_id = self::factory()->post->create(); + add_post_meta( $post_id, 'apple_news_api_created_at', 'abc123' ); + add_post_meta( $post_id, 'apple_news_api_id', $api_id ); + add_post_meta( $post_id, 'apple_news_api_modified_at', 'ghi789' ); + add_post_meta( $post_id, 'apple_news_api_revision', 'jkl123' ); + add_post_meta( $post_id, 'apple_news_api_share_url', 'mno456' ); + + $this->get( rest_url( '/apple-news/v1/get-published-state/' . $post_id ) ) + ->assertOk() + ->assertJsonPathMissing( 'publishState' ); + + // Ensure that the API postmeta was _not_ reset by the REST request. + $this->assertEquals( 'abc123', get_post_meta( $post_id, 'apple_news_api_created_at', true ) ); + $this->assertEquals( $api_id, get_post_meta( $post_id, 'apple_news_api_id', true ) ); + $this->assertEquals( 'ghi789', get_post_meta( $post_id, 'apple_news_api_modified_at', true ) ); + $this->assertEquals( 'jkl123', get_post_meta( $post_id, 'apple_news_api_revision', true ) ); + $this->assertEquals( 'mno456', get_post_meta( $post_id, 'apple_news_api_share_url', true ) ); + } + + /** + * Test the REST endpoint for getting the published state of a post when authenticated. + */ + public function test_get_post_published_state_of_an_invalid_id_when_authenticated(): void { + $api_id = 'def456'; + + // Create a test post and give it sample data for the API postmeta. + $post_id = self::factory()->post->create(); + add_post_meta( $post_id, 'apple_news_api_created_at', 'abc123' ); + add_post_meta( $post_id, 'apple_news_api_id', $api_id ); + add_post_meta( $post_id, 'apple_news_api_modified_at', 'ghi789' ); + add_post_meta( $post_id, 'apple_news_api_revision', 'jkl123' ); + add_post_meta( $post_id, 'apple_news_api_share_url', 'mno456' ); + + $this->actingAs( $this->create_user_with_role( 'editor' ) ); + $this->assertAuthenticated(); + + // Fake the API response for the GET request. + $this->add_http_response( + verb: 'GET', + url: 'https://news-api.apple.com/articles/' . $api_id, + body: wp_json_encode( + [ + 'errors' => [ + [ + 'code' => 'NOT_FOUND', + 'keyPath' => [ 'articleId' ], + 'value' => $api_id, + ], + ], + ] + ), + response: [ + 'code' => 404, + 'message' => 'Not Found', + ] + ); + + $this->get( rest_url( '/apple-news/v1/get-published-state/' . $post_id ) ) + ->assertOk() + ->assertJsonPath( 'publishState', 'N/A' ); + + // Ensure that the API postmeta _was_ reset. + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_created_at', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_id', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_modified_at', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_revision', true ) ); + $this->assertEmpty( get_post_meta( $post_id, 'apple_news_api_share_url', true ) ); + } +}