Completed
Push — try/jetpack-stories-block-mobi... ( 2fea66 )
by
unknown
126:35 queued 116:47
created

Publicize_Base::globalize_connection()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
nc 1
dl 0
loc 1
c 0
b 0
f 0
1
<?php
2
// phpcs:disable WordPress.NamingConventions.ValidVariableName
3
4
use Automattic\Jetpack\Redirect;
5
6
abstract class Publicize_Base {
7
8
	/**
9
	* Services that are currently connected to the given user
10
	* through publicize.
11
	*/
12
	public $connected_services = array();
13
14
	/**
15
	* Services that are supported by publicize. They don't
16
	* necessarily need to be connected to the current user.
17
	*/
18
	public $services;
19
20
	/**
21
	* key names for post meta
22
	*/
23
	public $ADMIN_PAGE        = 'wpas';
24
	public $POST_MESS         = '_wpas_mess';
25
26
	/**
27
	 * Post meta key for flagging when the post is a tweetstorm.
28
	 *
29
	 * @var string
30
	 */
31
	public $POST_TWEETSTORM = '_wpas_is_tweetstorm';
32
33
	public $POST_SKIP         = '_wpas_skip_'; // connection id appended to indicate that a connection should NOT be publicized to
34
	public $POST_DONE         = '_wpas_done_'; // connection id appended to indicate a connection has already been publicized to
35
	public $USER_AUTH         = 'wpas_authorize';
36
	public $USER_OPT          = 'wpas_';
37
	public $PENDING           = '_publicize_pending'; // ready for Publicize to do its thing
38
	public $POST_SERVICE_DONE = '_publicize_done_external'; // array of external ids where we've Publicized
39
40
	/**
41
	* default pieces of the message used in constructing the
42
	* content pushed out to other social networks
43
	*/
44
45
	public $default_prefix  = '';
46
	public $default_message = '%title%';
47
	public $default_suffix  = ' ';
48
49
	/**
50
	 * What WP capability is require to create/delete global connections?
51
	 * All users with this cap can un-globalize all other global connections, and globalize any of their own
52
	 * Globalized connections cannot be unselected by users without this capability when publishing
53
	 */
54
	public $GLOBAL_CAP = 'publish_posts';
55
56
	/**
57
	* Sets up the basics of Publicize
58
	*/
59
	function __construct() {
60
		$this->default_message = self::build_sprintf( array(
61
			/**
62
			 * Filter the default Publicize message.
63
			 *
64
			 * @module publicize
65
			 *
66
			 * @since 2.0.0
67
			 *
68
			 * @param string $this->default_message Publicize's default message. Default is the post title.
69
			 */
70
			apply_filters( 'wpas_default_message', $this->default_message ),
71
			'title',
72
			'url',
73
		) );
74
75
		$this->default_prefix = self::build_sprintf( array(
76
			/**
77
			 * Filter the message prepended to the Publicize custom message.
78
			 *
79
			 * @module publicize
80
			 *
81
			 * @since 2.0.0
82
			 *
83
			 * @param string $this->default_prefix String prepended to the Publicize custom message.
84
			 */
85
			apply_filters( 'wpas_default_prefix', $this->default_prefix ),
86
			'url',
87
		) );
88
89
		$this->default_suffix = self::build_sprintf( array(
90
			/**
91
			 * Filter the message appended to the Publicize custom message.
92
			 *
93
			 * @module publicize
94
			 *
95
			 * @since 2.0.0
96
			 *
97
			 * @param string $this->default_suffix String appended to the Publicize custom message.
98
			 */
99
			apply_filters( 'wpas_default_suffix', $this->default_suffix ),
100
			'url',
101
		) );
102
103
		/**
104
		 * Filter the capability to change global Publicize connection options.
105
		 *
106
		 * All users with this cap can un-globalize all other global connections, and globalize any of their own
107
		 * Globalized connections cannot be unselected by users without this capability when publishing.
108
		 *
109
		 * @module publicize
110
		 *
111
		 * @since 2.2.1
112
		 *
113
		 * @param string $this->GLOBAL_CAP default capability in control of global Publicize connection options. Default to edit_others_posts.
114
		 */
115
		$this->GLOBAL_CAP = apply_filters( 'jetpack_publicize_global_connections_cap', $this->GLOBAL_CAP );
116
117
		// stage 1 and 2 of 3-stage Publicize. Flag for Publicize on creation, save meta,
118
		// then check meta and publicize based on that. stage 3 implemented on wpcom
119
		add_action( 'transition_post_status', array( $this, 'flag_post_for_publicize' ), 10, 3 );
120
		add_action( 'save_post', array( &$this, 'save_meta' ), 20, 2 );
121
122
		// Default checkbox state for each Connection
123
		add_filter( 'publicize_checkbox_default', array( $this, 'publicize_checkbox_default' ), 10, 4 );
124
125
		// Alter the "Post Publish" admin notice to mention the Connections we Publicized to.
126
		add_filter( 'post_updated_messages', array( $this, 'update_published_message' ), 20, 1 );
127
128
		// Connection test callback
129
		add_action( 'wp_ajax_test_publicize_conns', array( $this, 'test_publicize_conns' ) );
130
131
		add_action( 'init', array( $this, 'add_post_type_support' ) );
132
		add_action( 'init', array( $this, 'register_post_meta' ), 20 );
133
		add_action( 'jetpack_register_gutenberg_extensions', array( $this, 'register_gutenberg_extension' ) );
134
	}
135
136
/*
137
 * Services: Facebook, Twitter, etc.
138
 */
139
140
	/**
141
	 * Get services for the given blog and user.
142
	 *
143
	 * Can return all available services or just the ones with an active connection.
144
	 *
145
	 * @param string $filter
146
	 *        'all' (default) - Get all services available for connecting
147
	 *        'connected'     - Get all services currently connected
148
	 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog
149
	 * @param false|int $_user_id The user ID. Use false (default) for the current user
150
	 * @return array
151
	 */
152
	abstract function get_services( $filter = 'all', $_blog_id = false, $_user_id = false );
153
154
	function can_connect_service( $service_name ) {
155
		return true;
156
	}
157
158
	/**
159
	 * Does the given user have a connection to the service on the given blog?
160
	 *
161
	 * @param string $service_name 'facebook', 'twitter', etc.
162
	 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog
163
	 * @param false|int $_user_id The user ID. Use false (default) for the current user
164
	 * @return bool
165
	 */
166
	function is_enabled( $service_name, $_blog_id = false, $_user_id = false ) {
167
		if ( !$_blog_id )
0 ignored issues
show
Bug Best Practice introduced by
The expression $_blog_id of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
168
			$_blog_id = $this->blog_id();
169
170
		if ( !$_user_id )
0 ignored issues
show
Bug Best Practice introduced by
The expression $_user_id of type false|integer is loosely compared to false; this is ambiguous if the integer can be zero. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
171
			$_user_id = $this->user_id();
172
173
		$connections = $this->get_connections( $service_name, $_blog_id, $_user_id );
174
		return ( is_array( $connections ) && count( $connections ) > 0 ? true : false );
175
	}
176
177
	/**
178
	 * Generates a connection URL.
179
	 *
180
	 * This is the URL, which, when visited by the user, starts the authentication
181
	 * process required to forge a connection.
182
	 *
183
	 * @param string $service_name 'facebook', 'twitter', etc.
184
	 * @return string
185
	 */
186
	abstract function connect_url( $service_name );
187
188
	/**
189
	 * Generates a Connection refresh URL.
190
	 *
191
	 * This is the URL, which, when visited by the user, re-authenticates their
192
	 * connection to the service.
193
	 *
194
	 * @param string $service_name 'facebook', 'twitter', etc.
195
	 * @return string
196
	 */
197
	abstract function refresh_url( $service_name );
198
199
	/**
200
	 * Generates a disconnection URL.
201
	 *
202
	 * This is the URL, which, when visited by the user, breaks their connection
203
	 * with the service.
204
	 *
205
	 * @param string $service_name 'facebook', 'twitter', etc.
206
	 * @param string $connection_id Connection ID
207
	 * @return string
208
	 */
209
	abstract function disconnect_url( $service_name, $connection_id );
210
211
	/**
212
	 * Returns a display name for the Service
213
	 *
214
	 * @param string $service_name 'facebook', 'twitter', etc.
215
	 * @return string
216
	 */
217
	public static function get_service_label( $service_name ) {
218
		switch ( $service_name ) {
219
			case 'linkedin':
220
				return 'LinkedIn';
221
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
222
			case 'google_drive': // google-drive used to be called google_drive.
223
			case 'google-drive':
224
				return 'Google Drive';
225
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
226
			case 'twitter':
227
			case 'facebook':
228
			case 'tumblr':
229
			default:
230
				return ucfirst( $service_name );
231
				break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
232
		}
233
	}
234
235
/*
236
 * Connections: For each Service, there can be multiple connections
237
 * for a given user. For example, one user could be connected to Twitter
238
 * as both @jetpack and as @wordpressdotcom
239
 *
240
 * For historical reasons, Connections are represented as an object
241
 * on WordPress.com and as an array in Jetpack.
242
 */
243
244
	/**
245
	 * Get the active Connections of a Service
246
	 *
247
	 * @param string $service_name 'facebook', 'twitter', etc.
248
	 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog
249
	 * @param false|int $_user_id The user ID. Use false (default) for the current user
250
	 * @return false|object[]|array[] false if no connections exist
251
	 */
252
	abstract function get_connections( $service_name, $_blog_id = false, $_user_id = false );
253
254
	/**
255
	 * Get a single Connection of a Service
256
	 *
257
	 * @param string $service_name 'facebook', 'twitter', etc.
258
	 * @param string $connection_id Connection ID
259
	 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog
260
	 * @param false|int $_user_id The user ID. Use false (default) for the current user
261
	 * @return false|object[]|array[] false if no connections exist
262
	 */
263
	abstract function get_connection( $service_name, $connection_id, $_blog_id = false, $_user_id = false );
264
265
	/**
266
	 * Get the Connection ID.
267
	 *
268
	 * Note that this is different than the Connection's uniqueid.
269
	 *
270
	 * Via a quirk of history, ID is globally unique and unique_id
271
	 * is only unique per site.
272
	 *
273
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
274
	 * @return string
275
	 */
276
	abstract function get_connection_id( $connection );
277
278
	/**
279
	 * Get the Connection unique_id
280
	 *
281
	 * Note that this is different than the Connections ID.
282
	 *
283
	 * Via a quirk of history, ID is globally unique and unique_id
284
	 * is only unique per site.
285
	 *
286
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
287
	 * @return string
288
	 */
289
	abstract function get_connection_unique_id( $connection );
290
291
	/**
292
	 * Get the Connection's Meta data
293
	 *
294
	 * @param object|array Connection
295
	 * @return array Connection Meta
296
	 */
297
	abstract function get_connection_meta( $connection );
298
299
	/**
300
	 * Disconnect a Connection
301
	 *
302
	 * @param string $service_name 'facebook', 'twitter', etc.
303
	 * @param string $connection_id Connection ID
304
	 * @param false|int $_blog_id The blog ID. Use false (default) for the current blog
305
	 * @param false|int $_user_id The user ID. Use false (default) for the current user
306
	 * @param bool $force_delete Whether to skip permissions checks
307
	 * @return false|void False on failure. Void on success.
308
	 */
309
	abstract function disconnect( $service_name, $connection_id, $_blog_id = false, $_user_id = false, $force_delete = false );
310
311
	/**
312
	 * Globalizes a Connection
313
	 *
314
	 * @param string $connection_id Connection ID
315
	 * @return bool Falsey on failure. Truthy on success.
316
	 */
317
	abstract function globalize_connection( $connection_id );
318
319
	/**
320
	 * Unglobalizes a Connection
321
	 *
322
	 * @param string $connection_id Connection ID
323
	 * @return bool Falsey on failure. Truthy on success.
324
	 */
325
	abstract function unglobalize_connection( $connection_id );
326
327
	/**
328
	 * Returns an external URL to the Connection's profile
329
	 *
330
	 * @param string $service_name 'facebook', 'twitter', etc.
331
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
332
	 * @return false|string False on failure. URL on success.
333
	 */
334
	function get_profile_link( $service_name, $connection ) {
335
		$cmeta = $this->get_connection_meta( $connection );
336
337
		if ( isset( $cmeta['connection_data']['meta']['link'] ) ) {
338
			if ( 'facebook' == $service_name && 0 === strpos( wp_parse_url( $cmeta['connection_data']['meta']['link'], PHP_URL_PATH ), '/app_scoped_user_id/' ) ) {
339
				// App-scoped Facebook user IDs are not usable profile links
340
				return false;
341
			}
342
343
			return $cmeta['connection_data']['meta']['link'];
344 View Code Duplication
		} elseif ( 'facebook' == $service_name && isset( $cmeta['connection_data']['meta']['facebook_page'] ) ) {
345
			return 'https://facebook.com/' . $cmeta['connection_data']['meta']['facebook_page'];
346
		} elseif ( 'tumblr' == $service_name && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
347
			 return 'https://' . $cmeta['connection_data']['meta']['tumblr_base_hostname'];
348
		} elseif ( 'twitter' == $service_name ) {
349
			return 'https://twitter.com/' . substr( $cmeta['external_display'], 1 ); // Has a leading '@'
350
		} else if ( 'linkedin' == $service_name ) {
351
			if ( !isset( $cmeta['connection_data']['meta']['profile_url'] ) ) {
352
				return false;
353
			}
354
355
			$profile_url_query = wp_parse_url( $cmeta['connection_data']['meta']['profile_url'], PHP_URL_QUERY );
356
			wp_parse_str( $profile_url_query, $profile_url_query_args );
0 ignored issues
show
Bug introduced by
The variable $profile_url_query_args does not exist. Did you mean $profile_url_query?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
357
			if ( isset( $profile_url_query_args['key'] ) ) {
0 ignored issues
show
Bug introduced by
The variable $profile_url_query_args does not exist. Did you mean $profile_url_query?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
358
				$id = $profile_url_query_args['key'];
359
			} elseif ( isset( $profile_url_query_args['id'] ) ) {
0 ignored issues
show
Bug introduced by
The variable $profile_url_query_args does not exist. Did you mean $profile_url_query?

This check looks for variables that are accessed but have not been defined. It raises an issue if it finds another variable that has a similar name.

The variable may have been renamed without also renaming all references.

Loading history...
360
				$id = $profile_url_query_args['id'];
361
			} else {
362
				return false;
363
			}
364
365
			return esc_url_raw( add_query_arg( 'id', urlencode( $id ), 'https://www.linkedin.com/profile/view' ) );
366
		} else {
367
			return false; // no fallback. we just won't link it
368
		}
369
	}
370
371
	/**
372
	 * Returns a display name for the Connection
373
	 *
374
	 * @param string $service_name 'facebook', 'twitter', etc.
375
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
376
	 * @return string
377
	 */
378
	function get_display_name( $service_name, $connection ) {
379
		$cmeta = $this->get_connection_meta( $connection );
380
381
		if ( isset( $cmeta['connection_data']['meta']['display_name'] ) ) {
382
			return $cmeta['connection_data']['meta']['display_name'];
383 View Code Duplication
		} elseif ( $service_name == 'tumblr' && isset( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) ) {
384
			 return $cmeta['connection_data']['meta']['tumblr_base_hostname'];
385
		} elseif ( $service_name == 'twitter' ) {
386
			return $cmeta['external_display'];
387
		} else {
388
			$connection_display = $cmeta['external_display'];
389
			if ( empty( $connection_display ) )
390
				$connection_display = $cmeta['external_name'];
391
			return $connection_display;
392
		}
393
	}
394
395
	/**
396
	 * Whether the user needs to select additional options after connecting
397
	 *
398
	 * @param string $service_name 'facebook', 'twitter', etc.
399
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
400
	 * @return bool
401
	 */
402
	function show_options_popup( $service_name, $connection ) {
403
		$cmeta = $this->get_connection_meta( $connection );
404
405
		// always show if no selection has been made for facebook
406
		if ( 'facebook' == $service_name && empty( $cmeta['connection_data']['meta']['facebook_profile'] ) && empty( $cmeta['connection_data']['meta']['facebook_page'] ) )
407
			return true;
408
409
		// always show if no selection has been made for tumblr
410
		if ( 'tumblr' == $service_name && empty ( $cmeta['connection_data']['meta']['tumblr_base_hostname'] ) )
411
			return true;
412
413
		// if we have the specific connection info..
414
		if ( isset( $_GET['id'] ) ) {
415
			if ( $cmeta['connection_data']['id'] == $_GET['id'] )
416
				return true;
417
		} else {
418
			// otherwise, just show if this is the completed step / first load
419
			if ( !empty( $_GET['action'] ) && 'completed' == $_GET['action'] && !empty( $_GET['service'] ) && $service_name == $_GET['service'] && ! in_array( $_GET['service'], array( 'facebook', 'tumblr' ) ) )
420
				return true;
421
		}
422
423
		return false;
424
	}
425
426
	/**
427
	 * Whether the Connection is "valid" wrt Facebook's requirements.
428
	 *
429
	 * Must be connected to a Page (not a Profile).
430
	 * (Also returns true if we're in the middle of the connection process)
431
	 *
432
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
433
	 * @return bool
434
	 */
435
	function is_valid_facebook_connection( $connection ) {
436
		if ( $this->is_connecting_connection( $connection ) ) {
437
			return true;
438
		}
439
		$connection_meta = $this->get_connection_meta( $connection );
440
		$connection_data = $connection_meta['connection_data'];
441
		return isset( $connection_data[ 'meta' ][ 'facebook_page' ] );
442
	}
443
444
	/**
445
	 * LinkedIn needs to be reauthenticated to use v2 of their API.
446
	 * If it's using LinkedIn old API, it's an 'invalid' connection
447
	 *
448
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
449
	 * @return bool
450
	 */
451
	function is_invalid_linkedin_connection( $connection ) {
452
		// LinkedIn API v1 included the profile link in the connection data.
453
		$connection_meta = $this->get_connection_meta( $connection );
454
		return isset( $connection_meta['connection_data']['meta']['profile_url'] );
455
	}
456
457
	/**
458
	 * Whether the Connection currently being connected
459
	 *
460
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
461
	 * @return bool
462
	 */
463
	function is_connecting_connection( $connection ) {
464
		$connection_meta = $this->get_connection_meta( $connection );
465
		$connection_data = $connection_meta['connection_data'];
466
		return isset( $connection_data[ 'meta' ]['options_responses'] );
467
	}
468
469
	/**
470
	 * AJAX Handler to run connection tests on all Connections
471
	 * @return void
472
	 */
473
	function test_publicize_conns() {
474
		wp_send_json_success( $this->get_publicize_conns_test_results() );
475
	}
476
477
	/**
478
	 * Run connection tests on all Connections
479
	 *
480
	 * @return array {
481
	 *     Array of connection test results.
482
	 *
483
	 *     @type string 'connectionID'          Connection identifier string that is unique for each connection
484
	 *     @type string 'serviceName'           Slug of the connection's service (facebook, twitter, ...)
485
	 *     @type bool   'connectionTestPassed'  Whether the connection test was successful
486
	 *     @type string 'connectionTestMessage' Test success or error message
487
	 *     @type bool   'userCanRefresh'        Whether the user can re-authenticate their connection to the service
488
	 *     @type string 'refreshText'           Message instructing user to re-authenticate their connection to the service
489
	 *     @type string 'refreshURL'            URL, which, when visited by the user, re-authenticates their connection to the service.
490
	 *     @type string 'unique_id'             ID string representing connection
491
	 * }
492
	 */
493
	function get_publicize_conns_test_results() {
494
		$test_results = array();
495
496
		foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
497
			foreach ( $connections as $connection ) {
498
499
				$id = $this->get_connection_id( $connection );
500
501
				$connection_test_passed = true;
502
				$connection_test_message = __( 'This connection is working correctly.' , 'jetpack' );
503
				$user_can_refresh = false;
504
				$refresh_text = '';
505
				$refresh_url = '';
506
507
				$connection_test_result = true;
508
				if ( method_exists( $this, 'test_connection' ) ) {
509
					$connection_test_result = $this->test_connection( $service_name, $connection );
510
				}
511
512
				if ( is_wp_error( $connection_test_result ) ) {
513
					$connection_test_passed = false;
514
					$connection_test_message = $connection_test_result->get_error_message();
0 ignored issues
show
Bug introduced by
The method get_error_message() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
515
					$error_data = $connection_test_result->get_error_data();
0 ignored issues
show
Bug introduced by
The method get_error_data() does not seem to exist on object<WP_Error>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
516
517
					$user_can_refresh = $error_data['user_can_refresh'];
518
					$refresh_text = $error_data['refresh_text'];
519
					$refresh_url = $error_data['refresh_url'];
520
				}
521
				// Mark facebook profiles as deprecated
522
				if ( 'facebook' === $service_name ) {
523
					if ( ! $this->is_valid_facebook_connection( $connection ) ) {
524
						$connection_test_passed = false;
525
						$user_can_refresh = false;
526
						$connection_test_message = __( 'Please select a Facebook Page to publish updates.', 'jetpack' );
527
					}
528
				}
529
530
				// LinkedIn needs reauthentication to be compatible with v2 of their API
531
				if ( 'linkedin' === $service_name && $this->is_invalid_linkedin_connection( $connection ) ) {
532
					$connection_test_passed = 'must_reauth';
533
					$user_can_refresh = false;
534
					$connection_test_message = esc_html__( 'Your LinkedIn connection needs to be reauthenticated to continue working – head to Sharing to take care of it.', 'jetpack' );
535
				}
536
537
				$unique_id = null;
538 View Code Duplication
				if ( ! empty( $connection->unique_id ) ) {
539
					$unique_id = $connection->unique_id;
540
				} else if ( ! empty( $connection['connection_data']['token_id'] ) ) {
541
					$unique_id = $connection['connection_data']['token_id'];
542
				}
543
544
				$test_results[] = array(
545
					'connectionID'          => $id,
546
					'serviceName'           => $service_name,
547
					'connectionTestPassed'  => $connection_test_passed,
548
					'connectionTestMessage' => esc_attr( $connection_test_message ),
549
					'userCanRefresh'        => $user_can_refresh,
550
					'refreshText'           => esc_attr( $refresh_text ),
551
					'refreshURL'            => $refresh_url,
552
					'unique_id'             => $unique_id,
553
				);
554
			}
555
		}
556
557
		return $test_results;
558
	}
