Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.14% covered (danger)
43.14%
44 / 102
58.33% covered (warning)
58.33%
7 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Jetpack_Post_By_Email
43.14% covered (danger)
43.14%
44 / 102
58.33% covered (warning)
58.33%
7 / 12
207.69
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 address_copy_option_name
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete_address_copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 action_init
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 profile_scripts
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 check_user_connection
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 user_profile
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 1
30
 get_post_by_email_address
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 process_api_request
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 process_rest_proxy_request
76.92% covered (warning)
76.92%
10 / 13
0.00% covered (danger)
0.00%
0 / 1
5.31
 init_rest_connection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Class Jetpack_Post_By_Email
4 *
5 * @package automattic/jetpack
6 */
7
8use Automattic\Jetpack\Connection\Tokens;
9use Automattic\Jetpack\Redirect;
10
11/**
12 * Class Jetpack_Post_By_Email
13 */
14class Jetpack_Post_By_Email {
15    /**
16     * Initialize PBE.
17     *
18     * @return Jetpack_Post_By_Email
19     */
20    public static function init() {
21        static $instance = null;
22
23        if ( ! $instance ) {
24            $instance = new Jetpack_Post_By_Email();
25        }
26
27        return $instance;
28    }
29
30    /**
31     * Singleton
32     */
33    private function __construct() {
34        add_action( 'init', array( $this, 'action_init' ) );
35    }
36
37    /**
38     * Name of the option holding a user's local copy of their Post by Email address.
39     *
40     * @since $$next-version$$
41     *
42     * @param int $user_id Local user ID.
43     *
44     * @return string
45     */
46    public static function address_copy_option_name( $user_id ) {
47        return 'post_by_email_address' . (int) $user_id;
48    }
49
50    /**
51     * Delete a user's local copy of their Post by Email address.
52     *
53     * @since $$next-version$$
54     *
55     * @param int $user_id Local user ID.
56     */
57    public static function delete_address_copy( $user_id ) {
58        delete_option( self::address_copy_option_name( $user_id ) );
59    }
60
61    /**
62     * Adds hooks for PBE.
63     */
64    public function action_init() {
65        if ( ! current_user_can( 'edit_posts' ) ) {
66            return;
67        }
68
69        add_action( 'profile_personal_options', array( $this, 'user_profile' ) );
70        add_action( 'admin_print_scripts-profile.php', array( $this, 'profile_scripts' ) );
71
72        add_action( 'wp_ajax_jetpack_post_by_email_enable', array( $this, 'create_post_by_email_address' ) );
73        add_action( 'wp_ajax_jetpack_post_by_email_regenerate', array( $this, 'regenerate_post_by_email_address' ) );
74        add_action( 'wp_ajax_jetpack_post_by_email_disable', array( $this, 'delete_post_by_email_address' ) );
75    }
76
77    /**
78     * Enqueues scripts for user profile page.
79     */
80    public function profile_scripts() {
81        wp_enqueue_script( 'post-by-email', plugins_url( 'post-by-email.js', __FILE__ ), array( 'jquery' ), JETPACK__VERSION, true );
82        wp_localize_script(
83            'post-by-email',
84            'pbeVars',
85            array(
86                'rest_nonce' => wp_create_nonce( 'wp_rest' ),
87            )
88        );
89        wp_enqueue_style( 'post-by-email', plugins_url( 'post-by-email.css', __FILE__ ), array(), JETPACK__VERSION );
90        // Inline styles. @see wp_maybe_inline_styles()
91        if ( is_rtl() ) {
92            wp_style_add_data( 'post-by-email', 'path', plugin_dir_path( __FILE__ ) . 'post-by-email-rtl.min.css' );
93        } else {
94            wp_style_add_data( 'post-by-email', 'path', plugin_dir_path( __FILE__ ) . 'post-by-email.min.css' );
95        }
96    }
97
98    /**
99     * Check if the user is connected.
100     *
101     * @return bool True if connected. False if not.
102     */
103    public function check_user_connection() {
104        $user_token = ( new Tokens() )->get_access_token( get_current_user_id() );
105
106        $is_user_connected = $user_token && ! is_wp_error( $user_token );
107
108        // If the user is already connected via Jetpack, then we're good.
109        if ( $is_user_connected ) {
110            return true;
111        }
112
113        return false;
114    }
115
116    /**
117     * Adds field to user profile page.
118     */
119    public function user_profile() {
120        $blog_name = get_bloginfo( 'blogname' );
121        if ( empty( $blog_name ) ) {
122            $blog_name = home_url( '/' );
123        }
124
125        ?>
126        <div id="post-by-email" class="jetpack-targetable">
127            <h3><?php esc_html_e( 'Post by Email', 'jetpack' ); ?></h3>
128            <table class="form-table">
129                <tr>
130                    <th scope="row"><?php esc_html_e( 'Email Address', 'jetpack' ); ?><span id="jp-pbe-spinner" class="spinner"></span></th>
131                    <td>
132                        <div id="jp-pbe-error" class="jetpack-inline-error"></div>
133                        <?php
134
135                        if ( $this->check_user_connection() ) {
136                            $email = $this->get_post_by_email_address();
137
138                            $enable_button_style = empty( $email ) ? '' : 'display: none;';
139                            $info_style          = empty( $email ) ? 'display: none;' : '';
140                            ?>
141
142                            <input type="button" name="jp-pbe-enable" id="jp-pbe-enable" class="button" value="<?php esc_attr_e( 'Enable Post By Email', 'jetpack' ); ?>" style="<?php echo esc_attr( $enable_button_style ); ?>" />
143                            <div id="jp-pbe-info" style="<?php echo esc_attr( $info_style ); ?>">
144                                <p id="jp-pbe-email-wrapper">
145                                    <input type="text" id="jp-pbe-email" value="<?php echo esc_attr( $email ); ?>" readonly="readonly" class="regular-text" />
146                                    <span class="description"><a target="_blank" rel="noopener noreferrer" href="<?php echo esc_url( Redirect::get_url( 'jetpack-support-post-by-email' ) ); ?>"><?php esc_html_e( 'More information', 'jetpack' ); ?></a></span>
147                                </p>
148                                <p>
149                                    <input type="button" name="jp-pbe-regenerate" id="jp-pbe-regenerate" class="button" value="<?php esc_attr_e( 'Regenerate Address', 'jetpack' ); ?> " />
150                                    <input type="button" name="jp-pbe-disable" id="jp-pbe-disable" class="button" value="<?php esc_attr_e( 'Disable Post By Email', 'jetpack' ); ?> " />
151                                </p>
152                            </div>
153                            <?php
154                        } else {
155                            $jetpack = Jetpack::init();
156                            ?>
157
158                            <p class="jetpack-inline-message">
159                                <?php
160                                printf(
161                                    /* translators: Placeholder is the site's name from WordPress settings. */
162                                    esc_html( wptexturize( __( 'To use Post By Email, you need to link your %s account to your WordPress.com account.', 'jetpack' ) ) ),
163                                    '<strong>' . esc_html( $blog_name ) . '</strong>'
164                                );
165                                ?>
166                                <br />
167                                <?php echo esc_html( wptexturize( __( "If you don't have a WordPress.com account yet, you can sign up for free in just a few seconds.", 'jetpack' ) ) ); ?>
168                            </p>
169                            <p>
170                                <a href="<?php echo esc_url( $jetpack->build_connect_url( true, get_edit_profile_url( get_current_user_id() ) . '#post-by-email', 'unlinked-user-pbe' ) ); ?>" class="button button-connector" id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack' ); ?></a>
171                            </p>
172                            <?php
173                        }
174                        ?>
175                    </td>
176                </tr>
177            </table>
178        </div>
179        <?php
180    }
181
182    /**
183     * XMLRPC Query to WP.com for PBE e-mail address for user.
184     *
185     * @return string|null PBE E-mail Address or null on error.
186     */
187    public function get_post_by_email_address() {
188        $xml = $this->init_rest_connection();
189
190        $xml->query( 'jetpack.getPostByEmailAddress' );
191
192        if ( $xml->isError() ) {
193            return null;
194        }
195
196        $response = $xml->getResponse();
197        if ( empty( $response ) ) {
198            return null;
199        }
200
201        return $response;
202    }
203
204    /**
205     * Process the REST API request to modify the "Post by Email" settings.
206     *
207     * @param string $action Allowed values: 'create', 'regenerate', 'delete'.
208     *
209     * @return array|false
210     */
211    public function process_api_request( $action ) {
212        $endpoint      = null;
213        $error_message = esc_html__( 'Please try again later.', 'jetpack' );
214        $result        = false;
215
216        switch ( $action ) {
217            case 'create':
218                $endpoint      = 'jetpack.createPostByEmailAddress';
219                $error_message = esc_html__( 'Unable to create the Post by Email address. Please try again later.', 'jetpack' );
220                break;
221            case 'regenerate':
222                $endpoint      = 'jetpack.regeneratePostByEmailAddress';
223                $error_message = esc_html__( 'Unable to regenerate the Post by Email address. Please try again later.', 'jetpack' );
224                break;
225            case 'delete':
226                $endpoint      = 'jetpack.deletePostByEmailAddress';
227                $error_message = esc_html__( 'Unable to delete the Post by Email address. Please try again later.', 'jetpack' );
228                break;
229        }
230
231        if ( $endpoint ) {
232            $result = $this->process_rest_proxy_request( $endpoint, $error_message );
233        }
234
235        return $result;
236    }
237
238    /**
239     * Calls WPCOM through authenticated request to create, regenerate or delete the Post by Email address.
240     *
241     * @since 4.3.0
242     *
243     * @param string $endpoint Process to call on WPCOM to create, regenerate or delete the Post by Email address.
244     * @param string $error    Error message to return.
245     *
246     * @return array
247     */
248    private function process_rest_proxy_request( $endpoint, $error ) {
249        if ( ! current_user_can( 'edit_posts' ) ) {
250            return array( 'message' => $error );
251        }
252
253        $xml = $this->init_rest_connection();
254
255        $xml->query( $endpoint );
256
257        if ( $xml->isError() ) {
258            return array( 'message' => $error );
259        }
260
261        $response = $xml->getResponse();
262        if ( empty( $response ) ) {
263            return array( 'message' => $error );
264        }
265
266        // Read by Jetpack_Core_Json_Api_Endpoints::get_remote_value. A delete answers `true`, which is not an address.
267        if ( is_string( $response ) ) {
268            update_option( self::address_copy_option_name( get_current_user_id() ), $response );
269        } else {
270            self::delete_address_copy( get_current_user_id() );
271        }
272
273        return $response;
274    }
275
276    /**
277     * Initialize the IXR client
278     *
279     * @return Jetpack_IXR_Client
280     */
281    private function init_rest_connection() {
282        return new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) );
283    }
284}