From 1537bb429805444a6e2b94c6962b0c699eb94d57 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Wed, 4 Mar 2020 18:37:56 -0600 Subject: [PATCH 1/7] Refactor the Post by Email module * Deprecate the WP-AJAX endpoints (`jetpack_post_by_email_enable`, `jetpack_post_by_email_regenerate`, `jetpack_post_by_email_disable`) * Switch the "User Profile -> Post by Email" UI from WP-AJAX to REST API * Moving REST requests proxying from `Jetpack_Core_API_Data` to the `post-by-email` module (as suggested by the @todo tag) --- ...lass.jetpack-core-api-module-endpoints.php | 52 +------ modules/post-by-email.php | 133 +++++++++++++++--- modules/post-by-email/post-by-email.js | 75 ++++++---- 3 files changed, 164 insertions(+), 96 deletions(-) diff --git a/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php b/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php index 3ade1c3497737..2a00ba97dc023 100644 --- a/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php +++ b/_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php @@ -681,24 +681,7 @@ public function update_data( $request ) { break; case 'post_by_email_address': - if ( 'create' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.createPostByEmailAddress', - esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' ) - ); - } elseif ( 'regenerate' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.regeneratePostByEmailAddress', - esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' ) - ); - } elseif ( 'delete' == $value ) { - $result = $this->_process_post_by_email( - 'jetpack.deletePostByEmailAddress', - esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' ) - ); - } else { - $result = false; - } + $result = Jetpack_Post_By_Email::init()->process_api_request( $value ); // If we got an email address (create or regenerate) or 1 (delete), consider it done. if ( is_string( $result ) && preg_match( '/[a-z0-9]+@post.wordpress.com/', $result ) ) { @@ -1206,39 +1189,6 @@ static function has_business_address_widget( $sidebar ) { return false; } - /** - * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address. - * @todo: When all settings are updated to use endpoints, move this to the Post by Email module and replace __process_ajax_proxy_request. - * - * @since 4.3.0 - * - * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address. - * @param string $error Error message to return. - * - * @return array - */ - private function _process_post_by_email( $endpoint, $error ) { - if ( ! current_user_can( 'edit_posts' ) ) { - return array( 'message' => $error ); - } - - $this->xmlrpc->query( $endpoint ); - - if ( $this->xmlrpc->isError() ) { - return array( 'message' => $error ); - } - - $response = $this->xmlrpc->getResponse(); - if ( empty( $response ) ) { - return array( 'message' => $error ); - } - - // Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. - update_option( 'post_by_email_address' . get_current_user_id(), $response ); - - return $response; - } - /** * Check if user is allowed to perform the update. * diff --git a/modules/post-by-email.php b/modules/post-by-email.php index 851303c12b626..fd0b800228b5b 100644 --- a/modules/post-by-email.php +++ b/modules/post-by-email.php @@ -12,6 +12,8 @@ * Additional Search Queries: post by email, email */ +use Automattic\Jetpack\Connection\Manager as Connection_Manager; + add_action( 'jetpack_modules_loaded', array( 'Jetpack_Post_By_Email', 'init' ) ); Jetpack::enable_module_configurable( __FILE__ ); @@ -46,11 +48,7 @@ function action_init() { function profile_scripts() { wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email/post-by-email.js', __FILE__ ), array( 'jquery' ) ); wp_localize_script( 'post-by-email', 'pbeVars', array( - 'nonces' => array( - 'enable' => wp_create_nonce( 'jetpack.createPostByEmailAddress' ), - 'regenerate' => wp_create_nonce( 'jetpack.regeneratePostByEmailAddress' ), - 'disable' => wp_create_nonce( 'jetpack.deletePostByEmailAddress' ), - ), + 'rest_nonce' => wp_create_nonce( 'wp_rest' ), )); wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email/post-by-email.css', __FILE__ ) ); wp_style_add_data( 'post-by-email', 'jetpack-inline', true ); @@ -59,7 +57,8 @@ function profile_scripts() { } function check_user_connection() { - $user_token = Jetpack_Data::get_access_token( get_current_user_id() ); + $user_token = ( new Connection_Manager() )->get_access_token( get_current_user_id() ); + $is_user_connected = $user_token && !is_wp_error( $user_token ); // If the user is already connected via Jetpack, then we're good @@ -129,9 +128,8 @@ function user_profile() { } function get_post_by_email_address() { - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); + $xml = $this->init_rest_connection(); + $xml->query( 'jetpack.getPostByEmailAddress' ); if ( $xml->isError() ) @@ -144,34 +142,91 @@ function get_post_by_email_address() { return $response; } + /** + * Process the REST API request to modify the "Post by Email" settings. + * + * @param string $action Allowed values: 'create', 'regenerate', 'delete'. + * + * @return array|false + */ + public function process_api_request( $action ) { + $endpoint = null; + $error_message = esc_html__( 'Please try again later.', 'jetpack' ); + $result = false; + + switch ( $action ) { + case 'create': + $endpoint = 'jetpack.createPostByEmailAddress'; + $error_message = esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' ); + break; + case 'regenerate': + $endpoint = 'jetpack.regeneratePostByEmailAddress'; + $error_message = esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' ); + break; + case 'delete': + $endpoint = 'jetpack.deletePostByEmailAddress'; + $error_message = esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' ); + break; + } + + if ( $endpoint ) { + $result = $this->process_rest_proxy_request( $endpoint, $error_message ); + } + + return $result; + } + function create_post_by_email_address() { - self::__process_ajax_proxy_request( + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( 'jetpack.createPostByEmailAddress', __( 'Unable to create your Post By Email address. Please try again later.', 'jetpack' ) ); } function regenerate_post_by_email_address() { - self::__process_ajax_proxy_request( + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( 'jetpack.regeneratePostByEmailAddress', __( 'Unable to regenerate your Post By Email address. Please try again later.', 'jetpack' ) ); } function delete_post_by_email_address() { - self::__process_ajax_proxy_request( + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( 'jetpack.deletePostByEmailAddress', __( 'Unable to disable your Post By Email address. Please try again later.', 'jetpack' ) ); } /** - * Back end function to abstract the xmlrpc function calls to wpcom. + * The AJAX proxying method for backward compatibility. + * To be removed in the upcoming versions. + * + * @param string $endpoint Jetpack API endpoint. + * @param string $error_message Error message to be returned if something goes wrong. * - * @param $endpoint - * @param $error_message + * @deprecated */ function __process_ajax_proxy_request( $endpoint, $error_message ) { // phpcs:ignore + $this->process_ajax_proxy_request( $endpoint, $error_message ); + } + + /** + * Back end function to abstract the xmlrpc function calls to wpcom. + * + * @param string $endpoint Jetpack API endpoint. + * @param string $error_message Error message to be returned if something goes wrong. + * + * @deprecated + */ + private function process_ajax_proxy_request( $endpoint, $error_message ) { + _deprecated_function( __METHOD__, 'jetpack-8.4', '_process_rest_proxy_request' ); + if ( ! current_user_can( 'edit_posts' ) ) { wp_send_json_error( $error_message ); } @@ -179,9 +234,7 @@ function __process_ajax_proxy_request( $endpoint, $error_message ) { // phpcs:ig wp_send_json_error( $error_message ); } - $xml = new Jetpack_IXR_Client( array( - 'user_id' => get_current_user_id(), - ) ); + $xml = $this->init_rest_connection(); $xml->query( $endpoint ); if ( $xml->isError() ) { @@ -198,4 +251,48 @@ function __process_ajax_proxy_request( $endpoint, $error_message ) { // phpcs:ig wp_send_json_success( $response ); } + + /** + * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address. + * + * @since 4.3.0 + * + * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address. + * @param string $error Error message to return. + * + * @return array + */ + private function process_rest_proxy_request( $endpoint, $error ) { + if ( ! current_user_can( 'edit_posts' ) ) { + return array( 'message' => $error ); + } + + $xml = $this->init_rest_connection(); + + $xml->query( $endpoint ); + + if ( $xml->isError() ) { + return array( 'message' => $error ); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + return array( 'message' => $error ); + } + + // Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. + update_option( 'post_by_email_address' . get_current_user_id(), $response ); + + return $response; + } + + /** + * Initialize the IXR client + * + * @return Jetpack_IXR_Client + */ + private function init_rest_connection() { + return new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); + } + } diff --git a/modules/post-by-email/post-by-email.js b/modules/post-by-email/post-by-email.js index a8141684026db..2e66629c39d44 100644 --- a/modules/post-by-email/post-by-email.js +++ b/modules/post-by-email/post-by-email.js @@ -1,4 +1,4 @@ -/* global jetpack_post_by_email:true, ajaxurl, pbeVars */ +/* global jetpack_post_by_email:true, pbeVars */ ( function( $ ) { var $pbeDisable, @@ -22,26 +22,24 @@ $pbeError.fadeOut(); $pbeSpinner.fadeIn(); - var data = { - action: 'jetpack_post_by_email_enable', - pbe_nonce: pbeVars.nonces.enable, - }; - - $.post( ajaxurl, data, jetpack_post_by_email.handle_enabled ); + jetpack_post_by_email.send_request( + { post_by_email_address: 'create' }, + jetpack_post_by_email.handle_enabled + ); }, handle_enabled: function( response ) { $pbeRegenerate.removeAttr( 'disabled' ); $pbeDisable.removeAttr( 'disabled' ); - if ( response.success ) { + if ( response.code === 'success' ) { $pbeEnable.fadeOut( 400, function() { $pbeEnable.removeAttr( 'disabled' ); - $pbeEmail.val( response.data ); + $pbeEmail.val( response.post_by_email_address ); $pbeInfo.fadeIn(); } ); } else { - $pbeError.text( response.data ); + $pbeError.text( jetpack_post_by_email.parse_error_message( response ) ); $pbeError.fadeIn(); $pbeEnable.removeAttr( 'disabled' ); } @@ -55,22 +53,20 @@ $pbeError.fadeOut(); $pbeSpinner.fadeIn(); - var data = { - action: 'jetpack_post_by_email_regenerate', - pbe_nonce: pbeVars.nonces.regenerate, - }; - - $.post( ajaxurl, data, jetpack_post_by_email.handle_regenerated ); + jetpack_post_by_email.send_request( + { post_by_email_address: 'regenerate' }, + jetpack_post_by_email.handle_regenerated + ); }, handle_regenerated: function( response ) { - if ( response.success ) { + if ( response.code === 'success' ) { $pbeEmailWrapper.fadeOut( 400, function() { - $pbeEmail.val( response.data ); + $pbeEmail.val( response.post_by_email_address ); $pbeEmailWrapper.fadeIn(); } ); } else { - $pbeError.text( response.data ); + $pbeError.text( jetpack_post_by_email.parse_error_message( response ) ); $pbeError.fadeIn(); } @@ -85,16 +81,14 @@ $pbeError.fadeOut(); $pbeSpinner.fadeIn(); - var data = { - action: 'jetpack_post_by_email_disable', - pbe_nonce: pbeVars.nonces.disable, - }; - - $.post( ajaxurl, data, jetpack_post_by_email.handle_disabled ); + jetpack_post_by_email.send_request( + { post_by_email_address: 'delete' }, + jetpack_post_by_email.handle_disabled + ); }, handle_disabled: function( response ) { - if ( response.success ) { + if ( response.code === 'success' ) { $pbeEnable.removeAttr( 'disabled' ); $pbeInfo.fadeOut( 400, function() { $pbeRegenerate.removeAttr( 'disabled' ); @@ -105,12 +99,39 @@ $pbeRegenerate.removeAttr( 'disabled' ); $pbeDisable.removeAttr( 'disabled' ); - $pbeError.text( response.data ); + $pbeError.text( jetpack_post_by_email.parse_error_message( response ) ); $pbeError.fadeIn(); } $pbeSpinner.fadeOut(); }, + + send_request: function( data, callback ) { + jQuery + .ajax( { + url: '/wp-json/jetpack/v4/settings/', + method: 'post', + beforeSend: function( xhr ) { + xhr.setRequestHeader( 'X-WP-Nonce', pbeVars.rest_nonce ); + }, + data: JSON.stringify( data ), + contentType: 'application/json', + dataType: 'json', + } ) + .always( callback ); + }, + + parse_error_message: function( response ) { + if ( response.responseText ) { + var data = JSON.parse( response.responseText ); + + if ( data.message ) { + return data.message.replace( /^.*?:/, '' ); + } + } + + return ''; + }, }; $( function() { From 949cb019907daa00cfbf7ddcfa41eae7bca5fb7f Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Sun, 8 Mar 2020 19:53:49 -0500 Subject: [PATCH 2/7] Integration tests for Post by Email REST API. Currently class `WP_Test_Post_By_Email_API` tests one API method: `post_by_email_address => create`. In the future tests for `regenerate` and `delete` will be added. REST API authentication mocking code is mostly copied form `WP_Test_Jetpack_REST_API_Authentication`. Test API requests are not being sent to the Jetpack API, instead the Jetpack API request is verified, and the response is mocked. This allows us to test the functionality internally. --- phpunit.xml.dist | 3 + .../test-class.post-by-email-api.php | 243 ++++++++++++++++++ 2 files changed, 246 insertions(+) create mode 100644 tests/php/modules/post-by-email/test-class.post-by-email-api.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 4d5b17fec1bbe..477dfa5bff76f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -36,6 +36,9 @@ tests/php/modules/publicize + + tests/php/modules/post-by-email + tests/php/modules/related-posts diff --git a/tests/php/modules/post-by-email/test-class.post-by-email-api.php b/tests/php/modules/post-by-email/test-class.post-by-email-api.php new file mode 100644 index 0000000000000..a0e03cfd0946b --- /dev/null +++ b/tests/php/modules/post-by-email/test-class.post-by-email-api.php @@ -0,0 +1,243 @@ +user->create( + array( + 'role' => 'administrator', + ) + ); + } + + /** + * Setup the environment for a test. + */ + public function setUp() { + parent::setUp(); + + foreach ( self::$save_server_keys as $key ) { + if ( isset( $_SERVER[ $key ] ) ) { + $this->server_values[ $key ] = $_SERVER[ $key ]; + } else { + unset( $this->server_values[ $key ] ); + } + } + + $_GET['_for'] = 'jetpack'; + + add_filter( 'rest_pre_dispatch', array( $this, 'rest_pre_dispatch' ), 100, 2 ); + } + + /** + * Reset the environment to its original state after the test. + */ + public function tearDown() { + parent::tearDown(); + + remove_filter( 'rest_pre_dispatch', array( $this, 'rest_pre_dispatch' ), 100 ); + + unset( $_SERVER['HTTP_CONTENT_TYPE'], $_GET['_for'], $_GET['token'], $_GET['timestamp'], $_GET['nonce'], $_GET['body-hash'], $_GET['signature'] ); // phpcs:ignore + + foreach ( self::$save_server_keys as $key ) { + if ( isset( $this->server_values[ $key ] ) ) { + $_SERVER[ $key ] = $this->server_values[ $key ]; + } else { + unset( $_SERVER[ $key ] ); + } + } + + wp_set_current_user( 0 ); + + unset( $GLOBALS['HTTP_RAW_POST_DATA'] ); + Jetpack::init()->HTTP_RAW_POST_DATA = null; + } + + /** + * Test the endpoint `post_by_email_address => create`. + */ + public function test_create() { + $_SERVER['HTTP_CONTENT_TYPE'] = 'application/json'; + + $body = wp_json_encode( array( 'post_by_email_address' => 'create' ) ); + $route = '/jetpack/v4/settings'; + + add_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); + add_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response' ), 10, 3 ); + + $this->request = new WP_REST_Request( 'POST', $route ); + $this->request->set_body( $body ); + $this->request->set_header( 'Content-Type', $_SERVER['HTTP_CONTENT_TYPE'] ); + + $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; + $_GET['timestamp'] = (string) time(); + $_GET['nonce'] = 'testing123'; + $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); + + // @codingStandardsIgnoreStart + $dataset = array( + $_GET['token'], + $_GET['timestamp'], + $_GET['nonce'], + $_GET['body-hash'], + 'POST', + 'example.org', + '80', + $route, + 'qstest=yep', + ); + // @codingStandardsIgnoreEnd + + $_GET['signature'] = base64_encode( // phpcs:ignore + hash_hmac( + 'sha1', + implode( "\n", $dataset ) . "\n", + 'secret', + true + ) + ); + + $response = $this->server->dispatch( $this->request ); + + $this->assertEquals( 'success', $response->data['code'] ); + $this->assertEquals( self::SAMPLE_EMAIL, $response->data['post_by_email_address'] ); + $this->assertEquals( 200, $response->status ); + + remove_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); + remove_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response' ), 10 ); + + $this->assertTrue( $this->request_validated, "Method 'mock_jetpack_api_response' was skipped, failed to validate the request" ); + } + + /** + * Most the user token. + * + * @return array + */ + public function mock_jetpack_private_options() { + $user_tokens = array(); + $user_tokens[ self::$admin_id ] = 'pretend_this_is_valid.secret.' . self::$admin_id; + + return array( + 'user_tokens' => $user_tokens, + ); + } + + /** + * Ensures that these tests pass through Jetpack::wp_rest_authenticate, + * because WP_REST_Server::dispatch doesn't call any auth logic (in a real + * request, this would all happen earlier). + * + * @param mixed $result Response to replace the requested version with. + * @param WP_Rest_Server $server Server instance. + * + * @return WP_Error|null WP_Error indicates unsuccessful login, null indicates successful or no authentication provided. + */ + public function rest_pre_dispatch( $result, $server ) { + // Reset Jetpack::xmlrpc_verification saved state. + $jetpack = Jetpack::init(); + $jetpack->reset_saved_auth_state(); + + // Set POST body for Jetpack::verify_xml_rpc_signature. + $GLOBALS['HTTP_RAW_POST_DATA'] = $this->request->get_body(); // phpcs:ignore + + // Set host and URL for Jetpack_Signature::sign_current_request. + $_SERVER['HTTP_HOST'] = 'example.org'; + $_SERVER['REQUEST_URI'] = $this->request->get_route() . '?qstest=yep'; + $_SERVER['REQUEST_METHOD'] = $this->request->get_method(); + $user_id = apply_filters( 'determine_current_user', false ); + + if ( $user_id ) { + wp_set_current_user( $user_id ); + } + $auth = $server->check_authentication( null ); + if ( true === $auth ) { + return $result; + } + + return $auth; + } + + /** + * Validate the Jetpack API request and mock the response. + * + * @param false|array|WP_Error $response Whether to preempt an HTTP request's return value. Default false. + * @param array $args HTTP request arguments. + * @param string $url The request URL. + * + * @return array + */ + public function mock_jetpack_api_response( $response, $args, $url ) { + $this->assertEquals( 'POST', $args['method'] ); + $this->assertStringContainsStringIgnoringCase( 'jetpack.createPostByEmailAddress', $args['body'] ); + $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); + $this->request_validated = true; + + return array( + 'headers' => array(), + 'body' => '' . self::SAMPLE_EMAIL . '', + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + ); + } + +} From ddc47eaed4cd9eaff94802ab5f09cb01f9955142 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Thu, 26 Mar 2020 13:56:13 -0500 Subject: [PATCH 3/7] =?UTF-8?q?Post=20by=20Email:=20Unit=20tests=20for=20?= =?UTF-8?q?=E2=80=9Cregenerate=E2=80=9D=20and=20=E2=80=9Cdelete=E2=80=9D?= =?UTF-8?q?=20API=20endpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The commit adds two unit tests for the “post-by-email” module: - `jetpack.regeneratePostByEmailAddress` - `jetpack.deletePostByEmailAddress` I also made some minor refactoring in the test class `WP_Test_Post_By_Email_API`. --- .../test-class.post-by-email-api.php | 183 +++++++++++++----- 1 file changed, 139 insertions(+), 44 deletions(-) diff --git a/tests/php/modules/post-by-email/test-class.post-by-email-api.php b/tests/php/modules/post-by-email/test-class.post-by-email-api.php index a0e03cfd0946b..6a87484dc8d2b 100644 --- a/tests/php/modules/post-by-email/test-class.post-by-email-api.php +++ b/tests/php/modules/post-by-email/test-class.post-by-email-api.php @@ -50,10 +50,12 @@ class WP_Test_Post_By_Email_API extends WP_Test_Jetpack_REST_Testcase { * * @var bool */ - private $request_validated = false; + private $request_validated; const SAMPLE_EMAIL = 'sample.email@post.wordpress.com'; + const PBE_API_ENDPOINT = '/jetpack/v4/settings'; + /** * Initialize the test class. * @@ -84,16 +86,29 @@ public function setUp() { $_GET['_for'] = 'jetpack'; add_filter( 'rest_pre_dispatch', array( $this, 'rest_pre_dispatch' ), 100, 2 ); + add_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); + + $_SERVER['HTTP_CONTENT_TYPE'] = 'application/json'; + + $this->request = new WP_REST_Request( 'POST', self::PBE_API_ENDPOINT ); + $this->request->set_header( 'Content-Type', $_SERVER['HTTP_CONTENT_TYPE'] ); + + $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; + $_GET['timestamp'] = (string) time(); + $_GET['nonce'] = 'testing123'; + + $this->request_validated = false; } /** * Reset the environment to its original state after the test. */ public function tearDown() { - parent::tearDown(); - + remove_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); remove_filter( 'rest_pre_dispatch', array( $this, 'rest_pre_dispatch' ), 100 ); + parent::tearDown(); + unset( $_SERVER['HTTP_CONTENT_TYPE'], $_GET['_for'], $_GET['token'], $_GET['timestamp'], $_GET['nonce'], $_GET['body-hash'], $_GET['signature'] ); // phpcs:ignore foreach ( self::$save_server_keys as $key ) { @@ -114,56 +129,47 @@ public function tearDown() { * Test the endpoint `post_by_email_address => create`. */ public function test_create() { - $_SERVER['HTTP_CONTENT_TYPE'] = 'application/json'; + add_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_create' ), 10, 3 ); - $body = wp_json_encode( array( 'post_by_email_address' => 'create' ) ); - $route = '/jetpack/v4/settings'; + $response = $this->rest_dispatch( 'create' ); - add_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); - add_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response' ), 10, 3 ); - - $this->request = new WP_REST_Request( 'POST', $route ); - $this->request->set_body( $body ); - $this->request->set_header( 'Content-Type', $_SERVER['HTTP_CONTENT_TYPE'] ); - - $_GET['token'] = 'pretend_this_is_valid:1:' . self::$admin_id; - $_GET['timestamp'] = (string) time(); - $_GET['nonce'] = 'testing123'; - $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); + $this->assertEquals( 'success', $response->data['code'] ); + $this->assertEquals( self::SAMPLE_EMAIL, $response->data['post_by_email_address'] ); + $this->assertEquals( 200, $response->status ); + $this->assertTrue( $this->request_validated, "Method 'mock_jetpack_api_response_create' was skipped, failed to validate the request" ); - // @codingStandardsIgnoreStart - $dataset = array( - $_GET['token'], - $_GET['timestamp'], - $_GET['nonce'], - $_GET['body-hash'], - 'POST', - 'example.org', - '80', - $route, - 'qstest=yep', - ); - // @codingStandardsIgnoreEnd + remove_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_create' ), 10 ); + } - $_GET['signature'] = base64_encode( // phpcs:ignore - hash_hmac( - 'sha1', - implode( "\n", $dataset ) . "\n", - 'secret', - true - ) - ); + /** + * Test the endpoint `post_by_email_address => regenerate`. + */ + public function test_regenerate() { + add_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_regenerate' ), 10, 3 ); - $response = $this->server->dispatch( $this->request ); + $response = $this->rest_dispatch( 'regenerate' ); $this->assertEquals( 'success', $response->data['code'] ); $this->assertEquals( self::SAMPLE_EMAIL, $response->data['post_by_email_address'] ); $this->assertEquals( 200, $response->status ); + $this->assertTrue( $this->request_validated, "Method 'mock_jetpack_api_response_regenerate' was skipped, failed to validate the request" ); - remove_filter( 'pre_option_jetpack_private_options', array( $this, 'mock_jetpack_private_options' ), 10 ); - remove_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response' ), 10 ); + remove_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_regenerate' ), 10 ); + } + + /** + * Test the endpoint `post_by_email_address => delete`. + */ + public function test_delete() { + add_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_delete' ), 10, 3 ); + + $response = $this->rest_dispatch( 'delete' ); + + $this->assertEquals( 'success', $response->data['code'] ); + $this->assertEquals( 200, $response->status ); + $this->assertTrue( $this->request_validated, "Method 'mock_jetpack_api_response_delete' was skipped, failed to validate the request" ); - $this->assertTrue( $this->request_validated, "Method 'mock_jetpack_api_response' was skipped, failed to validate the request" ); + remove_filter( 'pre_http_request', array( $this, 'mock_jetpack_api_response_delete' ), 10 ); } /** @@ -216,7 +222,7 @@ public function rest_pre_dispatch( $result, $server ) { } /** - * Validate the Jetpack API request and mock the response. + * Validate the "Create" Jetpack API request and mock the response. * * @param false|array|WP_Error $response Whether to preempt an HTTP request's return value. Default false. * @param array $args HTTP request arguments. @@ -224,7 +230,7 @@ public function rest_pre_dispatch( $result, $server ) { * * @return array */ - public function mock_jetpack_api_response( $response, $args, $url ) { + public function mock_jetpack_api_response_create( $response, $args, $url ) { $this->assertEquals( 'POST', $args['method'] ); $this->assertStringContainsStringIgnoringCase( 'jetpack.createPostByEmailAddress', $args['body'] ); $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); @@ -240,4 +246,93 @@ public function mock_jetpack_api_response( $response, $args, $url ) { ); } + /** + * Validate the "Regenerate" Jetpack API request and mock the response. + * + * @param false|array|WP_Error $response Whether to preempt an HTTP request's return value. Default false. + * @param array $args HTTP request arguments. + * @param string $url The request URL. + * + * @return array + */ + public function mock_jetpack_api_response_regenerate( $response, $args, $url ) { + $this->assertEquals( 'POST', $args['method'] ); + $this->assertStringContainsStringIgnoringCase( 'jetpack.regeneratePostByEmailAddress', $args['body'] ); + $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); + $this->request_validated = true; + + return array( + 'headers' => array(), + 'body' => '' . self::SAMPLE_EMAIL . '', + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + ); + } + + /** + * Validate the "Delete" Jetpack API request and mock the response. + * + * @param false|array|WP_Error $response Whether to preempt an HTTP request's return value. Default false. + * @param array $args HTTP request arguments. + * @param string $url The request URL. + * + * @return array + */ + public function mock_jetpack_api_response_delete( $response, $args, $url ) { + $this->assertEquals( 'POST', $args['method'] ); + $this->assertStringContainsStringIgnoringCase( 'jetpack.deletePostByEmailAddress', $args['body'] ); + $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); + $this->request_validated = true; + + return array( + 'headers' => array(), + 'body' => '1', + 'response' => array( + 'code' => 200, + 'message' => 'OK', + ), + ); + } + + /** + * Prepare and send the API request. + * + * @param string $action API action to be sent. + * + * @return WP_REST_Response + */ + private function rest_dispatch( $action ) { + $body = wp_json_encode( array( 'post_by_email_address' => $action ) ); + $this->request->set_body( $body ); + $_GET['body-hash'] = Jetpack::connection()->sha1_base64( $body ); + + // phpcs:disable WordPress.Security.NonceVerification + $dataset = array( + $_GET['token'], + $_GET['timestamp'], + $_GET['nonce'], + $_GET['body-hash'], + 'POST', + 'example.org', + '80', + self::PBE_API_ENDPOINT, + 'qstest=yep', + ); + // phpcs:enable + + //phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions + $_GET['signature'] = base64_encode( + hash_hmac( + 'sha1', + implode( "\n", $dataset ) . "\n", + 'secret', + true + ) + ); + + return $this->server->dispatch( $this->request ); + } + } From 84a2f3b0c53f2a17ea1211b016fba114f03b26c6 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Thu, 26 Mar 2020 19:26:41 -0500 Subject: [PATCH 4/7] Post by Email: Switching to XMLHttpRequest. Rewriting the jQuery AJAX request on VanillaJS. --- modules/post-by-email/post-by-email.js | 49 ++++++++++++-------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/modules/post-by-email/post-by-email.js b/modules/post-by-email/post-by-email.js index 2e66629c39d44..f6374856396f8 100644 --- a/modules/post-by-email/post-by-email.js +++ b/modules/post-by-email/post-by-email.js @@ -48,10 +48,7 @@ }, regenerate: function() { - $pbeRegenerate.attr( 'disabled', 'disabled' ); - $pbeDisable.attr( 'disabled', 'disabled' ); - $pbeError.fadeOut(); - $pbeSpinner.fadeIn(); + jetpack_post_by_email.before_request(); jetpack_post_by_email.send_request( { post_by_email_address: 'regenerate' }, @@ -76,10 +73,7 @@ }, disable: function() { - $pbeRegenerate.attr( 'disabled', 'disabled' ); - $pbeDisable.attr( 'disabled', 'disabled' ); - $pbeError.fadeOut(); - $pbeSpinner.fadeIn(); + jetpack_post_by_email.before_request(); jetpack_post_by_email.send_request( { post_by_email_address: 'delete' }, @@ -107,31 +101,32 @@ }, send_request: function( data, callback ) { - jQuery - .ajax( { - url: '/wp-json/jetpack/v4/settings/', - method: 'post', - beforeSend: function( xhr ) { - xhr.setRequestHeader( 'X-WP-Nonce', pbeVars.rest_nonce ); - }, - data: JSON.stringify( data ), - contentType: 'application/json', - dataType: 'json', - } ) - .always( callback ); + var request = new XMLHttpRequest(); + request.open( 'POST', '/wp-json/jetpack/v4/settings/' ); + request.setRequestHeader( 'Content-Type', 'application/json' ); + request.setRequestHeader( 'X-WP-Nonce', pbeVars.rest_nonce ); + request.onreadystatechange = function() { + if ( this.readyState === XMLHttpRequest.DONE ) { + callback( JSON.parse( this.response ) ); + } + }; + request.send( JSON.stringify( data ) ); }, - parse_error_message: function( response ) { - if ( response.responseText ) { - var data = JSON.parse( response.responseText ); - - if ( data.message ) { - return data.message.replace( /^.*?:/, '' ); - } + parse_error_message: function( data ) { + if ( data.message ) { + return data.message.replace( /^.*?:/, '' ); } return ''; }, + + before_request: function() { + $pbeRegenerate.attr( 'disabled', 'disabled' ); + $pbeDisable.attr( 'disabled', 'disabled' ); + $pbeError.fadeOut(); + $pbeSpinner.fadeIn(); + }, }; $( function() { From 3de453c3f983c1b079d565ce379be70a47ea12a0 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Thu, 26 Mar 2020 21:10:05 -0500 Subject: [PATCH 5/7] Post by Email: Modifying assertions for compatibility with PHPUnit 5.7 --- .../modules/post-by-email/test-class.post-by-email-api.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/php/modules/post-by-email/test-class.post-by-email-api.php b/tests/php/modules/post-by-email/test-class.post-by-email-api.php index 6a87484dc8d2b..5495af671dff7 100644 --- a/tests/php/modules/post-by-email/test-class.post-by-email-api.php +++ b/tests/php/modules/post-by-email/test-class.post-by-email-api.php @@ -232,7 +232,7 @@ public function rest_pre_dispatch( $result, $server ) { */ public function mock_jetpack_api_response_create( $response, $args, $url ) { $this->assertEquals( 'POST', $args['method'] ); - $this->assertStringContainsStringIgnoringCase( 'jetpack.createPostByEmailAddress', $args['body'] ); + $this->assertNotFalse( strpos( $args['body'], 'jetpack.createPostByEmailAddress' ) ); $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); $this->request_validated = true; @@ -257,7 +257,7 @@ public function mock_jetpack_api_response_create( $response, $args, $url ) { */ public function mock_jetpack_api_response_regenerate( $response, $args, $url ) { $this->assertEquals( 'POST', $args['method'] ); - $this->assertStringContainsStringIgnoringCase( 'jetpack.regeneratePostByEmailAddress', $args['body'] ); + $this->assertNotFalse( strpos( $args['body'], 'jetpack.regeneratePostByEmailAddress' ) ); $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); $this->request_validated = true; @@ -282,7 +282,7 @@ public function mock_jetpack_api_response_regenerate( $response, $args, $url ) { */ public function mock_jetpack_api_response_delete( $response, $args, $url ) { $this->assertEquals( 'POST', $args['method'] ); - $this->assertStringContainsStringIgnoringCase( 'jetpack.deletePostByEmailAddress', $args['body'] ); + $this->assertNotFalse( strpos( $args['body'], 'jetpack.deletePostByEmailAddress' ) ); $this->assertStringStartsWith( 'https://jetpack.wordpress.com/xmlrpc.php', $url ); $this->request_validated = true; From acb25a9372ff3ea43d4fecfc1412de492fe15a18 Mon Sep 17 00:00:00 2001 From: Brandon Kraft Date: Wed, 1 Apr 2020 15:49:18 -0500 Subject: [PATCH 6/7] Bring PBE into full PHPCS compliance --- _inc/lib/class.core-rest-api-endpoints.php | 3 +- bin/phpcs-whitelist.js | 2 + modules/post-by-email.php | 287 +-------------- .../class-jetpack-post-by-email.php | 340 ++++++++++++++++++ 4 files changed, 349 insertions(+), 283 deletions(-) create mode 100644 modules/post-by-email/class-jetpack-post-by-email.php diff --git a/_inc/lib/class.core-rest-api-endpoints.php b/_inc/lib/class.core-rest-api-endpoints.php index ab7c0d875d4b1..0bf5561490ec5 100644 --- a/_inc/lib/class.core-rest-api-endpoints.php +++ b/_inc/lib/class.core-rest-api-endpoints.php @@ -2960,8 +2960,7 @@ public static function get_remote_value( $module, $option ) { if ( ! class_exists( 'Jetpack_Post_By_Email' ) && ! include_once( Jetpack::get_module_path( $module ) ) ) { return false; } - $post_by_email = new Jetpack_Post_By_Email(); - $value = $post_by_email->get_post_by_email_address(); + $value = Jetpack_Post_By_Email::init()->get_post_by_email_address(); if ( $value === null ) { $value = 'NULL'; // sentinel value so it actually gets set } diff --git a/bin/phpcs-whitelist.js b/bin/phpcs-whitelist.js index 7df60342ead30..a57ffd547c134 100644 --- a/bin/phpcs-whitelist.js +++ b/bin/phpcs-whitelist.js @@ -29,6 +29,8 @@ module.exports = [ 'modules/memberships/', 'modules/module-extras.php', 'modules/module-info.php', + 'modules/post-by-email.php', + 'modules/post-by-email/', 'modules/search/class-jetpack-instant-search.php', 'modules/search/class-jetpack-search-customize.php', 'modules/search/class.jetpack-search-options.php', diff --git a/modules/post-by-email.php b/modules/post-by-email.php index fd0b800228b5b..0119f392040d7 100644 --- a/modules/post-by-email.php +++ b/modules/post-by-email.php @@ -1,5 +1,4 @@ wp_create_nonce( 'wp_rest' ), - )); - wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email/post-by-email.css', __FILE__ ) ); - wp_style_add_data( 'post-by-email', 'jetpack-inline', true ); - // Do we really need `admin_styles`? With the new admin UI, it's breaking some bits. - // Jetpack::init()->admin_styles(); - } - - function check_user_connection() { - $user_token = ( new Connection_Manager() )->get_access_token( get_current_user_id() ); - - $is_user_connected = $user_token && !is_wp_error( $user_token ); - - // If the user is already connected via Jetpack, then we're good - if ( $is_user_connected ) - return true; - - return false; - } - - function user_profile() { - $blog_name = get_bloginfo( 'blogname' ); - if ( empty( $blog_name ) ) { - $blog_name = home_url( '/' ); - } - - ?> -
-

