PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 11.0
Jetpack – WP Security, Backup, Speed, & Growth v11.0
16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 14.1.1 14.2.2 14.3.1 All 501 releases
jetpack / modules / publicize / publicize.php
publicize.php
1,466 lines 46.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Publicize_Base class.
4 *
5 * @package automattic/jetpack
6 */
7
8 // phpcs:disable WordPress.NamingConventions.ValidVariableName
9
10 use Automattic\Jetpack\Redirect;
11 use Automattic\Jetpack\Status;
12
13 /**
14 * Base class for Publicize.
15 */
16 abstract class Publicize_Base {
17
18 /**
19 * Services that are currently connected to the given user
20 * through Publicize.
21 *
22 * @var array
23 */
24 public $connected_services = array();
25
26 /**
27 * Services that are supported by publicize. They don't
28 * necessarily need to be connected to the current user.
29 *
30 * @var array
31 */
32 public $services;
33
34 /**
35 * Post meta key for admin page.
36 *
37 * @var string
38 */
39 public $ADMIN_PAGE = 'wpas';
40
41 /**
42 * Post meta key for post message.
43 *
44 * @var string
45 */
46 public $POST_MESS = '_wpas_mess';
47
48 /**
49 * Post meta key for flagging when the post is a tweetstorm.
50 *
51 * @var string
52 */
53 public $POST_TWEETSTORM = '_wpas_is_tweetstorm';
54
55 /**
56 * Post meta key for the flagging when the post share feature is disabled.
57 *
58 * @var string
59 */
60 const POST_PUBLICIZE_FEATURE_ENABLED = '_wpas_feature_enabled';
61
62 /**
63 * Connection ID appended to indicate that a connection should NOT be publicized to.
64 *
65 * @var string
66 */
67 public $POST_SKIP = '_wpas_skip_';
68
69 /**
70 * Connection ID appended to indicate a connection has already been publicized to.
71 *
72 * @var string
73 */
74 public $POST_DONE = '_wpas_done_';
75
76 /**
77 * Prefix for user authorization (used in publicize-wpcom.php)
78 *
79 * @var string
80 */
81 public $USER_AUTH = 'wpas_authorize';
82
83 /**
84 * Prefix for user opt.
85 *
86 * @var string
87 */
88 public $USER_OPT = 'wpas_';
89
90 /**
91 * Ready for Publicize to do its thing.
92 *
93 * @var string
94 */
95 public $PENDING = '_publicize_pending';
96
97 /**
98 * Array of external IDs where we've Publicized.
99 *
100 * @var string
101 */
102 public $POST_SERVICE_DONE = '_publicize_done_external';
103
104 /**
105 * Default pieces of the message used in constructing the
106 * content pushed out to other social networks.
107 */
108
109 /**
110 * Default prefix.
111 *
112 * @var string
113 */
114 public $default_prefix = '';
115
116 /**
117 * Default message.
118 *
119 * @var string
120 */
121 public $default_message = '%title%';
122
123 /**
124 * Default suffix.
125 *
126 * @var string
127 */
128 public $default_suffix = ' ';
129
130 /**
131 * What WP capability is require to create/delete global connections?
132 * All users with this cap can un-globalize all other global connections, and globalize any of their own
133 * Globalized connections cannot be unselected by users without this capability when publishing
134 *
135 * @var string
136 */
137 public $GLOBAL_CAP = 'publish_posts';
138
139 /**
140 * Sets up the basics of Publicize.
141 */
142 public function __construct() {
143 $this->default_message = self::build_sprintf(
144 array(
145 /**
146 * Filter the default Publicize message.
147 *
148 * @module publicize
149 *
150 * @since 2.0.0
151 *
152 * @param string $this->default_message Publicize's default message. Default is the post title.
153 */
154 apply_filters( 'wpas_default_message', $this->default_message ),
155 'title',
156 'url',
157 )
158 );
159
160 $this->default_prefix = self::build_sprintf(
161 array(
162 /**
163 * Filter the message prepended to the Publicize custom message.
164 *
165 * @module publicize
166 *
167 * @since 2.0.0
168 *
169 * @param string $this->default_prefix String prepended to the Publicize custom message.
170 */
171 apply_filters( 'wpas_default_prefix', $this->default_prefix ),
172 'url',
173 )
174 );
175
176 $this->default_suffix = self::build_sprintf(
177 array(
178 /**
179 * Filter the message appended to the Publicize custom message.
180 *
181 * @module publicize
182 *
183 * @since 2.0.0
184 *
185 * @param string $this->default_suffix String appended to the Publicize custom message.
186 */
187 apply_filters( 'wpas_default_suffix', $this->default_suffix ),
188 'url',
189 )
190 );
191
192 /**
193 * Filter the capability to change global Publicize connection options.
194 *
195 * All users with this cap can un-globalize all other global connections, and globalize any of their own
196 * Globalized connections cannot be unselected by users without this capability when publishing.
197 *
198 * @module publicize
199 *
200 * @since 2.2.1
201 *
202 * @param string $this->GLOBAL_CAP default capability in control of global Publicize connection options. Default to edit_others_posts.
203 */
204 $this->GLOBAL_CAP = apply_filters( 'jetpack_publicize_global_connections_cap', $this->GLOBAL_CAP );
205
206 // stage 1 and 2 of 3-stage Publicize. Flag for Publicize on creation, save meta,
207 // then check meta and publicize based on that. stage 3 implemented on wpcom.
208 add_action( 'transition_post_status', array( $this, 'flag_post_for_publicize' ), 10, 3 );
209 add_action( 'save_post', array( $this, 'save_meta' ), 20, 2 );
210
211 // Default checkbox state for each Connection.
212 add_filter( 'publicize_checkbox_default', array( $this, 'publicize_checkbox_default' ), 10, 2 );
213
214 // Alter the "Post Publish" admin notice to mention the Connections we Publicized to.
215 add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 20, 1 );
216
217 // Connection test callback.
218 add_action( 'wp_ajax_test_publicize_conns', array( $this, 'test_publicize_conns' ) );
219
220 add_action( 'init', array( $this, 'add_post_type_support' ) );
221 add_action( 'init', array( $this, 'register_post_meta' ), 20 );
222 add_action( 'jetpack_register_gutenberg_extensions', array( $this, 'register_gutenberg_extension' ) );
223 }
224
225 /**
226 * Services: Facebook, Twitter, etc.
227 */
228
229 /**
230 * Get services for the given blog and user.
231 *
232 * Can return all available services or just the ones with an active connection.
233 *
234 * @param string $filter Type of filter.
235 * 'all' (default) - Get all services available for connecting.
236 * 'connected' - Get all services currently connected.
237 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
238 * @param false|int $_user_id The user ID. Use false (default) for the current user.
239 * @return array
240 */
241 abstract public function get_services( $filter = 'all', $_blog_id = false, $_user_id = false );
242
243 /**
244 * Does the given user have a connection to the service on the given blog?
245 *
246 * @param string $service_name 'facebook', 'twitter', etc.
247 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
248 * @param false|int $_user_id The user ID. Use false (default) for the current user.
249 * @return bool
250 */
251 public function is_enabled( $service_name, $_blog_id = false, $_user_id = false ) {
252 if ( ! $_blog_id ) {
253 $_blog_id = $this->blog_id();
254 }
255
256 if ( ! $_user_id ) {
257 $_user_id = $this->user_id();
258 }
259
260 $connections = $this->get_connections( $service_name, $_blog_id, $_user_id );
261 return ( is_array( $connections ) && count( $connections ) > 0 ? true : false );
262 }
263
264 /**
265 * Generates a connection URL.
266 *
267 * This is the URL, which, when visited by the user, starts the authentication
268 * process required to forge a connection.
269 *
270 * @param string $service_name 'facebook', 'twitter', etc.
271 * @return string
272 */
273 abstract public function connect_url( $service_name );
274
275 /**
276 * Generates a Connection refresh URL.
277 *
278 * This is the URL, which, when visited by the user, re-authenticates their
279 * connection to the service.
280 *
281 * @param string $service_name 'facebook', 'twitter', etc.
282 * @return string
283 */
284 abstract public function refresh_url( $service_name );
285
286 /**
287 * Generates a disconnection URL.
288 *
289 * This is the URL, which, when visited by the user, breaks their connection
290 * with the service.
291 *
292 * @param string $service_name 'facebook', 'twitter', etc.
293 * @param string $connection_id Connection ID.
294 * @return string
295 */
296 abstract public function disconnect_url( $service_name, $connection_id );
297
298 /**
299 * Returns a display name for the Service
300 *
301 * @param string $service_name 'facebook', 'twitter', etc.
302 * @return string
303 */
304 public static function get_service_label( $service_name ) {
305 switch ( $service_name ) {
306 case 'linkedin':
307 return 'LinkedIn';
308 case 'google_drive': // google-drive used to be called google_drive.
309 case 'google-drive':
310 return 'Google Drive';
311 case 'twitter':
312 case 'facebook':
313 case 'tumblr':
314 default:
315 return ucfirst( $service_name );
316 }
317 }
318
319 /**
320 * Connections: For each Service, there can be multiple connections
321 * for a given user. For example, one user could be connected to Twitter
322 * as both @jetpack and as @wordpressdotcom
323 *
324 * For historical reasons, Connections are represented as an object
325 * on WordPress.com and as an array in Jetpack.
326 */
327
328 /**
329 * Get the active Connections of a Service
330 *
331 * @param string $service_name 'facebook', 'twitter', etc.
332 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
333 * @param false|int $_user_id The user ID. Use false (default) for the current user.
334 * @return false|object[]|array[] false if no connections exist
335 */
336 abstract public function get_connections( $service_name, $_blog_id = false, $_user_id = false );
337
338 /**
339 * Get a single Connection of a Service
340 *
341 * @param string $service_name 'facebook', 'twitter', etc.
342 * @param string $connection_id Connection ID.
343 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
344 * @param false|int $_user_id The user ID. Use false (default) for the current user.
345 * @return false|object[]|array[] false if no connections exist
346 */
347 abstract public function get_connection( $service_name, $connection_id, $_blog_id = false, $_user_id = false );
348
349 /**
350 * Get the Connection ID.
351 *
352 * Note that this is different than the Connection's uniqueid.
353 *
354 * Via a quirk of history, ID is globally unique and unique_id
355 * is only unique per site.
356 *
357 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
358 * @return string
359 */
360 abstract public function get_connection_id( $connection );
361
362 /**
363 * Get the Connection unique_id
364 *
365 * Note that this is different than the Connections ID.
366 *
367 * Via a quirk of history, ID is globally unique and unique_id
368 * is only unique per site.
369 *
370 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
371 * @return string
372 */
373 abstract public function get_connection_unique_id( $connection );
374
375 /**
376 * Get the Connection's Meta data
377 *
378 * @param object|array $connection Connection.
379 * @return array Connection Meta
380 */
381 abstract public function get_connection_meta( $connection );
382
383 /**
384 * Disconnect a Connection
385 *
386 * @param string $service_name 'facebook', 'twitter', etc.
387 * @param string $connection_id Connection ID.
388 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog.
389 * @param false|int $_user_id The user ID. Use false (default) for the current user.
390 * @param bool $force_delete Whether to skip permissions checks.
391 * @return false|void False on failure. Void on success.
392 */
393 abstract public function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false );
394
395 /**
396 * Globalizes a Connection
397 *
398 * @param string $connection_id Connection ID.
399 * @return bool Falsey on failure. Truthy on success.
400 */
401 abstract public function globalize_connection( $connection_id );
402
403 /**
404 * Unglobalizes a Connection
405 *
406 * @param string $connection_id Connection ID.
407 * @return bool Falsey on failure. Truthy on success.
408 */
409 abstract public function unglobalize_connection( $connection_id );
410
411 /**
412 * Returns an external URL to the Connection's profile
413 *
414 * @param string $service_name 'facebook', 'twitter', etc.
415 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
416 * @return false|string False on failure. URL on success.
417 */
418 public function get_profile_link( $service_name, $connection ) {
419 $cmeta = $this->get_connection_meta( $connection );
420
421 if ( isset( $cmeta['connection_data']['meta']['link'] ) ) {
422 if ( 'facebook' === $service_name && 0 === strpos( wp_parse_url( $cmeta['connection_data']['meta']['link'], PHP_URL_PATH ), '/app_scoped_user_id/' ) ) {
423 // App-scoped Facebook user IDs are not usable profile links.
424 return false;
425 }
426
427 return $cmeta['connection_data']['meta']['link'];
428 }
429
430 if ( 'facebook' === $service_name && isset( $cmeta['connection_data']['meta']['facebook_page'] ) ) {
431 return 'https://facebook.com/' . $cmeta['connection_data']['meta']['facebook_page'];
432 }
433
434 if ( 'tumblr' === $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
435 return 'https://' . $cmeta['connection_data']['meta']['tumblr_base_hostname'];
436 }
437
438 if ( 'twitter' === $service_name ) {
439 return 'https://twitter.com/' . substr( $cmeta['external_display'], 1 ); // Has a leading '@'.
440 }
441
442 if ( 'linkedin' === $service_name ) {
443 if ( ! isset( $cmeta['connection_data']['meta']['profile_url'] ) ) {
444 return false;
445 }
446
447 $profile_url_query = wp_parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY );
448 wp_parse_str( $profile_url_query, $profile_url_query_args );
449
450 $id = null;
451
452 if ( isset( $profile_url_query_args['key'] ) ) {
453 $id = $profile_url_query_args['key'];
454 } elseif ( isset( $profile_url_query_args['id'] ) ) {
455 $id = $profile_url_query_args['id'];
456 } else {
457 return false;
458 }
459
460 return esc_url_raw( add_query_arg( 'id', rawurlencode( $id ), 'https://www.linkedin.com/profile/view' ) );
461 }
462
463 return false; // no fallback. we just won't link it.
464 }
465
466 /**
467 * Returns a display name for the Connection
468 *
469 * @param string $service_name 'facebook', 'twitter', etc.
470 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
471 * @return string
472 */
473 public function get_display_name( $service_name, $connection ) {
474 $cmeta = $this->get_connection_meta( $connection );
475
476 if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) {
477 return $cmeta['connection_data']['meta']['display_name'];
478 }
479
480 if ( 'tumblr' === $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
481 return $cmeta['connection_data']['meta']['tumblr_base_hostname'];
482 }
483
484 if ( 'twitter' === $service_name ) {
485 return $cmeta['external_display'];
486 }
487
488 $connection_display = $cmeta['external_display'];
489
490 if ( empty( $connection_display ) ) {
491 $connection_display = $cmeta['external_name'];
492 }
493
494 return $connection_display;
495 }
496
497 /**
498 * Returns a profile picture for the Connection
499 *
500 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
501 * @return string
502 */
503 private function get_profile_picture( $connection ) {
504 $cmeta = $this->get_connection_meta( $connection );
505
506 if ( isset( $cmeta['profile_picture'] ) ) {
507 return $cmeta['profile_picture'];
508 }
509
510 return '';
511 }
512
513 /**
514 * Whether the user needs to select additional options after connecting
515 *
516 * @param string $service_name 'facebook', 'twitter', etc.
517 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
518 * @return bool
519 */
520 public function show_options_popup( $service_name, $connection ) {
521 $cmeta = $this->get_connection_meta( $connection );
522
523 // Always show if no selection has been made for Facebook.
524 if ( 'facebook' === $service_name && empty( $cmeta['connection_data']['meta']['facebook_profile'] ) && empty( $cmeta['connection_data']['meta']['facebook_page'] ) ) {
525 return true;
526 }
527
528 // Always show if no selection has been made for Tumblr.
529 if ( 'tumblr' === $service_name && empty( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
530 return true;
531 }
532
533 // if we have the specific connection info..
534 $id = ! empty( $_GET['id'] ) ? sanitize_text_field( wp_unslash( $_GET['id'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
535
536 if ( $id ) {
537 if ( $cmeta['connection_data']['id'] === $id ) {
538 return true;
539 }
540 } else {
541 // Otherwise, just show if this is the completed step / first load.
542 // phpcs:disable WordPress.Security.NonceVerification.Recommended
543 $is_completed = ! empty( $_GET['action'] ) && 'completed' === $_GET['action'];
544 $service = ! empty( $_GET['service'] ) ? sanitize_text_field( wp_unslash( $_GET['service'] ) ) : false;
545 // phpcs:enable WordPress.Security.NonceVerification.Recommended
546
547 if ( $is_completed && $service_name === $service && ! in_array( $service, array( 'facebook', 'tumblr' ), true ) ) {
548 return true;
549 }
550 }
551
552 return false;
553 }
554
555 /**
556 * Check if a connection is global
557 *
558 * @param array $connection Connection data.
559 * @return bool Whether the connection is global.
560 */
561 public function is_global_connection( $connection ) {
562 return empty( $connection['connection_data']['user_id'] );
563 }
564
565 /**
566 * Whether the Connection is "valid" wrt Facebook's requirements.
567 *
568 * Must be connected to a Page (not a Profile).
569 * (Also returns true if we're in the middle of the connection process)
570 *
571 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
572 * @return bool
573 */
574 public function is_valid_facebook_connection( $connection ) {
575 if ( $this->is_connecting_connection( $connection ) ) {
576 return true;
577 }
578 $connection_meta = $this->get_connection_meta( $connection );
579 $connection_data = $connection_meta['connection_data'];
580 return isset( $connection_data['meta']['facebook_page'] );
581 }
582
583 /**
584 * LinkedIn needs to be reauthenticated to use v2 of their API.
585 * If it's using LinkedIn old API, it's an 'invalid' connection
586 *
587 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
588 * @return bool
589 */
590 public function is_invalid_linkedin_connection( $connection ) {
591 // LinkedIn API v1 included the profile link in the connection data.
592 $connection_meta = $this->get_connection_meta( $connection );
593 return isset( $connection_meta['connection_data']['meta']['profile_url'] );
594 }
595
596 /**
597 * Whether the Connection currently being connected
598 *
599 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
600 * @return bool
601 */
602 public function is_connecting_connection( $connection ) {
603 $connection_meta = $this->get_connection_meta( $connection );
604 $connection_data = $connection_meta['connection_data'];
605 return isset( $connection_data['meta']['options_responses'] );
606 }
607
608 /**
609 * AJAX Handler to run connection tests on all Connections
610 *
611 * @return void
612 */
613 public function test_publicize_conns() {
614 wp_send_json_success( $this->get_publicize_conns_test_results() );
615 }
616
617 /**
618 * Run connection tests on all Connections
619 *
620 * @return array {
621 * Array of connection test results.
622 *
623 * @type string 'connectionID' Connection identifier string that is unique for each connection
624 * @type string 'serviceName' Slug of the connection's service (facebook, twitter, ...)
625 * @type bool 'connectionTestPassed' Whether the connection test was successful
626 * @type string 'connectionTestMessage' Test success or error message
627 * @type bool 'userCanRefresh' Whether the user can re-authenticate their connection to the service
628 * @type string 'refreshText' Message instructing user to re-authenticate their connection to the service
629 * @type string 'refreshURL' URL, which, when visited by the user, re-authenticates their connection to the service.
630 * @type string 'unique_id' ID string representing connection
631 * }
632 */
633 public function get_publicize_conns_test_results() {
634 $test_results = array();
635
636 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
637 foreach ( $connections as $connection ) {
638
639 $id = $this->get_connection_id( $connection );
640
641 $connection_test_passed = true;
642 $connection_test_message = __( 'This connection is working correctly.', 'jetpack' );
643 $user_can_refresh = false;
644 $refresh_text = '';
645 $refresh_url = '';
646
647 $connection_test_result = true;
648 if ( method_exists( $this, 'test_connection' ) ) {
649 $connection_test_result = $this->test_connection( $service_name, $connection );
650 }
651
652 if ( is_wp_error( $connection_test_result ) ) {
653 $connection_test_passed = false;
654 $connection_test_message = $connection_test_result->get_error_message();
655 $error_data = $connection_test_result->get_error_data();
656
657 $user_can_refresh = $error_data['user_can_refresh'];
658 $refresh_text = $error_data['refresh_text'];
659 $refresh_url = $error_data['refresh_url'];
660 }
661 // Mark Facebook profiles as deprecated.
662 if ( 'facebook' === $service_name ) {
663 if ( ! $this->is_valid_facebook_connection( $connection ) ) {
664 $connection_test_passed = false;
665 $user_can_refresh = false;
666 $connection_test_message = __( 'Please select a Facebook Page to publish updates.', 'jetpack' );
667 }
668 }
669
670 // LinkedIn needs reauthentication to be compatible with v2 of their API.
671 if ( 'linkedin' === $service_name && $this->is_invalid_linkedin_connection( $connection ) ) {
672 $connection_test_passed = 'must_reauth';
673 $user_can_refresh = false;
674 $connection_test_message = esc_html__( 'Your LinkedIn connection needs to be reauthenticated to continue working – head to Sharing to take care of it.', 'jetpack' );
675 }
676
677 $unique_id = null;
678
679 if ( ! empty( $connection->unique_id ) ) {
680 $unique_id = $connection->unique_id;
681 } elseif ( ! empty( $connection['connection_data']['token_id'] ) ) {
682 $unique_id = $connection['connection_data']['token_id'];
683 }
684
685 $test_results[] = array(
686 'connectionID' => $id,
687 'serviceName' => $service_name,
688 'connectionTestPassed' => $connection_test_passed,
689 'connectionTestMessage' => esc_attr( $connection_test_message ),
690 'userCanRefresh' => $user_can_refresh,
691 'refreshText' => esc_attr( $refresh_text ),
692 'refreshURL' => $refresh_url,
693 'unique_id' => $unique_id,
694 );
695 }
696 }
697
698 return $test_results;
699 }
700
701 /**
702 * Run the connection test for the Connection
703 *
704 * @param string $service_name $service_name 'facebook', 'twitter', etc.
705 * @param object|array $connection The Connection object (WordPress.com) or array (Jetpack).
706 * @return WP_Error|true WP_Error on failure. True on success
707 */
708 abstract public function test_connection( $service_name, $connection );
709
710 /**
711 * Retrieves current list of connections and applies filters.
712 *
713 * Retrieves current available connections and checks if the connections
714 * have already been used to share current post. Finally, the checkbox
715 * form UI fields are calculated. This function exposes connection form
716 * data directly as array so it can be retrieved for static HTML generation
717 * or JSON consumption.
718 *
719 * @since 6.7.0
720 *
721 * @param integer $selected_post_id Optional. Post ID to query connection status for.
722 *
723 * @return array {
724 * Array of UI setup data for connection list form.
725 *
726 * @type string 'unique_id' ID string representing connection
727 * @type string 'service_name' Slug of the connection's service (facebook, twitter, ...)
728 * @type string 'service_label' Service Label (Facebook, Twitter, ...)
729 * @type string 'display_name' Connection's human-readable Username: "@jetpack"
730 * @type string 'profile_picture' Connection profile picture.
731 * @type bool 'enabled' Default value for the connection (e.g., for a checkbox).
732 * @type bool 'done' Has this connection already been publicized to?
733 * @type bool 'toggleable' Is the user allowed to change the value for the connection?
734 * @type bool 'global' Is this connection a global one?
735 * }
736 */
737 public function get_filtered_connection_data( $selected_post_id = null ) {
738 $connection_list = array();
739
740 $post = get_post( $selected_post_id ); // Defaults to current post if $post_id is null.
741 // Handle case where there is no current post.
742 if ( ! empty( $post ) ) {
743 $post_id = $post->ID;
744 } else {
745 $post_id = null;
746 }
747
748 $services = $this->get_services( 'connected' );
749 $all_done = $this->post_is_done_sharing( $post_id );
750
751 // We don't allow Publicizing to the same external id twice, to prevent spam.
752 $service_id_done = (array) get_post_meta( $post_id, $this->POST_SERVICE_DONE, true );
753
754 foreach ( $services as $service_name => $connections ) {
755 foreach ( $connections as $connection ) {
756 $connection_meta = $this->get_connection_meta( $connection );
757 $connection_data = $connection_meta['connection_data'];
758
759 $unique_id = $this->get_connection_unique_id( $connection );
760
761 // Was this connection (OR, old-format service) already Publicized to?
762 $done = ! empty( $post ) && (
763 // New flags.
764 1 === (int) get_post_meta( $post->ID, $this->POST_DONE . $unique_id, true )
765 ||
766 // Old flags.
767 1 === (int) get_post_meta( $post->ID, $this->POST_DONE . $service_name, true )
768 );
769
770 /**
771 * Filter whether a post should be publicized to a given service.
772 *
773 * @module publicize
774 *
775 * @since 2.0.0
776 *
777 * @param bool true Should the post be publicized to a given service? Default to true.
778 * @param int $post_id Post ID.
779 * @param string $service_name Service name.
780 * @param array $connection_data Array of information about all Publicize details for the site.
781 */
782 /* phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores */
783 if ( ! apply_filters( 'wpas_submit_post?', true, $post_id, $service_name, $connection_data ) ) {
784 continue;
785 }
786
787 // Should we be skipping this one?
788 $skip = (
789 (
790 ! empty( $post )
791 &&
792 in_array( $post->post_status, array( 'publish', 'draft', 'future' ), true )
793 &&
794 (
795 // New flags.
796 get_post_meta( $post->ID, $this->POST_SKIP . $unique_id, true )
797 ||
798 // Old flags.
799 get_post_meta( $post->ID, $this->POST_SKIP . $service_name )
800 )
801 )
802 ||
803 (
804 is_array( $connection )
805 &&
806 isset( $connection_meta['external_id'] ) && ! empty( $service_id_done[ $service_name ][ $connection_meta['external_id'] ] )
807 )
808 );
809
810 // If this one has already been publicized to, don't let it happen again.
811 $toggleable = ! $done && ! $all_done;
812
813 // Determine the state of the checkbox (on/off) and allow filtering.
814 $enabled = $done || ! $skip;
815 /**
816 * Filter the checkbox state of each Publicize connection appearing in the post editor.
817 *
818 * @module publicize
819 *
820 * @since 2.0.1
821 *
822 * @param bool $enabled Should the Publicize checkbox be enabled for a given service.
823 * @param int $post_id Post ID.
824 * @param string $service_name Service name.
825 * @param array $connection Array of connection details.
826 */
827 $enabled = apply_filters( 'publicize_checkbox_default', $enabled, $post_id, $service_name, $connection );
828
829 /**
830 * If this is a global connection and this user doesn't have enough permissions to modify
831 * those connections, don't let them change it.
832 */
833 if ( ! $done && $this->is_global_connection( $connection_meta ) && ! current_user_can( $this->GLOBAL_CAP ) ) {
834 $toggleable = false;
835
836 /**
837 * Filters the checkboxes for global connections with non-prilvedged users.
838 *
839 * @module publicize
840 *
841 * @since 3.7.0
842 *
843 * @param bool $enabled Indicates if this connection should be enabled. Default true.
844 * @param int $post_id ID of the current post
845 * @param string $service_name Name of the connection (Facebook, Twitter, etc)
846 * @param array $connection Array of data about the connection.
847 */
848 $enabled = apply_filters( 'publicize_checkbox_global_default', $enabled, $post_id, $service_name, $connection );
849 }
850
851 // Force the checkbox to be checked if the post was DONE, regardless of what the filter does.
852 if ( $done ) {
853 $enabled = true;
854 }
855
856 $connection_list[] = array(
857 'unique_id' => $unique_id,
858 'service_name' => $service_name,
859 'service_label' => $this->get_service_label( $service_name ),
860 'display_name' => $this->get_display_name( $service_name, $connection ),
861 'profile_picture' => $this->get_profile_picture( $connection ),
862
863 'enabled' => $enabled,
864 'done' => $done,
865 'toggleable' => $toggleable,
866 'global' => 0 == $connection_data['user_id'], // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual,WordPress.PHP.StrictComparisons.LooseComparison -- Other types can be used at times.
867 );
868 }
869 }
870
871 return $connection_list;
872 }
873
874 /**
875 * Checks if post has already been shared by Publicize in the past.
876 *
877 * @since 6.7.0
878 *
879 * @param integer $post_id Optional. Post ID to query connection status for: will use current post if missing.
880 *
881 * @return bool True if post has already been shared by Publicize, false otherwise.
882 */
883 abstract public function post_is_done_sharing( $post_id = null );
884
885 /**
886 * Retrieves full list of available Publicize connection services.
887 *
888 * Retrieves current available publicize service connections
889 * with associated labels and URLs.
890 *
891 * @since 6.7.0
892 *
893 * @return array {
894 * Array of UI service connection data for all services
895 *
896 * @type string 'name' Name of service.
897 * @type string 'label' Display label for service.
898 * @type string 'url' URL for adding connection to service.
899 * }
900 */
901 public function get_available_service_data() {
902 $available_services = $this->get_services( 'all' );
903 $available_service_data = array();
904
905 foreach ( $available_services as $service_name => $service ) {
906 $available_service_data[] = array(
907 'name' => $service_name,
908 'label' => $this->get_service_label( $service_name ),
909 'url' => $this->connect_url( $service_name ),
910 );
911 }
912
913 return $available_service_data;
914 }
915
916 /**
917 * Site Data
918 */
919
920 /**
921 * Get user ID.
922 *
923 * @return int The current user's ID, or 0 if no user is logged in.
924 */
925 public function user_id() {
926 return get_current_user_id();
927 }
928
929 /**
930 * Get site ID.
931 *
932 * @return int Site ID.
933 */
934 public function blog_id() {
935 return get_current_blog_id();
936 }
937
938 /**
939 * Posts
940 */
941
942 /**
943 * Checks old and new status to see if the post should be flagged as
944 * ready to Publicize.
945 *
946 * Attached to the `transition_post_status` filter.
947 *
948 * @param string $new_status New status.
949 * @param string $old_status Old status.
950 * @param WP_Post $post Post object.
951 * @return void
952 */
953 abstract public function flag_post_for_publicize( $new_status, $old_status, $post );
954
955 /**
956 * Ensures the Post internal post-type supports `publicize`
957 *
958 * This feature support flag is used by the REST API.
959 */
960 public function add_post_type_support() {
961 add_post_type_support( 'post', 'publicize' );
962 }
963
964 /**
965 * Register the Publicize Gutenberg extension
966 */
967 public function register_gutenberg_extension() {
968 // TODO: The `gutenberg/available-extensions` endpoint currently doesn't accept a post ID,
969 // so we cannot pass one to `$this->current_user_can_access_publicize_data()`.
970
971 if ( $this->current_user_can_access_publicize_data() ) {
972 Jetpack_Gutenberg::set_extension_available( 'jetpack/publicize' );
973 } else {
974 Jetpack_Gutenberg::set_extension_unavailable( 'jetpack/publicize', 'unauthorized' );
975 }
976 }
977
978 /**
979 * Can the current user access Publicize Data.
980 *
981 * @param int $post_id 0 for general access. Post_ID for specific access.
982 * @return bool
983 */
984 public function current_user_can_access_publicize_data( $post_id = 0 ) {
985 /**
986 * Filter what user capability is required to use the publicize form on the edit post page. Useful if publish post capability has been removed from role.
987 *
988 * @module publicize
989 *
990 * @since 4.1.0
991 *
992 * @param string $capability User capability needed to use publicize
993 */
994 $capability = apply_filters( 'jetpack_publicize_capability', 'publish_posts' );
995
996 if ( 'publish_posts' === $capability && $post_id ) {
997 return current_user_can( 'publish_post', $post_id );
998 }
999
1000 return current_user_can( $capability );
1001 }
1002
1003 /**
1004 * Auth callback for the protected ->POST_MESS post_meta
1005 *
1006 * @param int $object_id Post ID.
1007 * @return bool
1008 */
1009 public function message_meta_auth_callback( $object_id ) {
1010 return $this->current_user_can_access_publicize_data( $object_id );
1011 }
1012
1013 /**
1014 * Registers the post_meta for use in the REST API.
1015 *
1016 * Registers for each post type that with `publicize` feature support.
1017 */
1018 public function register_post_meta() {
1019 $message_args = array(
1020 'type' => 'string',
1021 'description' => __( 'The message to use instead of the title when sharing to Publicize Services', 'jetpack' ),
1022 'single' => true,
1023 'default' => '',
1024 'show_in_rest' => array(
1025 'name' => 'jetpack_publicize_message',
1026 ),
1027 'auth_callback' => array( $this, 'message_meta_auth_callback' ),
1028 );
1029
1030 $tweetstorm_args = array(
1031 'type' => 'boolean',
1032 'description' => __( 'Whether or not the post should be treated as a Twitter thread.', 'jetpack' ),
1033 'single' => true,
1034 'default' => false,
1035 'show_in_rest' => array(
1036 'name' => 'jetpack_is_tweetstorm',
1037 ),
1038 'auth_callback' => array( $this, 'message_meta_auth_callback' ),
1039 );
1040
1041 $publicize_feature_enable_args = array(
1042 'type' => 'boolean',
1043 'description' => __( 'Whether or not the Share Post feature is enabled.', 'jetpack' ),
1044 'single' => true,
1045 'default' => true,
1046 'show_in_rest' => array(
1047 'name' => 'jetpack_publicize_feature_enabled',
1048 ),
1049 'auth_callback' => array( $this, 'message_meta_auth_callback' ),
1050 );
1051
1052 foreach ( get_post_types() as $post_type ) {
1053 if ( ! $this->post_type_is_publicizeable( $post_type ) ) {
1054 continue;
1055 }
1056
1057 $message_args['object_subtype'] = $post_type;
1058 $tweetstorm_args['object_subtype'] = $post_type;
1059 $publicize_feature_enable_args['object_subtype'] = $post_type;
1060
1061 register_meta( 'post', $this->POST_MESS, $message_args );
1062 register_meta( 'post', $this->POST_TWEETSTORM, $tweetstorm_args );
1063 register_meta( 'post', self::POST_PUBLICIZE_FEATURE_ENABLED, $publicize_feature_enable_args );
1064 }
1065 }
1066
1067 /**
1068 * Helper function to allow us to not publicize posts in certain contexts.
1069 *
1070 * @param WP_Post $post Post object.
1071 */
1072 public function should_submit_post_pre_checks( $post ) {
1073 $submit_post = true;
1074
1075 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
1076 $submit_post = false;
1077 }
1078
1079 if (
1080 defined( 'DOING_AUTOSAVE' )
1081 &&
1082 DOING_AUTOSAVE
1083 ) {
1084 $submit_post = false;
1085 }
1086
1087 // To prevent quick edits from getting publicized.
1088 if ( did_action( 'wp_ajax_inline-save' ) ) {
1089 $submit_post = false;
1090 }
1091
1092 // phpcs:disable WordPress.Security.NonceVerification.Recommended
1093 if ( ! empty( $_GET['bulk_edit'] ) ) {
1094 $submit_post = false;
1095 }
1096 // phpcs:enable WordPress.Security.NonceVerification.Recommended
1097
1098 // - API/XML-RPC Test Posts
1099 if (
1100 (
1101 defined( 'XMLRPC_REQUEST' )
1102 &&
1103 XMLRPC_REQUEST
1104 ||
1105 defined( 'APP_REQUEST' )
1106 &&
1107 APP_REQUEST
1108 )
1109 &&
1110 0 === strpos( $post->post_title, 'Temporary Post Used For Theme Detection' )
1111 ) {
1112 $submit_post = false;
1113 }
1114
1115 // Only work with certain statuses (avoids inherits, auto drafts etc).
1116 if ( ! in_array( $post->post_status, array( 'publish', 'draft', 'future' ), true ) ) {
1117 $submit_post = false;
1118 }
1119
1120 // Don't publish password protected posts.
1121 if ( '' !== $post->post_password ) {
1122 $submit_post = false;
1123 }
1124
1125 return $submit_post;
1126 }
1127
1128 /**
1129 * Fires when a post is saved, checks conditions and saves state in postmeta so that it
1130 * can be picked up later by @see ::publicize_post() on WordPress.com codebase.
1131 *
1132 * Attached to the `save_post` action.
1133 *
1134 * @param int $post_id Post ID.
1135 * @param WP_Post $post Post object.
1136 */
1137 public function save_meta( $post_id, $post ) {
1138 $cron_user = null;
1139 $submit_post = true;
1140
1141 if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) {
1142 return;
1143 }
1144
1145 $submit_post = $this->should_submit_post_pre_checks( $post );
1146
1147 // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- We're only checking if a value is set
1148 $admin_page = isset( $_POST[ $this->ADMIN_PAGE ] ) ? $_POST[ $this->ADMIN_PAGE ] : null;
1149
1150 // Did this request happen via wp-admin?
1151 $from_web = isset( $_SERVER['REQUEST_METHOD'] )
1152 &&
1153 'post' === strtolower( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) )
1154 &&
1155 ! empty( $admin_page );
1156
1157 // phpcs:ignore WordPress.Security.NonceVerification.Missing
1158 $title = isset( $_POST['wpas_title'] ) ? sanitize_textarea_field( wp_unslash( $_POST['wpas_title'] ) ) : null;
1159
1160 if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && $title ) {
1161 if ( empty( $title ) ) {
1162 delete_post_meta( $post_id, $this->POST_MESS );
1163 } else {
1164 update_post_meta( $post_id, $this->POST_MESS, trim( stripslashes( $title ) ) );
1165 }
1166 }
1167
1168 // Change current user to provide context for get_services() if we're running during cron.
1169 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
1170 $cron_user = (int) $GLOBALS['user_ID'];
1171 wp_set_current_user( $post->post_author );
1172 }
1173
1174 /**
1175 * In this phase, we mark connections that we want to SKIP. When Publicize is actually triggered,
1176 * it will Publicize to everything *except* those marked for skipping.
1177 */
1178 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
1179 foreach ( $connections as $connection ) {
1180 $connection_data = '';
1181 if ( is_object( $connection ) && method_exists( $connection, 'get_meta' ) ) {
1182 $connection_data = $connection->get_meta( 'connection_data' );
1183 } elseif ( ! empty( $connection['connection_data'] ) ) {
1184 $connection_data = $connection['connection_data'];
1185 }
1186
1187 /** This action is documented in modules/publicize/ui.php */
1188 /* phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores */
1189 if ( false === apply_filters( 'wpas_submit_post?', $submit_post, $post_id, $service_name, $connection_data ) ) {
1190 delete_post_meta( $post_id, $this->PENDING );
1191 continue;
1192 }
1193
1194 if ( ! empty( $connection->unique_id ) ) {
1195 $unique_id = $connection->unique_id;
1196 } elseif ( ! empty( $connection['connection_data']['token_id'] ) ) {
1197 $unique_id = $connection['connection_data']['token_id'];
1198 }
1199
1200 // This was a wp-admin request, so we need to check the state of checkboxes.
1201 if ( $from_web ) {
1202 // Delete stray service-based post meta.
1203 delete_post_meta( $post_id, $this->POST_SKIP . $service_name );
1204
1205 // We *unchecked* this stream from the admin page, or it's set to readonly, or it's a new addition.
1206 if ( empty( $admin_page['submit'][ $unique_id ] ) ) {
1207 // Also make sure that the service-specific input isn't there.
1208 // If the user connected to a new service 'in-page' then a hidden field with the service
1209 // name is added, so we just assume they wanted to Publicize to that service.
1210 if ( empty( $admin_page['submit'][ $service_name ] ) ) {
1211 // Nothing seems to be checked, so we're going to mark this one to be skipped.
1212 update_post_meta( $post_id, $this->POST_SKIP . $unique_id, 1 );
1213 continue;
1214 } else {
1215 // Clean up any stray post meta.
1216 delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
1217 }
1218 } else {
1219 // The checkbox for this connection is explicitly checked -- make sure we DON'T skip it.
1220 delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
1221 }
1222 }
1223
1224 /**
1225 * Fires right before the post is processed for Publicize.
1226 * Users may hook in here and do anything else they need to after meta is written,
1227 * and before the post is processed for Publicize.
1228 *
1229 * @since 2.1.2
1230 *
1231 * @param bool $submit_post Should the post be publicized.
1232 * @param int $post->ID Post ID.
1233 * @param string $service_name Service name.
1234 * @param array $connection Array of connection details.
1235 */
1236 do_action( 'publicize_save_meta', $submit_post, $post_id, $service_name, $connection );
1237 }
1238 }
1239
1240 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
1241 wp_set_current_user( $cron_user );
1242 }
1243
1244 // Next up will be ::publicize_post().
1245 }
1246
1247 /**
1248 * Alters the "Post Published" message to include information about where the post
1249 * was Publicized to.
1250 *
1251 * Attached to the `post_updated_messages` filter
1252 *
1253 * @param string[] $messages Array of messages.
1254 * @return string[]
1255 */
1256 public function update_published_message( $messages ) {
1257 global $post_type, $post_type_object, $post;
1258 if ( ! $this->post_type_is_publicizeable( $post_type ) ) {
1259 return $messages;
1260 }
1261
1262 // Bail early if the post is private.
1263 if ( 'publish' !== $post->post_status ) {
1264 return $messages;
1265 }
1266
1267 $view_post_link_html = '';
1268 $viewable = is_post_type_viewable( $post_type_object );
1269 if ( $viewable ) {
1270 /* phpcs:ignore WordPress.WP.I18n.MissingArgDomain, WordPress.Utils.I18nTextDomainFixer.MissingArgDomain */
1271 $view_text = esc_html__( 'View post' ); // Intentionally omitted domain.
1272
1273 if ( 'jetpack-portfolio' === $post_type ) {
1274 $view_text = esc_html__( 'View project', 'jetpack' );
1275 }
1276
1277 $view_post_link_html = sprintf(
1278 ' <a href="%1$s">%2$s</a>',
1279 esc_url( get_permalink( $post ) ),
1280 $view_text
1281 );
1282 }
1283
1284 $services = $this->get_publicizing_services( $post->ID );
1285 if ( empty( $services ) ) {
1286 return $messages;
1287 }
1288
1289 $labels = array();
1290 foreach ( $services as $service_name => $display_names ) {
1291 $labels[] = sprintf(
1292 /* translators: Service name is %1$s, and account name is %2$s. */
1293 esc_html__( '%1$s (%2$s)', 'jetpack' ),
1294 esc_html( $service_name ),
1295 esc_html( is_array( $display_names ) ? implode( ', ', $display_names ) : $display_names )
1296 );
1297 }
1298
1299 $messages['post'][6] = sprintf(
1300 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1301 esc_html__( 'Post published and sharing on %1$s.', 'jetpack' ),
1302 implode( ', ', $labels )
1303 ) . $view_post_link_html;
1304
1305 if ( 'post' === $post_type && class_exists( 'Jetpack_Subscriptions' ) ) {
1306 $subscription = Jetpack_Subscriptions::init();
1307 if ( $subscription->should_email_post_to_subscribers( $post ) ) {
1308 $messages['post'][6] = sprintf(
1309 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1310 esc_html__( 'Post published, sending emails to subscribers and sharing post on %1$s.', 'jetpack' ),
1311 implode( ', ', $labels )
1312 ) . $view_post_link_html;
1313 }
1314 }
1315
1316 $messages['jetpack-portfolio'][6] = sprintf(
1317 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1318 esc_html__( 'Project published and sharing project on %1$s.', 'jetpack' ),
1319 implode( ', ', $labels )
1320 ) . $view_post_link_html;
1321
1322 return $messages;
1323 }
1324
1325 /**
1326 * Get the Connections the Post was just Publicized to.
1327 *
1328 * Only reliable just after the Post was published.
1329 *
1330 * @param int $post_id Post ID.
1331 * @return string[] Array of Service display name => Connection display name
1332 */
1333 public function get_publicizing_services( $post_id ) {
1334 $services = array();
1335
1336 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
1337 // services have multiple connections.
1338 foreach ( $connections as $connection ) {
1339 $unique_id = '';
1340 if ( ! empty( $connection->unique_id ) ) {
1341 $unique_id = $connection->unique_id;
1342 } elseif ( ! empty( $connection['connection_data']['token_id'] ) ) {
1343 $unique_id = $connection['connection_data']['token_id'];
1344 }
1345
1346 // Did we skip this connection?
1347 if ( get_post_meta( $post_id, $this->POST_SKIP . $unique_id, true ) ) {
1348 continue;
1349 }
1350 $services[ $this->get_service_label( $service_name ) ][] = $this->get_display_name( $service_name, $connection );
1351 }
1352 }
1353
1354 return $services;
1355 }
1356
1357 /**
1358 * Is the post Publicize-able?
1359 *
1360 * Only valid prior to Publicizing a Post.
1361 *
1362 * @param WP_Post $post Post to check.
1363 * @return bool
1364 */
1365 public function post_is_publicizeable( $post ) {
1366 if ( ! $this->post_type_is_publicizeable( $post->post_type ) ) {
1367 return false;
1368 }
1369
1370 // This is more a precaution. To only publicize posts that are published. (Mostly relevant for Jetpack sites).
1371 if ( 'publish' !== $post->post_status ) {
1372 return false;
1373 }
1374
1375 // If it's not flagged as ready, then abort. @see ::flag_post_for_publicize().
1376 if ( ! get_post_meta( $post->ID, $this->PENDING, true ) ) {
1377 return false;
1378 }
1379
1380 return true;
1381 }
1382
1383 /**
1384 * Is a given post type Publicize-able?
1385 *
1386 * Not every CPT lends itself to Publicize-ation. Allow CPTs to register by adding their CPT via
1387 * the publicize_post_types array filter.
1388 *
1389 * @param string $post_type The post type to check.
1390 * @return bool True if the post type can be Publicized.
1391 */
1392 public function post_type_is_publicizeable( $post_type ) {
1393 if ( 'post' === $post_type ) {
1394 return true;
1395 }
1396
1397 return post_type_supports( $post_type, 'publicize' );
1398 }
1399
1400 /**
1401 * Already-published posts should not be Publicized by default. This filter sets checked to
1402 * false if a post has already been published.
1403 *
1404 * Attached to the `publicize_checkbox_default` filter
1405 *
1406 * @param bool $checked True if checkbox is checked, false otherwise.
1407 * @param int $post_id Post ID to set checkbox for.
1408 * @return bool
1409 */
1410 public function publicize_checkbox_default( $checked, $post_id ) {
1411 if ( 'publish' === get_post_status( $post_id ) ) {
1412 return false;
1413 }
1414
1415 return $checked;
1416 }
1417
1418 /**
1419 * Util
1420 */
1421
1422 /**
1423 * Converts a Publicize message template string into a sprintf format string
1424 *
1425 * @param string[] $args Array of arguments.
1426 * 0 - The Publicize message template: 'Check out my post: %title% @ %url'
1427 * ... - The template tags 'title', 'url', etc.
1428 * @return string
1429 */
1430 protected static function build_sprintf( $args ) {
1431 $search = array();
1432 $replace = array();
1433 foreach ( $args as $k => $arg ) {
1434 if ( 0 === $k ) {
1435 $string = $arg;
1436 continue;
1437 }
1438 $search[] = "%$arg%";
1439 $replace[] = "%$k\$s";
1440 }
1441 return str_replace( $search, $replace, $string );
1442 }
1443
1444 /**
1445 * Get Calypso URL for Publicize connections.
1446 *
1447 * @param string $source The idenfitier of the place the function is called from.
1448 * @return string
1449 */
1450 public function publicize_connections_url( $source = 'calypso-marketing-connections' ) {
1451 $allowed_sources = array( 'jetpack-social-connections-admin-page', 'jetpack-social-connections-classic-editor', 'calypso-marketing-connections' );
1452 $source = in_array( $source, $allowed_sources, true ) ? $source : 'calypso-marketing-connections';
1453 return Redirect::get_url( $source, array( 'site' => ( new Status() )->get_site_suffix() ) );
1454 }
1455 }
1456
1457 /**
1458 * Get Calypso URL for Publicize connections.
1459 *
1460 * @return string
1461 */
1462 function publicize_calypso_url() {
1463 _deprecated_function( __METHOD__, '11.0', 'Publicize::publicize_connections_url' );
1464 return Redirect::get_url( 'calypso-marketing-connections', array( 'site' => ( new Status() )->get_site_suffix() ) );
1465 }
1466