559
560
	/**
561
	 * Run the connection test for the Connection
562
	 *
563
	 * @param string $service_name 'facebook', 'twitter', etc.
564
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
565
	 * @return WP_Error|true WP_Error on failure. True on success
566
	 */
567
	abstract function test_connection( $service_name, $connection );
568
569
	/**
570
	 * Retrieves current list of connections and applies filters.
571
	 *
572
	 * Retrieves current available connections and checks if the connections
573
	 * have already been used to share current post. Finally, the checkbox
574
	 * form UI fields are calculated. This function exposes connection form
575
	 * data directly as array so it can be retrieved for static HTML generation
576
	 * or JSON consumption.
577
	 *
578
	 * @since 6.7.0
579
	 *
580
	 * @param integer $selected_post_id Optional. Post ID to query connection status for.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $selected_post_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
581
	 *
582
	 * @return array {
583
	 *     Array of UI setup data for connection list form.
584
	 *
585
	 *     @type string 'unique_id'     ID string representing connection
586
	 *     @type string 'service_name'  Slug of the connection's service (facebook, twitter, ...)
587
	 *     @type string 'service_label' Service Label (Facebook, Twitter, ...)
588
	 *     @type string 'display_name'  Connection's human-readable Username: "@jetpack"
589
	 *     @type bool   'enabled'       Default value for the connection (e.g., for a checkbox).
590
	 *     @type bool   'done'          Has this connection already been publicized to?
591
	 *     @type bool   'toggleable'    Is the user allowed to change the value for the connection?
592
	 *     @type bool   'global'        Is this connection a global one?
593
	 * }
594
	 */
