Completed
Push — renovate/react-monorepo ( 545e89...65a81e )
by
unknown
268:48 queued 253:47
created

Publicize_Base   F

Complexity

Total Complexity 162

Size/Duplication

Total Lines 1258
Duplicated Lines 1.51 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 19
loc 1258
rs 0.8
c 0
b 0
f 0
wmc 162
lcom 2
cbo 2

43 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 76 1
get_services() 0 1 ?
A can_connect_service() 0 3 1
A is_enabled() 0 10 5
connect_url() 0 1 ?
refresh_url() 0 1 ?
disconnect_url() 0 1 ?
B get_service_label() 0 17 7
get_connections() 0 1 ?
get_connection() 0 1 ?
get_connection_id() 0 1 ?
get_connection_unique_id() 0 1 ?
get_connection_meta() 0 1 ?
disconnect() 0 1 ?
globalize_connection() 0 1 ?
unglobalize_connection() 0 1 ?
C get_profile_link() 3 36 13
A get_display_name() 3 16 6
C show_options_popup() 0 23 13
A is_valid_facebook_connection() 0 8 2
A is_invalid_linkedin_connection() 0 5 1
A is_connecting_connection() 0 5 1
A test_publicize_conns() 0 3 1
C get_publicize_conns_test_results() 5 66 11
test_connection() 0 1 ?
F get_filtered_connection_data() 0 135 19
post_is_done_sharing() 0 1 ?
A get_available_service_data() 0 14 2
A user_id() 0 3 1
A blog_id() 0 3 1
flag_post_for_publicize() 0 1 ?
A add_post_type_support() 0 3 1
A register_gutenberg_extension() 0 11 2
A current_user_can_access_publicize_data() 0 18 3
A message_meta_auth_callback() 0 3 1
A register_post_meta() 0 35 3
F save_meta() 4 156 40
C update_published_message() 0 66 10
B get_publicizing_services() 4 22 6
A post_is_publicizeable() 0 15 4
A post_type_is_publicizeable() 0 6 2
A publicize_checkbox_default() 0 7 2
A build_sprintf() 0 13 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Publicize_Base often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Publicize_Base, and based on these observations, apply Extract Interface, too.

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