- - - - - -
-
check_user_connection() ) { - $email = $this->get_post_by_email_address(); - - if ( empty( $email ) ) { - $enable_hidden = ''; - $info_hidden = ' style="display: none;"'; - } else { - $enable_hidden = ' style="display: none;"'; - $info_hidden = ''; - } ?> - - /> -
> -

- - -

-

- - -

-
- -

- ' . esc_html( $blog_name ) . '' - ); ?>
- -

-

- -

- -
-
- init_rest_connection(); - - $xml->query( 'jetpack.getPostByEmailAddress' ); - - if ( $xml->isError() ) - return NULL; - - $response = $xml->getResponse(); - if ( empty( $response ) ) - return NULL; - - return $response; - } - - /** - * Process the REST API request to modify the "Post by Email" settings. - * - * @param string $action Allowed values: 'create', 'regenerate', 'delete'. - * - * @return array|false - */ - public function process_api_request( $action ) { - $endpoint = null; - $error_message = esc_html__( 'Please try again later.', 'jetpack' ); - $result = false; - - switch ( $action ) { - case 'create': - $endpoint = 'jetpack.createPostByEmailAddress'; - $error_message = esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' ); - break; - case 'regenerate': - $endpoint = 'jetpack.regeneratePostByEmailAddress'; - $error_message = esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' ); - break; - case 'delete': - $endpoint = 'jetpack.deletePostByEmailAddress'; - $error_message = esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' ); - break; - } - - if ( $endpoint ) { - $result = $this->process_rest_proxy_request( $endpoint, $error_message ); - } - - return $result; - } - - function create_post_by_email_address() { - _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); - - self::process_ajax_proxy_request( - 'jetpack.createPostByEmailAddress', - __( 'Unable to create your Post By Email address. Please try again later.', 'jetpack' ) - ); - } - - function regenerate_post_by_email_address() { - _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); - - self::process_ajax_proxy_request( - 'jetpack.regeneratePostByEmailAddress', - __( 'Unable to regenerate your Post By Email address. Please try again later.', 'jetpack' ) - ); - } - - function delete_post_by_email_address() { - _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); - - self::process_ajax_proxy_request( - 'jetpack.deletePostByEmailAddress', - __( 'Unable to disable your Post By Email address. Please try again later.', 'jetpack' ) - ); - } - - /** - * The AJAX proxying method for backward compatibility. - * To be removed in the upcoming versions. - * - * @param string $endpoint Jetpack API endpoint. - * @param string $error_message Error message to be returned if something goes wrong. - * - * @deprecated - */ - function __process_ajax_proxy_request( $endpoint, $error_message ) { // phpcs:ignore - $this->process_ajax_proxy_request( $endpoint, $error_message ); - } - - /** - * Back end function to abstract the xmlrpc function calls to wpcom. - * - * @param string $endpoint Jetpack API endpoint. - * @param string $error_message Error message to be returned if something goes wrong. - * - * @deprecated - */ - private function process_ajax_proxy_request( $endpoint, $error_message ) { - _deprecated_function( __METHOD__, 'jetpack-8.4', '_process_rest_proxy_request' ); - - if ( ! current_user_can( 'edit_posts' ) ) { - wp_send_json_error( $error_message ); - } - if ( empty( $_REQUEST['pbe_nonce'] ) || ! wp_verify_nonce( $_REQUEST['pbe_nonce'], $endpoint ) ) { - wp_send_json_error( $error_message ); - } - - $xml = $this->init_rest_connection(); - $xml->query( $endpoint ); - - if ( $xml->isError() ) { - wp_send_json_error( $error_message ); - } - - $response = $xml->getResponse(); - if ( empty( $response ) ) { - wp_send_json_error( $error_message ); - } - - // Will be used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. - update_option( 'post_by_email_address' . get_current_user_id(), $response ); - - wp_send_json_success( $response ); - } - - /** - * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address. - * - * @since 4.3.0 - * - * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address. - * @param string $error Error message to return. - * - * @return array - */ - private function process_rest_proxy_request( $endpoint, $error ) { - if ( ! current_user_can( 'edit_posts' ) ) { - return array( 'message' => $error ); - } - - $xml = $this->init_rest_connection(); - - $xml->query( $endpoint ); - - if ( $xml->isError() ) { - return array( 'message' => $error ); - } - - $response = $xml->getResponse(); - if ( empty( $response ) ) { - return array( 'message' => $error ); - } - - // Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. - update_option( 'post_by_email_address' . get_current_user_id(), $response ); - - return $response; - } - - /** - * Initialize the IXR client - * - * @return Jetpack_IXR_Client - */ - private function init_rest_connection() { - return new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); - } - -} diff --git a/modules/post-by-email/class-jetpack-post-by-email.php b/modules/post-by-email/class-jetpack-post-by-email.php new file mode 100644 index 0000000000000..97db6507395e7 --- /dev/null +++ b/modules/post-by-email/class-jetpack-post-by-email.php @@ -0,0 +1,340 @@ + wp_create_nonce( 'wp_rest' ), + ) + ); + wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email/post-by-email.css', __FILE__ ), array(), JETPACK__VERSION ); + wp_style_add_data( 'post-by-email', 'jetpack-inline', true ); + } + + /** + * Check if the user is connected. + * + * @return bool True if connected. False if not. + */ + public function check_user_connection() { + $user_token = ( new Connection_Manager() )->get_access_token( get_current_user_id() ); + + $is_user_connected = $user_token && ! is_wp_error( $user_token ); + + // If the user is already connected via Jetpack, then we're good. + if ( $is_user_connected ) { + return true; + } + + return false; + } + + /** + * Adds field to user profile page. + */ + public function user_profile() { + $blog_name = get_bloginfo( 'blogname' ); + if ( empty( $blog_name ) ) { + $blog_name = home_url( '/' ); + } + + ?> +
+

+ + + + + +
+
+ check_user_connection() ) { + $email = $this->get_post_by_email_address(); + + if ( empty( $email ) ) { + $enable_hidden = ''; + $info_hidden = ' style="display: none;"'; + } else { + $enable_hidden = ' style="display: none;"'; + $info_hidden = ''; + } + ?> + + /> +
> +

+ + +

+

+ + +

+
+ + +

+ ' . esc_html( $blog_name ) . '' + ); + ?> +
+ +

+

+ +

+ +
+
+ init_rest_connection(); + + $xml->query( 'jetpack.getPostByEmailAddress' ); + + if ( $xml->isError() ) { + return null; + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + return null; + } + + return $response; + } + + /** + * Process the REST API request to modify the "Post by Email" settings. + * + * @param string $action Allowed values: 'create', 'regenerate', 'delete'. + * + * @return array|false + */ + public function process_api_request( $action ) { + $endpoint = null; + $error_message = esc_html__( 'Please try again later.', 'jetpack' ); + $result = false; + + switch ( $action ) { + case 'create': + $endpoint = 'jetpack.createPostByEmailAddress'; + $error_message = esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' ); + break; + case 'regenerate': + $endpoint = 'jetpack.regeneratePostByEmailAddress'; + $error_message = esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' ); + break; + case 'delete': + $endpoint = 'jetpack.deletePostByEmailAddress'; + $error_message = esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' ); + break; + } + + if ( $endpoint ) { + $result = $this->process_rest_proxy_request( $endpoint, $error_message ); + } + + return $result; + } + + /** + * AJAX endpoint to create a new e-mail address. + */ + public function create_post_by_email_address() { + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( + 'jetpack.createPostByEmailAddress', + __( 'Unable to create your Post By Email address. Please try again later.', 'jetpack' ) + ); + } + + /** + * AJAX endpoint to regenerate PBE e-mail address. + */ + public function regenerate_post_by_email_address() { + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( + 'jetpack.regeneratePostByEmailAddress', + __( 'Unable to regenerate your Post By Email address. Please try again later.', 'jetpack' ) + ); + } + + /** + * AJAX endpoint to delete a PBE e-mail address. + */ + public function delete_post_by_email_address() { + _doing_it_wrong( __METHOD__, esc_html__( "Use REST API endpoint '/wp-json/jetpack/v4/settings' instead.", 'jetpack' ), 'jetpack-8.4' ); + + self::process_ajax_proxy_request( + 'jetpack.deletePostByEmailAddress', + __( 'Unable to disable your Post By Email address. Please try again later.', 'jetpack' ) + ); + } + + /** + * The AJAX proxying method for backward compatibility. + * To be removed in the upcoming versions. + * + * @param string $endpoint Jetpack API endpoint. + * @param string $error_message Error message to be returned if something goes wrong. + * + * @deprecated + */ + public function __process_ajax_proxy_request( $endpoint, $error_message ) { // phpcs:ignore + $this->process_ajax_proxy_request( $endpoint, $error_message ); + } + + /** + * Back end function to abstract the xmlrpc function calls to wpcom. + * + * @param string $endpoint Jetpack API endpoint. + * @param string $error_message Error message to be returned if something goes wrong. + * + * @deprecated + */ + private function process_ajax_proxy_request( $endpoint, $error_message ) { + _deprecated_function( __METHOD__, 'jetpack-8.4', '_process_rest_proxy_request' ); + + if ( ! current_user_can( 'edit_posts' ) ) { + wp_send_json_error( $error_message ); + } + if ( empty( $_REQUEST['pbe_nonce'] ) || ! wp_verify_nonce( $_REQUEST['pbe_nonce'], $endpoint ) ) { + wp_send_json_error( $error_message ); + } + + $xml = $this->init_rest_connection(); + $xml->query( $endpoint ); + + if ( $xml->isError() ) { + wp_send_json_error( $error_message ); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + wp_send_json_error( $error_message ); + } + + // Will be used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. + update_option( 'post_by_email_address' . get_current_user_id(), $response ); + + wp_send_json_success( $response ); + } + + /** + * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address. + * + * @since 4.3.0 + * + * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address. + * @param string $error Error message to return. + * + * @return array + */ + private function process_rest_proxy_request( $endpoint, $error ) { + if ( ! current_user_can( 'edit_posts' ) ) { + return array( 'message' => $error ); + } + + $xml = $this->init_rest_connection(); + + $xml->query( $endpoint ); + + if ( $xml->isError() ) { + return array( 'message' => $error ); + } + + $response = $xml->getResponse(); + if ( empty( $response ) ) { + return array( 'message' => $error ); + } + + // Used only in Jetpack_Core_Json_Api_Endpoints::get_remote_value. + update_option( 'post_by_email_address' . get_current_user_id(), $response ); + + return $response; + } + + /** + * Initialize the IXR client + * + * @return Jetpack_IXR_Client + */ + private function init_rest_connection() { + return new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); + } +} From 867af1bcf92d3d16c713dec294a86236f2ccf354 Mon Sep 17 00:00:00 2001 From: Sergey Mitroshin Date: Wed, 1 Apr 2020 21:55:53 -0500 Subject: [PATCH 7/7] Post by Email: corrected paths to JS and CSS files, adjusted style attribute. - Files `post-by-email.js` and `post-by-email.css` were enqueued incorrectly. Since the class was moved into the module directory, they got additional `post-by-email/` in the path, fixed now. - Escaping `style` attributes in the "User Profile" form leads to quotes being converted to HTML entities. I refactored this tiny piece of code to make the attribute escaping work. --- .../class-jetpack-post-by-email.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/modules/post-by-email/class-jetpack-post-by-email.php b/modules/post-by-email/class-jetpack-post-by-email.php index 97db6507395e7..78f44544e3b53 100644 --- a/modules/post-by-email/class-jetpack-post-by-email.php +++ b/modules/post-by-email/class-jetpack-post-by-email.php @@ -53,7 +53,7 @@ public function action_init() { * Enqueues scripts for user profile page. */ public function profile_scripts() { - wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email/post-by-email.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION, true ); + wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION, true ); wp_localize_script( 'post-by-email', 'pbeVars', @@ -61,7 +61,7 @@ public function profile_scripts() { 'rest_nonce' => wp_create_nonce( 'wp_rest' ), ) ); - wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email/post-by-email.css', __FILE__ ), array(), JETPACK__VERSION ); + wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email.css', __FILE__ ), array(), JETPACK__VERSION ); wp_style_add_data( 'post-by-email', 'jetpack-inline', true ); } @@ -105,17 +105,12 @@ public function user_profile() { if ( $this->check_user_connection() ) { $email = $this->get_post_by_email_address(); - if ( empty( $email ) ) { - $enable_hidden = ''; - $info_hidden = ' style="display: none;"'; - } else { - $enable_hidden = ' style="display: none;"'; - $info_hidden = ''; - } + $enable_button_style = empty( $email ) ? '' : 'display: none;'; + $info_style = empty( $email ) ? 'display: none;' : ''; ?> - /> -
> + +