595
	public function get_filtered_connection_data( $selected_post_id = null ) {
596
		$connection_list = array();
597
598
		$post = get_post( $selected_post_id ); // Defaults to current post if $post_id is null.
599
		// Handle case where there is no current post.
600
		if ( ! empty( $post ) ) {
601
			$post_id = $post->ID;
602
		} else {
603
			$post_id = null;
604
		}
605
606
		$services = $this->get_services( 'connected' );
607
		$all_done = $this->post_is_done_sharing( $post_id );
608
609
		// We don't allow Publicizing to the same external id twice, to prevent spam.
610
		$service_id_done = (array) get_post_meta( $post_id, $this->POST_SERVICE_DONE, true );
611
612
		foreach ( $services as $service_name => $connections ) {
613
			foreach ( $connections as $connection ) {
614
				$connection_meta = $this->get_connection_meta( $connection );
615
				$connection_data = $connection_meta['connection_data'];
616
617
				$unique_id = $this->get_connection_unique_id( $connection );
618
619
620
				// Was this connection (OR, old-format service) already Publicized to?
621
				$done = ! empty( $post ) && (
622
					// New flags
623
					1 == get_post_meta( $post->ID, $this->POST_DONE . $unique_id, true )
624
					||
625
					// old flags
626
					1 == get_post_meta( $post->ID, $this->POST_DONE . $service_name, true )
627
				);
628
629
				/**
630
				 * Filter whether a post should be publicized to a given service.
631
				 *
632
				 * @module publicize
633
				 *
634
				 * @since 2.0.0
635
				 *
636
				 * @param bool true Should the post be publicized to a given service? Default to true.
637
				 * @param int $post_id Post ID.
638
				 * @param string $service_name Service name.
639
				 * @param array $connection_data Array of information about all Publicize details for the site.
640
				 */
641
				if ( ! apply_filters( 'wpas_submit_post?', true, $post_id, $service_name, $connection_data ) ) {
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
642
					continue;
643
				}
644
645
				// Should we be skipping this one?
646
				$skip = (
647
					(
648
						! empty( $post )
649
						&&
650
						in_array( $post->post_status, array( 'publish', 'draft', 'future' ) )
651
						&&
652
						(
653
							// New flags
654
							get_post_meta( $post->ID, $this->POST_SKIP . $unique_id, true )
655
							||
656
							// Old flags
657
							get_post_meta( $post->ID, $this->POST_SKIP . $service_name )
658
						)
659
					)
660
					||
661
					(
662
						is_array( $connection )
663
						&&
664
						isset( $connection_meta['external_id'] ) && ! empty( $service_id_done[ $service_name ][ $connection_meta['external_id'] ] )
665
					)
666
				);
667
668
				// If this one has already been publicized to, don't let it happen again.
669
				$toggleable = ! $done && ! $all_done;
670
671
				// Determine the state of the checkbox (on/off) and allow filtering.
672
				$enabled = $done || ! $skip;
673
				/**
674
				 * Filter the checkbox state of each Publicize connection appearing in the post editor.
675
				 *
676
				 * @module publicize
677
				 *
678
				 * @since 2.0.1
679
				 *
680
				 * @param bool $enabled Should the Publicize checkbox be enabled for a given service.
681
				 * @param int $post_id Post ID.
682
				 * @param string $service_name Service name.
683
				 * @param array $connection Array of connection details.
684
				 */
685
				$enabled = apply_filters( 'publicize_checkbox_default', $enabled, $post_id, $service_name, $connection );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
686
687
				/**
688
				 * If this is a global connection and this user doesn't have enough permissions to modify
689
				 * those connections, don't let them change it.
690
				 */
691
				if ( ! $done && ( 0 == $connection_data['user_id'] && ! current_user_can( $this->GLOBAL_CAP ) ) ) {
692
					$toggleable = false;
693
694
					/**
695
					 * Filters the checkboxes for global connections with non-prilvedged users.
696
					 *
697
					 * @module publicize
698
					 *
699
					 * @since 3.7.0
700
					 *
701
					 * @param bool   $enabled Indicates if this connection should be enabled. Default true.
702
					 * @param int    $post_id ID of the current post
703
					 * @param string $service_name Name of the connection (Facebook, Twitter, etc)
704
					 * @param array  $connection Array of data about the connection.
705
					 */
706
					$enabled = apply_filters( 'publicize_checkbox_global_default', $enabled, $post_id, $service_name, $connection );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
707
				}
708
709
				// Force the checkbox to be checked if the post was DONE, regardless of what the filter does.
710
				if ( $done ) {
711
					$enabled = true;
712
				}
713
714
				$connection_list[] = array(
715
					'unique_id'     => $unique_id,
716
					'service_name'  => $service_name,
717
					'service_label' => $this->get_service_label( $service_name ),
718
					'display_name'  => $this->get_display_name( $service_name, $connection ),
719
720
					'enabled'      => $enabled,
721
					'done'         => $done,
722
					'toggleable'   => $toggleable,
723
					'global'       => 0 == $connection_data['user_id'],
724
				);
725
			}
726
		}
727
728
		return $connection_list;
729
	}
