PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 6.1
Jetpack – WP Security, Backup, Speed, & Growth v6.1
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
569 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 abstract class Publicize_Base {
4
5 /**
6 * Services that are currently connected to the given user
7 * through publicize.
8 */
9 public $connected_services = array();
10
11 /**
12 * Services that are supported by publicize. They don't
13 * necessarily need to be connected to the current user.
14 */
15 public $services;
16
17 /**
18 * key names for post meta
19 */
20 public $ADMIN_PAGE = 'wpas';
21 public $POST_MESS = '_wpas_mess';
22 public $POST_SKIP = '_wpas_skip_'; // connection id appended to indicate that a connection should NOT be publicized to
23 public $POST_DONE = '_wpas_done_'; // connection id appended to indicate a connection has already been publicized to
24 public $USER_AUTH = 'wpas_authorize';
25 public $USER_OPT = 'wpas_';
26 public $PENDING = '_publicize_pending'; // ready for Publicize to do its thing
27 public $POST_SERVICE_DONE = '_publicize_done_external'; // array of external ids where we've Publicized
28
29 /**
30 * default pieces of the message used in constructing the
31 * content pushed out to other social networks
32 */
33
34 public $default_prefix = '';
35 public $default_message = '%title%';
36 public $default_suffix = ' ';
37
38 /**
39 * What WP capability is require to create/delete global connections?
40 * All users with this cap can un-globalize all other global connections, and globalize any of their own
41 * Globalized connections cannot be unselected by users without this capability when publishing
42 */
43 public $GLOBAL_CAP = 'edit_others_posts';
44
45 /**
46 * Sets up the basics of Publicize
47 */
48 function __construct() {
49 $this->default_message = Publicize_Util::build_sprintf( array(
50 /**
51 * Filter the default Publicize message.
52 *
53 * @module publicize
54 *
55 * @since 2.0.0
56 *
57 * @param string $this->default_message Publicize's default message. Default is the post title.
58 */
59 apply_filters( 'wpas_default_message', $this->default_message ),
60 'title',
61 'url',
62 ) );
63
64 $this->default_prefix = Publicize_Util::build_sprintf( array(
65 /**
66 * Filter the message prepended to the Publicize custom message.
67 *
68 * @module publicize
69 *
70 * @since 2.0.0
71 *
72 * @param string $this->default_prefix String prepended to the Publicize custom message.
73 */
74 apply_filters( 'wpas_default_prefix', $this->default_prefix ),
75 'url',
76 ) );
77
78 $this->default_suffix = Publicize_Util::build_sprintf( array(
79 /**
80 * Filter the message appended to the Publicize custom message.
81 *
82 * @module publicize
83 *
84 * @since 2.0.0
85 *
86 * @param string $this->default_suffix String appended to the Publicize custom message.
87 */
88 apply_filters( 'wpas_default_suffix', $this->default_suffix ),
89 'url',
90 ) );
91
92 /**
93 * Filter the capability to change global Publicize connection options.
94 *
95 * All users with this cap can un-globalize all other global connections, and globalize any of their own
96 * Globalized connections cannot be unselected by users without this capability when publishing.
97 *
98 * @module publicize
99 *
100 * @since 2.2.1
101 *
102 * @param string $this->GLOBAL_CAP default capability in control of global Publicize connection options. Default to edit_others_posts.
103 */
104 $this->GLOBAL_CAP = apply_filters( 'jetpack_publicize_global_connections_cap', $this->GLOBAL_CAP );
105
106 // stage 1 and 2 of 3-stage Publicize. Flag for Publicize on creation, save meta,
107 // then check meta and publicize based on that. stage 3 implemented on wpcom
108 add_action( 'transition_post_status', array( $this, 'flag_post_for_publicize' ), 10, 3 );
109 add_action( 'save_post', array( &$this, 'save_meta' ), 20, 2 );
110 add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 20, 1 );
111
112 // Connection test callback
113 add_action( 'wp_ajax_test_publicize_conns', array( $this, 'test_publicize_conns' ) );
114 }
115
116 /**
117 * Functions to be implemented by the extended class (publicize-wpcom or publicize-jetpack)
118 */
119 abstract function get_connection_id( $connection );
120 abstract function connect_url( $service_name );
121 abstract function disconnect_url( $service_name, $id );
122 abstract function get_connection_meta( $connection );
123 abstract function get_services( $filter = 'all' );
124 abstract function get_connections( $service, $_blog_id = false, $_user_id = false );
125 abstract function get_connection( $service, $id, $_blog_id = false, $_user_id = false );
126 abstract function flag_post_for_publicize( $new_status, $old_status, $post );
127 abstract function test_connection( $service_name, $connection );
128 abstract function disconnect( $service, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false );
129
130 /**
131 * Shared Functions
132 */
133
134 /**
135 * Returns an external URL to the connection's profile
136 */
137 function get_profile_link( $service_name, $connection ) {
138 $cmeta = $this->get_connection_meta( $connection );
139
140 if ( isset( $cmeta['connection_data']['meta']['link'] ) ) {
141 if ( 'facebook' == $service_name && 0 === strpos( parse_url( $cmeta['connection_data']['meta']['link'], PHP_URL_PATH ), '/app_scoped_user_id/' ) ) {
142 // App-scoped Facebook user IDs are not usable profile links
143 return false;
144 }
145
146 return $cmeta['connection_data']['meta']['link'];
147 } elseif ( 'facebook' == $service_name && isset( $cmeta['connection_data']['meta']['facebook_page'] ) ) {
148 return 'https://facebook.com/' . $cmeta['connection_data']['meta']['facebook_page'];
149 } elseif ( 'tumblr' == $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
150 return 'http://' . $cmeta['connection_data']['meta']['tumblr_base_hostname'];
151 } elseif ( 'twitter' == $service_name ) {
152 return 'https://twitter.com/' . substr( $cmeta['external_display'], 1 ); // Has a leading '@'
153 } elseif ( 'google_plus' == $service_name && isset( $cmeta['connection_data']['meta']['google_plus_page'] ) ) {
154 return 'https://plus.google.com/' . $cmeta['connection_data']['meta']['google_plus_page'];
155 } elseif ( 'google_plus' == $service_name ) {
156 return 'https://plus.google.com/' . $cmeta['external_id'];
157 } else if ( 'linkedin' == $service_name ) {
158 if ( !isset( $cmeta['connection_data']['meta']['profile_url'] ) ) {
159 return false;
160 }
161
162 $profile_url_query = parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY );
163 wp_parse_str( $profile_url_query, $profile_url_query_args );
164 if ( isset( $profile_url_query_args['key'] ) ) {
165 $id = $profile_url_query_args['key'];
166 } elseif ( isset( $profile_url_query_args['id'] ) ) {
167 $id = $profile_url_query_args['id'];
168 } else {
169 return false;
170 }
171
172 return esc_url_raw( add_query_arg( 'id', urlencode( $id ), 'http://www.linkedin.com/profile/view' ) );
173 } else {
174 return false; // no fallback. we just won't link it
175 }
176 }
177
178 /**
179 * Returns a display name for the connection
180 */
181 function get_display_name( $service_name, $connection ) {
182 $cmeta = $this->get_connection_meta( $connection );
183
184 if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) {
185 return $cmeta['connection_data']['meta']['display_name'];
186 } elseif ( $service_name == 'tumblr' && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
187 return $cmeta['connection_data']['meta']['tumblr_base_hostname'];
188 } elseif ( $service_name == 'twitter' ) {
189 return $cmeta['external_display'];
190 } else {
191 $connection_display = $cmeta['external_display'];
192 if ( empty( $connection_display ) )
193 $connection_display = $cmeta['external_name'];
194 return $connection_display;
195 }
196 }
197
198 public static function get_service_label( $service_name ) {
199 switch ( $service_name ) {
200 case 'linkedin':
201 return 'LinkedIn';
202 break;
203 case 'google_plus':
204 return 'Google+';
205 break;
206 case 'twitter':
207 case 'facebook':
208 case 'tumblr':
209 default:
210 return ucfirst( $service_name );
211 break;
212 }
213 }
214
215 function show_options_popup( $service_name, $connection ) {
216 $cmeta = $this->get_connection_meta( $connection );
217
218 // always show if no selection has been made for facebook
219 if ( 'facebook' == $service_name && empty( $cmeta['connection_data']['meta']['facebook_profile'] ) && empty( $cmeta['connection_data']['meta']['facebook_page'] ) )
220 return true;
221
222 // always show if no selection has been made for tumblr
223 if ( 'tumblr' == $service_name && empty ( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) )
224 return true;
225
226 // if we have the specific connection info..
227 if ( isset( $_GET['id'] ) ) {
228 if ( $cmeta['connection_data']['id'] == $_GET['id'] )
229 return true;
230 } else {
231 // otherwise, just show if this is the completed step / first load
232 if ( !empty( $_GET['action'] ) && 'completed' == $_GET['action'] && !empty( $_GET['service'] ) && $service_name == $_GET['service'] && ! in_array( $_GET['service'], array( 'facebook', 'tumblr' ) ) )
233 return true;
234 }
235
236 return false;
237 }
238
239 function user_id() {
240 global $current_user;
241 return $current_user->ID;
242 }
243
244 function blog_id() {
245 return get_current_blog_id();
246 }
247
248 /**
249 * Returns true if a user has a connection to a particular service, false otherwise
250 */
251 function is_enabled( $service, $_blog_id = false, $_user_id = false ) {
252 if ( !$_blog_id )
253 $_blog_id = $this->blog_id();
254
255 if ( !$_user_id )
256 $_user_id = $this->user_id();
257
258 $connections = $this->get_connections( $service, $_blog_id, $_user_id );
259 return ( is_array( $connections ) && count( $connections ) > 0 ? true : false );
260 }
261
262 /**
263 * Fires when a post is saved, checks conditions and saves state in postmeta so that it
264 * can be picked up later by @see ::publicize_post() on WordPress.com codebase.
265 */
266 function save_meta( $post_id, $post ) {
267 $cron_user = null;
268 $submit_post = true;
269
270 if ( ! $this->post_type_is_publicizeable( $post->post_type ) )
271 return;
272
273 // Don't Publicize during certain contexts:
274
275 // - import
276 if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
277 $submit_post = false;
278 }
279
280 // - on quick edit, autosave, etc but do fire on p2, quickpress, and instapost ajax
281 if (
282 defined( 'DOING_AJAX' )
283 &&
284 DOING_AJAX
285 &&
286 !did_action( 'p2_ajax' )
287 &&
288 !did_action( 'wp_ajax_json_quickpress_post' )
289 &&
290 !did_action( 'wp_ajax_instapost_publish' )
291 &&
292 !did_action( 'wp_ajax_post_reblog' )
293 &&
294 !did_action( 'wp_ajax_press-this-save-post' )
295 ) {
296 $submit_post = false;
297 }
298
299 // - bulk edit
300 if ( isset( $_GET['bulk_edit'] ) ) {
301 $submit_post = false;
302 }
303
304 // - API/XML-RPC Test Posts
305 if (
306 (
307 defined( 'XMLRPC_REQUEST' )
308 &&
309 XMLRPC_REQUEST
310 ||
311 defined( 'APP_REQUEST' )
312 &&
313 APP_REQUEST
314 )
315 &&
316 0 === strpos( $post->post_title, 'Temporary Post Used For Theme Detection' )
317 ) {
318 $submit_post = false;
319 }
320
321 // only work with certain statuses (avoids inherits, auto drafts etc)
322 if ( !in_array( $post->post_status, array( 'publish', 'draft', 'future' ) ) ) {
323 $submit_post = false;
324 }
325
326 // don't publish password protected posts
327 if ( '' !== $post->post_password ) {
328 $submit_post = false;
329 }
330
331 // Did this request happen via wp-admin?
332 $from_web = isset( $_SERVER['REQUEST_METHOD'] )
333 &&
334 'post' == strtolower( $_SERVER['REQUEST_METHOD'] )
335 &&
336 isset( $_POST[$this->ADMIN_PAGE] );
337
338 if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && isset( $_POST['wpas_title'] ) ) {
339 if ( empty( $_POST['wpas_title'] ) ) {
340 delete_post_meta( $post_id, $this->POST_MESS );
341 } else {
342 update_post_meta( $post_id, $this->POST_MESS, trim( stripslashes( $_POST['wpas_title'] ) ) );
343 }
344 }
345
346 // change current user to provide context for get_services() if we're running during cron
347 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
348 $cron_user = (int) $GLOBALS['user_ID'];
349 wp_set_current_user( $post->post_author );
350 }
351
352 /**
353 * In this phase, we mark connections that we want to SKIP. When Publicize is actually triggered,
354 * it will Publicize to everything *except* those marked for skipping.
355 */
356 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
357 foreach ( $connections as $connection ) {
358 $connection_data = '';
359 if ( method_exists( $connection, 'get_meta' ) )
360 $connection_data = $connection->get_meta( 'connection_data' );
361 elseif ( ! empty( $connection['connection_data'] ) )
362 $connection_data = $connection['connection_data'];
363
364 /** This action is documented in modules/publicize/ui.php */
365 if ( false == apply_filters( 'wpas_submit_post?', $submit_post, $post_id, $service_name, $connection_data ) ) {
366 delete_post_meta( $post_id, $this->PENDING );
367 continue;
368 }
369
370 if ( !empty( $connection->unique_id ) )
371 $unique_id = $connection->unique_id;
372 else if ( !empty( $connection['connection_data']['token_id'] ) )
373 $unique_id = $connection['connection_data']['token_id'];
374
375 // This was a wp-admin request, so we need to check the state of checkboxes
376 if ( $from_web ) {
377 // delete stray service-based post meta
378 delete_post_meta( $post_id, $this->POST_SKIP . $service_name );
379
380 // We *unchecked* this stream from the admin page, or it's set to readonly, or it's a new addition
381 if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$unique_id] ) ) {
382 // Also make sure that the service-specific input isn't there.
383 // If the user connected to a new service 'in-page' then a hidden field with the service
384 // name is added, so we just assume they wanted to Publicize to that service.
385 if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$service_name] ) ) {
386 // Nothing seems to be checked, so we're going to mark this one to be skipped
387 update_post_meta( $post_id, $this->POST_SKIP . $unique_id, 1 );
388 continue;
389 } else {
390 // clean up any stray post meta
391 delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
392 }
393 } else {
394 // The checkbox for this connection is explicitly checked -- make sure we DON'T skip it
395 delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
396 }
397 }
398
399 /**
400 * Fires right before the post is processed for Publicize.
401 * Users may hook in here and do anything else they need to after meta is written,
402 * and before the post is processed for Publicize.
403 *
404 * @since 2.1.2
405 *
406 * @param bool $submit_post Should the post be publicized.
407 * @param int $post->ID Post ID.
408 * @param string $service_name Service name.
409 * @param array $connection Array of connection details.
410 */
411 do_action( 'publicize_save_meta', $submit_post, $post_id, $service_name, $connection );
412 }
413 }
414
415 if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
416 wp_set_current_user( $cron_user );
417 }
418
419 // Next up will be ::publicize_post()
420 }
421
422 public function update_published_message( $messages ) {
423 global $post_type, $post_type_object, $post;
424 if ( ! $this->post_type_is_publicizeable( $post_type ) ) {
425 return $messages;
426 }
427 $view_post_link_html = '';
428 $viewable = is_post_type_viewable( $post_type_object );
429 if ( $viewable ) {
430 $view_text = esc_html__( 'View post' ); // intentionally omitted domain
431
432 if ( 'jetpack-portfolio' == $post_type ) {
433 $view_text = esc_html__( 'View project', 'jetpack' );
434 }
435
436 $view_post_link_html = sprintf( ' <a href="%1$s">%2$s</a>',
437 esc_url( get_permalink( $post ) ),
438 $view_text
439 );
440 }
441
442 $services = $this->get_publicizing_services( $post->ID );
443 if ( empty( $services ) ) {
444 return $messages;
445 }
446
447 $labels = array();
448 foreach ( $services as $service => $display_names ) {
449 $labels[] = sprintf(
450 /* translators: Service name is %1$s, and account name is %2$s. */
451 esc_html__( '%1$s (%2$s)', 'jetpack' ),
452 esc_html( $service ),
453 esc_html( implode( ', ', $display_names ) )
454 );
455 }
456
457 $messages['post'][6] = sprintf(
458 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
459 esc_html__( 'Post published and sharing on %1$s.', 'jetpack' ),
460 implode( ', ', $labels )
461 ) . $view_post_link_html;
462
463 if ( $post_type == 'post' && class_exists('Jetpack_Subscriptions' ) ) {
464 $subscription = Jetpack_Subscriptions::init();
465 if ( $subscription->should_email_post_to_subscribers( $post ) ) {
466 $messages['post'][6] = sprintf(
467 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
468 esc_html__( 'Post published, sending emails to subscribers and sharing post on %1$s.', 'jetpack' ),
469 implode( ', ', $labels )
470 ) . $view_post_link_html;
471 }
472 }
473
474 $messages['jetpack-portfolio'][6] = sprintf(
475 /* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
476 esc_html__( 'Project published and sharing project on %1$s.', 'jetpack' ),
477 implode( ', ', $labels )
478 ) . $view_post_link_html;
479
480 return $messages;
481 }
482
483 function get_publicizing_services( $post_id ) {
484 $services = array();
485
486 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
487 // services have multiple connections.
488 foreach ( $connections as $connection ) {
489 $unique_id = '';
490 if ( ! empty( $connection->unique_id ) )
491 $unique_id = $connection->unique_id;
492 else if ( ! empty( $connection['connection_data']['token_id'] ) )
493 $unique_id = $connection['connection_data']['token_id'];
494
495 // Did we skip this connection?
496 if ( get_post_meta( $post_id, $this->POST_SKIP . $unique_id, true ) ) {
497 continue;
498 }
499 $services[ $this->get_service_label( $service_name ) ][] = $this->get_display_name( $service_name, $connection );
500 }
501 }
502
503 return $services;
504 }
505
506 /**
507 * Is a given post type Publicize-able?
508 *
509 * Not every CPT lends itself to Publicize-ation. Allow CPTs to register by adding their CPT via
510 * the publicize_post_types array filter.
511 *
512 * @param string $post_type The post type to check.
513 * @return bool True if the post type can be Publicized.
514 */
515 function post_type_is_publicizeable( $post_type ) {
516 if ( 'post' == $post_type )
517 return true;
518
519 return post_type_supports( $post_type, 'publicize' );
520 }
521
522 /**
523 * Runs tests on all the connections and returns the results to the caller
524 */
525 function test_publicize_conns() {
526 $test_results = array();
527
528 foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
529 foreach ( $connections as $connection ) {
530
531 $id = $this->get_connection_id( $connection );
532
533 $connection_test_passed = true;
534 $connection_test_message = __( 'This connection is working correctly.' , 'jetpack' );
535 $user_can_refresh = false;
536 $refresh_text = '';
537 $refresh_url = '';
538
539 $connection_test_result = true;
540 if ( method_exists( $this, 'test_connection' ) ) {
541 $connection_test_result = $this->test_connection( $service_name, $connection );
542 }
543
544 if ( is_wp_error( $connection_test_result ) ) {
545 $connection_test_passed = false;
546 $connection_test_message = $connection_test_result->get_error_message();
547 $error_data = $connection_test_result->get_error_data();
548
549 $user_can_refresh = $error_data['user_can_refresh'];
550 $refresh_text = $error_data['refresh_text'];
551 $refresh_url = $error_data['refresh_url'];
552 }
553
554 $test_results[] = array(
555 'connectionID' => $id,
556 'serviceName' => $service_name,
557 'connectionTestPassed' => $connection_test_passed,
558 'connectionTestMessage' => esc_attr( $connection_test_message ),
559 'userCanRefresh' => $user_can_refresh,
560 'refreshText' => esc_attr( $refresh_text ),
561 'refreshURL' => $refresh_url
562 );
563 }
564 }
565
566 wp_send_json_success( $test_results );
567 }
568 }
569