Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
22.18% covered (danger)
22.18%
57 / 257
6.06% covered (danger)
6.06%
2 / 33
CRAP
0.00% covered (danger)
0.00%
0 / 1
Publicize
22.18% covered (danger)
22.18%
57 / 257
6.06% covered (danger)
6.06%
2 / 33
4908.66
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 add_disconnect_notice
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 force_user_connection
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 admin_page_warning
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
6
 disconnect
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 receive_updated_publicize_connections
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
2.09
 register_update_publicize_connections_xmlrpc_method
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 get_all_connections
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 get_connections
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
8.05
 get_all_connections_for_user
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
110
 add_connection_test_results
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 get_connection_for_user
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 get_connection_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_connection_unique_id
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_connection_meta
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 admin_page_load
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 display_connection_error
0.00% covered (danger)
0.00%
0 / 51
0.00% covered (danger)
0.00%
0 / 1
132
 display_disconnected
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 globalization
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 globalize_connection
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 unglobalize_connection
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 refresh_connections
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 clear_connections_transient
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 connect_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 refresh_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 disconnect_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_services
95.00% covered (success)
95.00%
19 / 20
0.00% covered (danger)
0.00%
0 / 1
4
 get_connection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 flag_post_for_publicize
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 test_connection
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
42
 post_is_done_sharing
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 save_publicized
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
4.13
 set_post_flags
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2/**
3 * Publicize class.
4 *
5 * @package automattic/jetpack-publicize
6 */
7
8namespace Automattic\Jetpack\Publicize;
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\Connection\Tokens;
12use Jetpack_IXR_Client;
13use Jetpack_Options;
14use WP_Error;
15use WP_Post;
16
17/**
18 * Extend the base class with Jetpack-specific functionality.
19 */
20class Publicize extends Publicize_Base {
21
22    const JETPACK_SOCIAL_CONNECTIONS_TRANSIENT = 'jetpack_social_connections';
23
24    /**
25     * Transitory storage of connection testing results.
26     *
27     * @var array
28     */
29    private $test_connection_results = array();
30
31    /**
32     * Property to store the results of fetching the connections.
33     *
34     * @var null|array
35     */
36    private $current_connections = null;
37
38    /**
39     * Add hooks.
40     */
41    public function __construct() {
42        parent::__construct();
43
44        add_filter( 'jetpack_xmlrpc_unauthenticated_methods', array( $this, 'register_update_publicize_connections_xmlrpc_method' ) );
45
46        add_action( 'load-settings_page_sharing', array( $this, 'admin_page_load' ), 9 );
47
48        add_action( 'load-settings_page_sharing', array( $this, 'force_user_connection' ) );
49
50        add_filter( 'jetpack_published_post_flags', array( $this, 'set_post_flags' ), 10, 2 );
51
52        add_action( 'wp_insert_post', array( $this, 'save_publicized' ), 11, 2 );
53
54        add_action( 'connection_disconnected', array( $this, 'add_disconnect_notice' ) );
55    }
56
57    /**
58     * Add a notice when a connection has been disconnected.
59     */
60    public function add_disconnect_notice() {
61        add_action( 'admin_notices', array( $this, 'display_disconnected' ) );
62    }
63
64    /**
65     * Force user connection before showing the Publicize UI.
66     */
67    public function force_user_connection() {
68        global $current_user;
69
70        $user_token        = ( new Tokens() )->get_access_token( $current_user->ID );
71        $is_user_connected = $user_token && ! is_wp_error( $user_token );
72
73        // If the user is already connected via Jetpack, then we're good.
74        if ( $is_user_connected ) {
75            return;
76        }
77
78        // If they're not connected, then remove the Publicize UI and tell them they need to connect first.
79        global $publicize_ui;
80        remove_action( 'pre_admin_screen_sharing', array( $publicize_ui, 'admin_page' ) );
81
82        // Do we really need `admin_styles`? With the new admin UI, it's breaking some bits.
83        // Jetpack::init()->admin_styles();.
84        add_action( 'pre_admin_screen_sharing', array( $this, 'admin_page_warning' ), 1 );
85    }
86
87    /**
88     * Show a warning when Publicize does not have a connection.
89     */
90    public function admin_page_warning() {
91        $jetpack   = \Jetpack::init();
92        $blog_name = get_bloginfo( 'blogname' );
93        if ( empty( $blog_name ) ) {
94            $blog_name = home_url( '/' );
95        }
96
97        ?>
98        <div id="message" class="updated jetpack-message jp-connect">
99            <div class="jetpack-wrap-container">
100                <div class="jetpack-text-container">
101                    <p>
102                        <?php
103                            printf(
104                                /* translators: %s is the name of the blog */
105                                esc_html( wptexturize( __( "To use Jetpack Social, you'll need to link your %s account to your WordPress.com account using the link below.", 'jetpack-publicize-pkg' ) ) ),
106                                '<strong>' . esc_html( $blog_name ) . '</strong>'
107                            );
108                        ?>
109                    </p>
110                    <p><?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-publicize-pkg' ) ) ); ?></p>
111                </div>
112                <div class="jetpack-install-container">
113                    <p class="submit"><a
114                            href="<?php echo esc_url( $jetpack->build_connect_url( false, menu_page_url( 'sharing', false ) ) ); ?>"
115                            class="button-connector"
116                            id="wpcom-connect"><?php esc_html_e( 'Link account with WordPress.com', 'jetpack-publicize-pkg' ); ?></a>
117                    </p>
118                    <p class="jetpack-install-blurb">
119                        <?php jetpack_render_tos_blurb(); ?>
120                    </p>
121                </div>
122            </div>
123        </div>
124        <?php
125    }
126
127    /**
128     * Remove a Publicize Connection.
129     *
130     * @param string    $service_name 'facebook', 'twitter', etc.
131     * @param string    $connection_id Connection ID.
132     * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
133     * @param false|int $_user_id The user ID. Use false (default) for the current user.
134     * @param bool      $force_delete Whether to skip permissions checks.
135     * @return false|void False on failure. Void on success.
136     */
137    public function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false ) {
138        return Keyring_Helper::disconnect( $service_name, $connection_id, $_blog_id, $_user_id, $force_delete );
139    }
140
141    /**
142     * Set updated Publicize connections in a transient.
143     *
144     * @param mixed $publicize_connections Updated connections.
145     * @return true
146     */
147    public function receive_updated_publicize_connections( $publicize_connections ) {
148
149        // Populate the cache with the new data.
150        Connections::get_all( array( 'ignore_cache' => true ) );
151
152        $expiry = 3600 * 4;
153        if ( ! set_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT, $publicize_connections, $expiry ) ) {
154            // If the transient has beeen set in another request, the call to set_transient can fail. If so,
155            // we can delete the transient and try again.
156            $this->clear_connections_transient();
157            set_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT, $publicize_connections, $expiry );
158        }
159        // Regardless of whether the transient is set ok, let's set and use the local property for this request.
160        $this->current_connections = $publicize_connections;
161        return true;
162    }
163
164    /**
165     * Add method to update Publicize connections.
166     *
167     * @param array $methods Array of registered methods.
168     * @return array
169     */
170    public function register_update_publicize_connections_xmlrpc_method( $methods ) {
171        return array_merge(
172            $methods,
173            array(
174                'jetpack.updatePublicizeConnections' => array( $this, 'receive_updated_publicize_connections' ),
175            )
176        );
177    }
178
179    /**
180     * Get a list of all connections.
181     *
182     * Google Plus is no longer a functional service, so we remove it from the list.
183     *
184     * @return array
185     */
186    public function get_all_connections() {
187        $this->refresh_connections();
188
189        $connections = $this->current_connections;
190
191        if ( empty( $connections ) ) {
192            $connections = array();
193        }
194
195        if ( isset( $connections['google_plus'] ) ) {
196            unset( $connections['google_plus'] );
197        }
198        return $connections;
199    }
200
201    /**
202     * Get connections for a specific service.
203     *
204     * @param string    $service_name 'facebook', 'twitter', etc.
205     * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
206     * @param false|int $_user_id The user ID. Use false (default) for the current user.
207     * @return false|object[]|array[]
208     */
209    public function get_connections( $service_name, $_blog_id = false, $_user_id = false ) {
210        if ( false === $_user_id ) {
211            $_user_id = $this->user_id();
212        }
213
214        $connections           = $this->get_all_connections();
215        $connections_to_return = array();
216
217        if ( ! empty( $connections ) && is_array( $connections ) ) {
218            if ( ! empty( $connections[ $service_name ] ) ) {
219                foreach ( $connections[ $service_name ] as $id => $connection ) {
220                    if ( $this->is_global_connection( $connection ) || $_user_id === (int) $connection['connection_data']['user_id'] ) {
221                        $connections_to_return[ $id ] = $connection;
222                    }
223                }
224            }
225
226            return $connections_to_return;
227        }
228
229        return false;
230    }
231
232    /**
233     * Get all connections for a specific user.
234     *
235     * @param array $args Arguments to run operations such as force refresh and connection test results.
236     * @return array
237     */
238    public function get_all_connections_for_user( $args = array() ) {
239        if ( ( isset( $args['clear_cache'] ) && $args['clear_cache'] )
240        || ( isset( $args['test_connections'] ) && $args['test_connections'] ) ) {
241            $this->clear_connections_transient();
242        }
243        $connections = $this->get_all_connections();
244
245        $connections_to_return = array();
246        if ( ! empty( $connections ) ) {
247            foreach ( (array) $connections as $service_name => $connections_for_service ) {
248                foreach ( $connections_for_service as $id => $connection ) {
249                    $user_id = (int) $connection['connection_data']['user_id'];
250                    // phpcs:ignore WordPress.PHP.YodaConditions.NotYoda
251                    if ( $user_id === 0 || $this->user_id() === $user_id ) {
252                        $connections_to_return[ $service_name ][ $id ] = $connection;
253                    }
254                }
255            }
256        }
257
258        return $connections_to_return;
259    }
260
261    /**
262     * To add the connection test results to the connections.
263     *
264     * @param array $connections The Jetpack Social connections.
265
266     * @return array
267     */
268    public function add_connection_test_results( $connections ) {
269        $path                   = sprintf( '/sites/%d/publicize/connection-test-results', absint( Jetpack_Options::get_option( 'id' ) ) );
270        $response               = Client::wpcom_json_api_request_as_user( $path, '2', array(), null, 'wpcom' );
271        $connection_results     = json_decode( wp_remote_retrieve_body( $response ), true );
272        $connection_results_map = array();
273
274        foreach ( $connection_results as $connection_result ) {
275            $connection_results_map[ $connection_result['connection_id'] ] = $connection_result['test_success'] ? 'ok' : 'broken';
276        }
277        foreach ( $connections as $key => $connection ) {
278            if ( isset( $connection_results_map[ $connection['connection_id'] ] ) ) {
279                $connections[ $key ]['status'] = $connection_results_map[ $connection['connection_id'] ];
280            }
281        }
282
283        return $connections;
284    }
285
286    /**
287     * Get a connections for a user.
288     *
289     * @param int $connection_id The connection_id.
290
291     * @return array
292     */
293    public function get_connection_for_user( $connection_id ) {
294        foreach ( $this->get_all_connections_for_user() as $connection ) {
295            if ( (int) $connection['connection_id'] === (int) $connection_id ) {
296                return $connection;
297            }
298        }
299        return array();
300    }
301
302    /**
303     * Get the ID of a connection.
304     *
305     * @param array $connection The connection.
306     * @return string
307     */
308    public function get_connection_id( $connection ) {
309        return $connection['connection_data']['id'];
310    }
311
312    /**
313     * Get the unique ID of a connection.
314     *
315     * @param array $connection The connection.
316     * @return string
317     */
318    public function get_connection_unique_id( $connection ) {
319        return $connection['connection_data']['token_id'];
320    }
321
322    /**
323     * Get the meta of a connection.
324     *
325     * @param array $connection The connection.
326     * @return array
327     */
328    public function get_connection_meta( $connection ) {
329        $connection['user_id'] = $connection['connection_data']['user_id']; // Allows for shared connections.
330        return $connection;
331    }
332
333    /**
334     * Show error on settings page if applicable.
335     */
336    public function admin_page_load() {
337        $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
338
339        if ( 'error' === $action ) {
340            add_action( 'pre_admin_screen_sharing', array( $this, 'display_connection_error' ), 9 );
341        }
342    }
343
344    /**
345     * Display an error message.
346     */
347    public function display_connection_error() {
348        $code = false;
349        // phpcs:disable WordPress.Security.NonceVerification.Recommended
350        $service         = isset( $_GET['service'] ) ? sanitize_text_field( wp_unslash( $_GET['service'] ) ) : null;
351        $publicize_error = isset( $_GET['publicize_error'] ) ? sanitize_text_field( wp_unslash( $_GET['publicize_error'] ) ) : null;
352        // phpcs:enable WordPress.Security.NonceVerification.Recommended
353
354        if ( $service ) {
355            /* translators: %s is the name of the Jetpack Social service (e.g. Facebook, Twitter) */
356            $error = sprintf( __( 'There was a problem connecting to %s to create an authorized connection. Please try again in a moment.', 'jetpack-publicize-pkg' ), self::get_service_label( $service ) );
357        } elseif ( $publicize_error ) {
358            $code = strtolower( $publicize_error );
359            switch ( $code ) {
360                case '400':
361                    $error = __( 'An invalid request was made. This normally means that something intercepted or corrupted the request from your server to the Jetpack Server. Try again and see if it works this time.', 'jetpack-publicize-pkg' );
362                    break;
363                case 'secret_mismatch':
364                    $error = __( 'We could not verify that your server is making an authorized request. Please try again, and make sure there is nothing interfering with requests from your server to the Jetpack Server.', 'jetpack-publicize-pkg' );
365                    break;
366                case 'empty_blog_id':
367                    $error = __( 'No blog_id was included in your request. Please try disconnecting Jetpack from WordPress.com and then reconnecting it. Once you have done that, try connecting Jetpack Social again.', 'jetpack-publicize-pkg' );
368                    break;
369                case 'empty_state':
370                    /* translators: %s is the URL of the Jetpack admin page */
371                    $error = sprintf( __( 'No user information was included in your request. Please make sure that your user account has connected to Jetpack. Connect your user account by going to the <a href="%s">Jetpack page</a> within wp-admin.', 'jetpack-publicize-pkg' ), \Jetpack::admin_url() );
372                    break;
373                default:
374                    $error = __( 'Something which should never happen, happened. Sorry about that. If you try again, maybe it will work.', 'jetpack-publicize-pkg' );
375                    break;
376            }
377        } else {
378            $error = __( 'There was a problem connecting with Jetpack Social. Please try again in a moment.', 'jetpack-publicize-pkg' );
379        }
380        // Using the same formatting/style as Jetpack::admin_notices() error.
381        ?>
382        <div id="message" class="jetpack-message jetpack-err">
383            <div class="squeezer">
384                <h2>
385                    <?php
386                        echo wp_kses(
387                            $error,
388                            array(
389                                'a'      => array(
390                                    'href' => true,
391                                ),
392                                'code'   => true,
393                                'strong' => true,
394                                'br'     => true,
395                                'b'      => true,
396                            )
397                        );
398                    ?>
399                </h2>
400                <?php if ( $code ) : ?>
401                    <p>
402                    <?php
403                    printf(
404                        /* translators: %s is the name of the error */
405                        esc_html__( 'Error code: %s', 'jetpack-publicize-pkg' ),
406                        esc_html( stripslashes( $code ) )
407                    );
408                    ?>
409                    </p>
410                <?php endif; ?>
411            </div>
412        </div>
413        <?php
414    }
415
416    /**
417     * Show a message that the connection has been removed.
418     */
419    public function display_disconnected() {
420        echo "<div class='updated'>\n";
421        echo '<p>' . esc_html( __( 'That connection has been removed.', 'jetpack-publicize-pkg' ) ) . "</p>\n";
422        echo "</div>\n\n";
423    }
424
425    /**
426     * If applicable, globalize a connection.
427     *
428     * @param string $connection_id Connection ID.
429     */
430    public function globalization( $connection_id ) {
431        if ( isset( $_REQUEST['global'] ) && 'on' === $_REQUEST['global'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- nonce check happens earlier in the process before we get here
432            if ( ! current_user_can( $this->GLOBAL_CAP ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
433                return;
434            }
435
436            $this->globalize_connection( $connection_id );
437        }
438    }
439
440    /**
441     * Globalize a connection.
442     *
443     * @param string $connection_id Connection ID.
444     */
445    public function globalize_connection( $connection_id ) {
446        $xml = new Jetpack_IXR_Client();
447        $xml->query( 'jetpack.globalizePublicizeConnection', $connection_id, 'globalize' );
448
449        if ( ! $xml->isError() ) {
450            $response = $xml->getResponse();
451            $this->receive_updated_publicize_connections( $response );
452        }
453    }
454
455    /**
456     * Unglobalize a connection.
457     *
458     * @param string $connection_id Connection ID.
459     */
460    public function unglobalize_connection( $connection_id ) {
461        $xml = new Jetpack_IXR_Client();
462        $xml->query( 'jetpack.globalizePublicizeConnection', $connection_id, 'unglobalize' );
463
464        if ( ! $xml->isError() ) {
465            $response = $xml->getResponse();
466            $this->receive_updated_publicize_connections( $response );
467        }
468    }
469
470    /**
471     * Grabs a fresh copy of the publicize connections data, if the cache is busted.
472     */
473    public function refresh_connections() {
474        if ( null !== $this->current_connections ) {
475            return;
476        }
477
478        $connections = get_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT );
479        if ( false === $connections ) {
480            $xml = new Jetpack_IXR_Client();
481            $xml->query( 'jetpack.fetchPublicizeConnections' );
482            if ( ! $xml->isError() ) {
483                $response = $xml->getResponse();
484                $this->receive_updated_publicize_connections( $response );
485            }
486            return;
487        }
488        $this->current_connections = $connections;
489    }
490
491    /**
492     * Delete the transient.
493     */
494    public function clear_connections_transient() {
495        delete_transient( self::JETPACK_SOCIAL_CONNECTIONS_TRANSIENT );
496    }
497
498    /**
499     * Get the Publicize connect URL from Keyring.
500     *
501     * @param string $service_name Name of the service to get connect URL for.
502     * @param string $for What the URL is for. Default 'publicize'.
503     * @return string
504     */
505    public function connect_url( $service_name, $for = 'publicize' ) {
506        return Keyring_Helper::connect_url( $service_name, $for );
507    }
508
509    /**
510     * Get the Publicize refresh URL from Keyring.
511     *
512     * @param string $service_name Name of the service to get refresh URL for.
513     * @param string $for What the URL is for. Default 'publicize'.
514     * @return string
515     */
516    public function refresh_url( $service_name, $for = 'publicize' ) {
517        return Keyring_Helper::refresh_url( $service_name, $for );
518    }
519
520    /**
521     * Get the Publicize disconnect URL from Keyring.
522     *
523     * @param string $service_name Name of the service to get disconnect URL for.
524     * @param mixed  $id ID of the conenction to disconnect.
525     * @return string
526     */
527    public function disconnect_url( $service_name, $id ) {
528        return Keyring_Helper::disconnect_url( $service_name, $id );
529    }
530
531    /**
532     * Get social networks, either all available or only those that the site is connected to.
533     *
534     * @since 0.1.0
535     * @since-jetpack 2.0.0
536     *
537     * @since-jetpack 6.6.0 Removed Path. Service closed October 2018.
538     *
539     * @param string    $filter Select the list of services that will be returned. Defaults to 'all', accepts 'connected'.
540     * @param false|int $_blog_id Get services for a specific blog by ID, or set to false for current blog. Default false.
541     * @param false|int $_user_id Get services for a specific user by ID, or set to false for current user. Default false.
542     * @return array List of social networks.
543     */
544    public function get_services( $filter = 'all', $_blog_id = false, $_user_id = false ) {
545        $services = array(
546            'facebook'           => array(),
547            'twitter'            => array(),
548            'linkedin'           => array(),
549            'tumblr'             => array(),
550            'mastodon'           => array(),
551            'instagram-business' => array(),
552            'nextdoor'           => array(),
553            'threads'            => array(),
554            'x'                  => array(),
555            'bluesky'            => array(),
556        );
557
558        if ( 'all' === $filter ) {
559            return $services;
560        }
561
562        $connected_services = array();
563        foreach ( $services as $service_name => $empty ) {
564            $connections = $this->get_connections( $service_name, $_blog_id, $_user_id );
565            if ( $connections ) {
566                $connected_services[ $service_name ] = $connections;
567            }
568        }
569        return $connected_services;
570    }
571
572    /**
573     * Get a specific connection. Stub.
574     *
575     * @param string    $service_name 'facebook', 'twitter', etc.
576     * @param string    $connection_id Connection ID.
577     * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
578     * @param false|int $_user_id The user ID. Use false (default) for the current user.
579     * @return void
580     */
581    public function get_connection( $service_name, $connection_id, $_blog_id = false, $_user_id = false ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
582        // Stub.
583    }
584
585    /**
586     * Flag a post for Publicize after publishing.
587     *
588     * @param string  $new_status New status of the post.
589     * @param string  $old_status Old status of the post.
590     * @param WP_Post $post Post object.
591     */
592    public function flag_post_for_publicize( $new_status, $old_status, $post ) {
593        if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) {
594            return;
595        }
596
597        $should_publicize = $this->should_submit_post_pre_checks( $post );
598
599        if ( 'publish' === $new_status && 'publish' !== $old_status ) {
600            /**
601             * Determines whether a post being published gets publicized.
602             *
603             * Side-note: Possibly our most alliterative filter name.
604             *
605             * @since 0.1.0 No longer defaults to true. Adds checks to not publicize based on different contexts.
606             * @since-jetpack 4.1.0
607             *
608             * @param bool $should_publicize Should the post be publicized? Default to true.
609             * @param WP_POST $post Current Post object.
610             */
611            $should_publicize = apply_filters( 'publicize_should_publicize_published_post', $should_publicize, $post );
612
613            if ( $should_publicize ) {
614                update_post_meta( $post->ID, $this->PENDING, true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
615            }
616        }
617    }
618
619    /**
620     * Test a connection.
621     *
622     * @param string $service_name Name of the service.
623     * @param array  $connection Connection to be tested.
624     */
625    public function test_connection( $service_name, $connection ) {
626        $id = $this->get_connection_id( $connection );
627
628        if ( array_key_exists( $id, $this->test_connection_results ) ) {
629            return $this->test_connection_results[ $id ];
630        }
631
632        $xml = new Jetpack_IXR_Client();
633        $xml->query( 'jetpack.testPublicizeConnection', $id );
634
635        // Bail if all is well.
636        if ( ! $xml->isError() ) {
637            $this->test_connection_results[ $id ] = true;
638            return true;
639        }
640
641        $xml_response            = $xml->getResponse();
642        $connection_test_message = $xml_response['faultString'];
643        $connection_error_code   = ( empty( $xml_response['faultCode'] ) || ! is_int( $xml_response['faultCode'] ) )
644            ? -1
645            : $xml_response['faultCode'];
646
647        // Set up refresh if the user can.
648        $user_can_refresh = current_user_can( $this->GLOBAL_CAP ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
649        if ( $user_can_refresh ) {
650            /* translators: %s is the name of a social media service */
651            $refresh_text = sprintf( _x( 'Refresh connection with %s', 'Refresh connection with {social media service}', 'jetpack-publicize-pkg' ), $this->get_service_label( $service_name ) );
652            $refresh_url  = $this->refresh_url( $service_name );
653        }
654
655        $error_data = array(
656            'user_can_refresh' => $user_can_refresh,
657            'refresh_text'     => $refresh_text ?? null,
658            'refresh_url'      => $refresh_url ?? null,
659        );
660
661        $this->test_connection_results[ $id ] = new WP_Error( $connection_error_code, $connection_test_message, $error_data );
662
663        return $this->test_connection_results[ $id ];
664    }
665
666    /**
667     * Checks if post has already been shared by Publicize in the past.
668     *
669     * Jetpack uses two methods:
670     * 1. A POST_DONE . 'all' postmeta flag, or
671     * 2. if the post has already been published.
672     *
673     * @since 0.1.0
674     * @since-jetpack 6.7.0
675     *
676     * @param integer $post_id Optional. Post ID to query connection status for: will use current post if missing.
677     *
678     * @return bool True if post has already been shared by Publicize, false otherwise.
679     */
680    public function post_is_done_sharing( $post_id = null ) {
681        // Defaults to current post if $post_id is null.
682        $post = get_post( $post_id );
683        if ( null === $post ) {
684            return false;
685        }
686
687        return 'publish' === $post->post_status || get_post_meta( $post->ID, $this->POST_DONE . 'all', true ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
688    }
689
690    /**
691     * Save a flag locally to indicate that this post has already been Publicized via the selected
692     * connections.
693     *
694     * @param int     $post_ID Post ID.
695     * @param WP_Post $post Post object.
696     */
697    public function save_publicized( $post_ID, $post = null ) {
698        if ( null === $post ) {
699            return;
700        }
701        // Only do this when a post transitions to being published.
702        // phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
703        if ( get_post_meta( $post->ID, $this->PENDING, false ) && $this->post_type_is_publicizeable( $post->post_type ) ) {
704            delete_post_meta( $post->ID, $this->PENDING );
705            update_post_meta( $post->ID, $this->POST_DONE . 'all', true );
706        }
707        // phpcs:enable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
708    }
709
710    /**
711     * Set post flags for Publicize.
712     *
713     * @param array   $flags List of flags.
714     * @param WP_Post $post Post object.
715     * @return array
716     */
717    public function set_post_flags( $flags, $post ) {
718        $flags['publicize_post'] = false;
719        if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) {
720            return $flags;
721        }
722
723        $should_publicize = $this->should_submit_post_pre_checks( $post );
724
725        /** This filter is already documented in modules/publicize/publicize-jetpack.php */
726        if ( ! apply_filters( 'publicize_should_publicize_published_post', $should_publicize, $post ) ) {
727            return $flags;
728        }
729
730        $connected_services = $this->get_all_connections();
731
732        if ( empty( $connected_services ) ) {
733            return $flags;
734        }
735
736        $flags['publicize_post'] = true;
737
738        return $flags;
739    }
740}