730
731
	/**
732
	 * Checks if post has already been shared by Publicize in the past.
733
	 *
734
	 * @since 6.7.0
735
	 *
736
	 * @param integer $post_id Optional. Post ID to query connection status for: will use current post if missing.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $post_id not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
737
	 *
738
	 * @return bool True if post has already been shared by Publicize, false otherwise.
739
	 */
740
	abstract public function post_is_done_sharing( $post_id = null );
741
742
	/**
743
	 * Retrieves full list of available Publicize connection services.
744
	 *
745
	 * Retrieves current available publicize service connections
746
	 * with associated labels and URLs.
747
	 *
748
	 * @since 6.7.0
749
	 *
750
	 * @return array {
751
	 *     Array of UI service connection data for all services
752
	 *
753
	 *     @type string 'name'  Name of service.
754
	 *     @type string 'label' Display label for service.
755
	 *     @type string 'url'   URL for adding connection to service.
756
	 * }
757
	 */
758
	function get_available_service_data() {
759
		$available_services     = $this->get_services( 'all' );
760
		$available_service_data = array();
761
762
		foreach ( $available_services as $service_name => $service ) {
763
			$available_service_data[] = array(
764
				'name'  => $service_name,
765
				'label' => $this->get_service_label( $service_name ),
766
				'url'   => $this->connect_url( $service_name ),
767
			);
768
		}
769
770
		return $available_service_data;
771
	}
772
773
/*
774
 * Site Data
775
 */
776
777
	function user_id() {
778
		return get_current_user_id();
779
	}
780
781
	function blog_id() {
782
		return get_current_blog_id();
783
	}
784
785
/*
786
 * Posts
787
 */
788
789
	/**
790
	 * Checks old and new status to see if the post should be flagged as
791
	 * ready to Publicize.
792
	 *
793
	 * Attached to the `transition_post_status` filter.
794
	 *
795
	 * @param string $new_status
796
	 * @param string $old_status
797
	 * @param WP_Post $post
798
	 * @return void
799
	 */
800
	abstract function flag_post_for_publicize( $new_status, $old_status, $post );
801
802
	/**
803
	 * Ensures the Post internal post-type supports `publicize`
804
	 *
805
	 * This feature support flag is used by the REST API.
806
	 */
807
	function add_post_type_support() {
808
		add_post_type_support( 'post', 'publicize' );
809
	}
810
811
	/**
812
	 * Register the Publicize Gutenberg extension
813
	 */
814
	function register_gutenberg_extension() {
815
		// TODO: The `gutenberg/available-extensions` endpoint currently doesn't accept a post ID,
816
		// so we cannot pass one to `$this->current_user_can_access_publicize_data()`.
817
818
		if ( $this->current_user_can_access_publicize_data() ) {
819
			Jetpack_Gutenberg::set_extension_available( 'jetpack/publicize' );
820
		} else {
821
			Jetpack_Gutenberg::set_extension_unavailable( 'jetpack/publicize', 'unauthorized' );
822
823
		}
824
	}
825
826
	/**
827
	 * Can the current user access Publicize Data.
828
	 *
829
	 * @param int $post_id. 0 for general access. Post_ID for specific access.
0 ignored issues
show
Documentation introduced by
There is no parameter named $post_id.. Did you maybe mean $post_id?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
830
	 * @return bool
831
	 */
832
	function current_user_can_access_publicize_data( $post_id = 0 ) {
833
		/**
834
		 * 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.
835
		 *
836
		 * @module publicize
837
		 *
838
		 * @since 4.1.0
839
		 *
840
		 * @param string $capability User capability needed to use publicize
841
		 */
842
		$capability = apply_filters( 'jetpack_publicize_capability', 'publish_posts' );
843
844
		if ( 'publish_posts' === $capability && $post_id ) {
845
			return current_user_can( 'publish_post', $post_id );
846
		}
847
848
		return current_user_can( $capability );
849
	}
850
851
	/**
852
	 * Auth callback for the protected ->POST_MESS post_meta
853
	 *
854
	 * @param bool $allowed
855
	 * @param string $meta_key
856
	 * @param int $object_id Post ID
857
	 * @return bool
858
	 */
859
	function message_meta_auth_callback( $allowed, $meta_key, $object_id ) {
860
		return $this->current_user_can_access_publicize_data( $object_id );
861
	}
862
863
	/**
864
	 * Registers the post_meta for use in the REST API.
865
	 *
866
	 * Registers for each post type that with `publicize` feature support.
867
	 */
868
	function register_post_meta() {
869
		$message_args = array(
870
			'type'          => 'string',
871
			'description'   => __( 'The message to use instead of the title when sharing to Publicize Services', 'jetpack' ),
872
			'single'        => true,
873
			'default'       => '',
874
			'show_in_rest'  => array(
875
				'name' => 'jetpack_publicize_message',
876
			),
877
			'auth_callback' => array( $this, 'message_meta_auth_callback' ),
878
		);
879
880
		$tweetstorm_args = array(
881
			'type'          => 'boolean',
882
			'description'   => __( 'Whether or not the post should be treated as a Twitter thread.', 'jetpack' ),
883
			'single'        => true,
884
			'default'       => false,
885
			'show_in_rest'  => array(
886
				'name' => 'jetpack_is_tweetstorm',
887
			),
888
			'auth_callback' => array( $this, 'message_meta_auth_callback' ),
889
		);
890
891
		foreach ( get_post_types() as $post_type ) {
892
			if ( ! $this->post_type_is_publicizeable( $post_type ) ) {
893
				continue;
894
			}
895
896
			$message_args['object_subtype']    = $post_type;
897
			$tweetstorm_args['object_subtype'] = $post_type;
898
899
			register_meta( 'post', $this->POST_MESS, $message_args );
900
			register_meta( 'post', $this->POST_TWEETSTORM, $tweetstorm_args );
901
		}
902
	}
903
904
	/**
905
	 * Fires when a post is saved, checks conditions and saves state in postmeta so that it
906
	 * can be picked up later by @see ::publicize_post() on WordPress.com codebase.
907
	 *
908
	 * Attached to the `save_post` action.
909
	 *
910
	 * @param int $post_id
911
	 * @param WP_Post $post
912
	 * @return void
913
	 */
914
	function save_meta( $post_id, $post ) {
915
		$cron_user = null;
916
		$submit_post = true;
917
918
		if ( ! $this->post_type_is_publicizeable( $post->post_type ) )
919
			return;
920
921
		// Don't Publicize during certain contexts:
922
923
		// - import
924
		if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING  ) {
925
			$submit_post = false;
926
		}
927
928
		// - on quick edit, autosave, etc but do fire on p2, quickpress, and instapost ajax
929
		if (
930
			defined( 'DOING_AJAX' )
931
		&&
932
			DOING_AJAX
933
		&&
934
			!did_action( 'p2_ajax' )
935
		&&
936
			!did_action( 'wp_ajax_json_quickpress_post' )
937
		&&
938
			!did_action( 'wp_ajax_instapost_publish' )
939
		&&
940
			!did_action( 'wp_ajax_post_reblog' )
941
		&&
942
			!did_action( 'wp_ajax_press-this-save-post' )
943
		) {
944
			$submit_post = false;
945
		}
946
947
		// - bulk edit
948
		if ( isset( $_GET['bulk_edit'] ) ) {
949
			$submit_post = false;
950
		}
951
952
		// - API/XML-RPC Test Posts
953
		if (
954
			(
955
				defined( 'XMLRPC_REQUEST' )
956
			&&
957
				XMLRPC_REQUEST
958
			||
959
				defined( 'APP_REQUEST' )
960
			&&
961
				APP_REQUEST
962
			)
963
		&&
964
			0 === strpos( $post->post_title, 'Temporary Post Used For Theme Detection' )
965
		) {
966
			$submit_post = false;
967
		}
968
969
		// only work with certain statuses (avoids inherits, auto drafts etc)
970
		if ( !in_array( $post->post_status, array( 'publish', 'draft', 'future' ) ) ) {
971
			$submit_post = false;
972
		}
973
974
		// don't publish password protected posts
975
		if ( '' !== $post->post_password ) {
976
			$submit_post = false;
977
		}
978
979
		// Did this request happen via wp-admin?
980
		$from_web = isset( $_SERVER['REQUEST_METHOD'] )
981
			&&
982
			'post' == strtolower( $_SERVER['REQUEST_METHOD'] )
983
			&&
984
			isset( $_POST[$this->ADMIN_PAGE] );
985
986
		if ( ( $from_web || defined( 'POST_BY_EMAIL' ) ) && isset( $_POST['wpas_title'] ) ) {
987
			if ( empty( $_POST['wpas_title'] ) ) {
988
				delete_post_meta( $post_id, $this->POST_MESS );
989
			} else {
990
				update_post_meta( $post_id, $this->POST_MESS, trim( stripslashes( $_POST['wpas_title'] ) ) );
991
			}
992
		}
993
994
		// change current user to provide context for get_services() if we're running during cron
995
		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
996
			$cron_user = (int) $GLOBALS['user_ID'];
997
			wp_set_current_user( $post->post_author );
998
		}
999
1000
		/**
1001
		 * In this phase, we mark connections that we want to SKIP. When Publicize is actually triggered,
1002
		 * it will Publicize to everything *except* those marked for skipping.
1003
		 */
1004
		foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
1005
			foreach ( $connections as $connection ) {
1006
				$connection_data = '';
1007
				if ( method_exists( $connection, 'get_meta' ) )
1008
					$connection_data = $connection->get_meta( 'connection_data' );
1009
				elseif ( ! empty( $connection['connection_data'] ) )
1010
					$connection_data = $connection['connection_data'];
1011
1012
				/** This action is documented in modules/publicize/ui.php */
1013
				if ( false == apply_filters( 'wpas_submit_post?', $submit_post, $post_id, $service_name, $connection_data ) ) {
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1014
					delete_post_meta( $post_id, $this->PENDING );
1015
					continue;
1016
				}
1017
1018 View Code Duplication
				if ( !empty( $connection->unique_id ) )
1019
					$unique_id = $connection->unique_id;
1020
				else if ( !empty( $connection['connection_data']['token_id'] ) )
1021
					$unique_id = $connection['connection_data']['token_id'];
1022
1023
				// This was a wp-admin request, so we need to check the state of checkboxes
1024
				if ( $from_web ) {
1025
					// delete stray service-based post meta
1026
					delete_post_meta( $post_id, $this->POST_SKIP . $service_name );
1027
1028
					// We *unchecked* this stream from the admin page, or it's set to readonly, or it's a new addition
1029
					if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$unique_id] ) ) {
1030
						// Also make sure that the service-specific input isn't there.
1031
						// If the user connected to a new service 'in-page' then a hidden field with the service
1032
						// name is added, so we just assume they wanted to Publicize to that service.
1033
						if ( empty( $_POST[$this->ADMIN_PAGE]['submit'][$service_name] ) ) {
1034
							// Nothing seems to be checked, so we're going to mark this one to be skipped
1035
							update_post_meta( $post_id, $this->POST_SKIP . $unique_id, 1 );
0 ignored issues
show
Bug introduced by
The variable $unique_id does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1036
							continue;
1037
						} else {
1038
							// clean up any stray post meta
1039
							delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
1040
						}
1041
					} else {
1042
						// The checkbox for this connection is explicitly checked -- make sure we DON'T skip it
1043
						delete_post_meta( $post_id, $this->POST_SKIP . $unique_id );
1044
					}
1045
				}
1046
1047
				/**
1048
				 * Fires right before the post is processed for Publicize.
1049
				 * Users may hook in here and do anything else they need to after meta is written,
1050
				 * and before the post is processed for Publicize.
1051
				 *
1052
				 * @since 2.1.2
1053
				 *
1054
				 * @param bool $submit_post Should the post be publicized.
1055
				 * @param int $post->ID Post ID.
1056
				 * @param string $service_name Service name.
1057
				 * @param array $connection Array of connection details.
1058
				 */
1059
				do_action( 'publicize_save_meta', $submit_post, $post_id, $service_name, $connection );
1060
			}
1061
		}
1062
1063
		if ( defined( 'DOING_CRON' ) && DOING_CRON ) {
1064
			wp_set_current_user( $cron_user );
1065
		}
1066
1067
		// Next up will be ::publicize_post()
1068
	}
1069
1070
	/**
1071
	 * Alters the "Post Published" message to include information about where the post
1072
	 * was Publicized to.
1073
	 *
1074
	 * Attached to the `post_updated_messages` filter
1075
	 *
1076
	 * @param string[] $messages
1077
	 * @return string[]
1078
	 */
1079
	public function update_published_message( $messages ) {
1080
		global $post_type, $post_type_object, $post;
1081
		if ( ! $this->post_type_is_publicizeable( $post_type ) ) {
1082
			return $messages;
1083
		}
1084
1085
		// Bail early if the post is private.
1086
		if ( 'publish' !== $post->post_status ) {
1087
			return $messages;
1088
		}
1089
1090
		$view_post_link_html = '';
1091
		$viewable = is_post_type_viewable( $post_type_object );
1092
		if ( $viewable ) {
1093
			$view_text = esc_html__( 'View post' ); // intentionally omitted domain
1094
1095
			if ( 'jetpack-portfolio' == $post_type ) {
1096
				$view_text = esc_html__( 'View project', 'jetpack' );
1097
			}
1098
1099
			$view_post_link_html = sprintf( ' <a href="%1$s">%2$s</a>',
1100
				esc_url( get_permalink( $post ) ),
1101
				$view_text
1102
			);
1103
		}
1104
1105
		$services = $this->get_publicizing_services( $post->ID );
1106
		if ( empty( $services ) ) {
1107
			return $messages;
1108
		}
1109
1110
		$labels = array();
1111
		foreach ( $services as $service_name => $display_names ) {
1112
			$labels[] = sprintf(
1113
				/* translators: Service name is %1$s, and account name is %2$s. */
1114
				esc_html__( '%1$s (%2$s)', 'jetpack' ),
1115
				esc_html( $service_name ),
1116
				esc_html( implode( ', ', $display_names ) )
1117
			);
1118
		}
1119
1120
		$messages['post'][6] = sprintf(
1121
			/* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1122
			esc_html__( 'Post published and sharing on %1$s.', 'jetpack' ),
1123
			implode( ', ', $labels )
1124
		) . $view_post_link_html;
1125
1126
		if ( $post_type == 'post' && class_exists('Jetpack_Subscriptions' ) ) {
1127
			$subscription = Jetpack_Subscriptions::init();
1128
			if ( $subscription->should_email_post_to_subscribers( $post ) ) {
1129
				$messages['post'][6] = sprintf(
1130
					/* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1131
					esc_html__( 'Post published, sending emails to subscribers and sharing post on %1$s.', 'jetpack' ),
1132
					implode( ', ', $labels )
1133
				) . $view_post_link_html;
1134
			}
1135
		}
1136
1137
		$messages['jetpack-portfolio'][6] = sprintf(
1138
			/* translators: %1$s is a comma-separated list of services and accounts. Ex. Facebook (@jetpack), Twitter (@jetpack) */
1139
			esc_html__( 'Project published and sharing project on %1$s.', 'jetpack' ),
1140
			implode( ', ', $labels )
1141
		) . $view_post_link_html;
1142
1143
		return $messages;
1144
	}
1145
1146
	/**
1147
	 * Get the Connections the Post was just Publicized to.
1148
	 *
1149
	 * Only reliable just after the Post was published.
1150
	 *
1151
	 * @param int $post_id
1152
	 * @return string[] Array of Service display name => Connection display name
1153
	 */
1154
	function get_publicizing_services( $post_id ) {
1155
		$services = array();
1156
1157
		foreach ( (array) $this->get_services( 'connected' ) as $service_name => $connections ) {
1158
			// services have multiple connections.
1159
			foreach ( $connections as $connection ) {
1160
				$unique_id = '';
1161 View Code Duplication
				if ( ! empty( $connection->unique_id ) )
1162
					$unique_id = $connection->unique_id;
1163
				else if ( ! empty( $connection['connection_data']['token_id'] ) )
1164
					$unique_id = $connection['connection_data']['token_id'];
1165
1166
				// Did we skip this connection?
1167
				if ( get_post_meta( $post_id, $this->POST_SKIP . $unique_id,  true ) ) {
1168
					continue;
1169
				}
1170
				$services[ $this->get_service_label( $service_name ) ][] = $this->get_display_name( $service_name, $connection );
1171
			}
1172
		}
1173
1174
		return $services;
1175
	}
1176
1177
	/**
1178
	 * Is the post Publicize-able?
1179
	 *
1180
	 * Only valid prior to Publicizing a Post.
1181
	 *
1182
	 * @param WP_Post $post
1183
	 * @return bool
1184
	 */
1185
	function post_is_publicizeable( $post ) {
1186
		if ( ! $this->post_type_is_publicizeable( $post->post_type ) )
1187
			return false;
1188
1189
		// This is more a precaution. To only publicize posts that are published. (Mostly relevant for Jetpack sites)
1190
		if ( 'publish' !== $post->post_status ) {
1191
			return false;
1192
		}
1193
1194
		// If it's not flagged as ready, then abort. @see ::flag_post_for_publicize()
1195
		if ( ! get_post_meta( $post->ID, $this->PENDING, true ) )
1196
			return false;
1197
1198
		return true;
1199
	}
1200
1201
	/**
1202
	 * Is a given post type Publicize-able?
1203
	 *
1204
	 * Not every CPT lends itself to Publicize-ation.  Allow CPTs to register by adding their CPT via
1205
	 * the publicize_post_types array filter.
1206
	 *
1207
	 * @param string $post_type The post type to check.
1208
	 * @return bool True if the post type can be Publicized.
1209
	 */
1210
	function post_type_is_publicizeable( $post_type ) {
1211
		if ( 'post' == $post_type )
1212
			return true;
1213
1214
		return post_type_supports( $post_type, 'publicize' );
1215
	}
1216
1217
	/**
1218
	 * Already-published posts should not be Publicized by default. This filter sets checked to
1219
	 * false if a post has already been published.
1220
	 *
1221
	 * Attached to the `publicize_checkbox_default` filter
1222
	 *
1223
	 * @param bool $checked
1224
	 * @param int $post_id
1225
	 * @param string $service_name 'facebook', 'twitter', etc
1226
	 * @param object|array The Connection object (WordPress.com) or array (Jetpack)
1227
	 * @return bool
1228
	 */
1229
	function publicize_checkbox_default( $checked, $post_id, $service_name, $connection ) {
1230
		if ( 'publish' == get_post_status( $post_id ) ) {
1231
			return false;
1232
		}
1233
1234
		return $checked;
1235
	}
1236
1237
/*
1238
 * Util
1239
 */
1240
1241
	/**
1242
	 * Converts a Publicize message template string into a sprintf format string
1243
	 *
1244
	 * @param string[] $args
1245
	 *               0 - The Publicize message template: 'Check out my post: %title% @ %url'
1246
	 *             ... - The template tags 'title', 'url', etc.
1247
	 * @return string
1248
	 */
1249
	protected static function build_sprintf( $args ) {
1250
		$search = array();
1251
		$replace = array();
1252
		foreach ( $args as $k => $arg ) {
1253
			if ( 0 == $k ) {
1254
				$string = $arg;
1255
				continue;
1256
			}
1257
			$search[] = "%$arg%";
1258
			$replace[] = "%$k\$s";
1259
		}
1260
		return str_replace( $search, $replace, $string );
0 ignored issues
show
Bug introduced by
The variable $string does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1261
	}
1262
}
1263
1264 View Code Duplication
function publicize_calypso_url() {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1265
	if ( class_exists( 'Jetpack' ) && method_exists( 'Jetpack', 'build_raw_urls' ) ) {
1266
		$site_suffix = Jetpack::build_raw_urls( home_url() );
1267
	} elseif ( class_exists( 'WPCOM_Masterbar' ) && method_exists( 'WPCOM_Masterbar', 'get_calypso_site_slug' ) ) {
1268
		$site_suffix = WPCOM_Masterbar::get_calypso_site_slug( get_current_blog_id() );
1269
	}
1270
1271
	if ( $site_suffix ) {
1272
		return Redirect::get_url( 'calypso-marketing-connections', array( 'site' => $site_suffix ) );
0 ignored issues
show
Bug introduced by
The variable $site_suffix does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1273
	} else {
1274
		return Redirect::get_url( 'calypso-marketing-connections-base' );
1275
	}
1276
}
1277