Completed
Push — renovate/slack-web-api-5.x ( f1014a...4f2b74 )
by
unknown
30:35 queued 23:31
created

Callables   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 505
Duplicated Lines 3.76 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 0
Metric Value
wmc 58
lcom 1
cbo 6
dl 19
loc 505
rs 4.5599
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A set_defaults() 0 7 2
A init_listeners() 0 18 2
A init_full_sync_listeners() 0 3 1
A init_before_send() 0 6 1
A reset_data() 0 9 2
A set_callable_whitelist() 0 3 1
A get_callable_whitelist() 0 3 1
A get_all_callables() 0 11 1
A get_callable() 0 3 1
A enqueue_full_sync_actions() 0 13 1
A send_full_sync_actions() 0 7 1
A estimate_full_sync_actions() 0 3 1
A get_full_sync_actions() 0 3 1
A unlock_sync_callable() 0 3 1
A unlock_plugin_action_link_and_callables() 0 5 1
C set_plugin_action_links() 0 73 16
A should_send_callable() 0 12 3
B maybe_sync_callables() 19 50 11
A get_always_sent_callables() 0 19 5
A expand_callables() 0 13 3
A total() 0 3 1

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 Callables 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 Callables, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Callables sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Functions;
11
use Automattic\Jetpack\Sync\Defaults;
12
use Automattic\Jetpack\Sync\Settings;
13
use Automattic\Jetpack\Constants as Jetpack_Constants;
14
15
/**
16
 * Class to handle sync for callables.
17
 */
18
class Callables extends Module {
19
	/**
20
	 * Name of the callables checksum option.
21
	 *
22
	 * @var string
23
	 */
24
	const CALLABLES_CHECKSUM_OPTION_NAME = 'jetpack_callables_sync_checksum';
25
26
	/**
27
	 * Name of the transient for locking callables.
28
	 *
29
	 * @var string
30
	 */
31
	const CALLABLES_AWAIT_TRANSIENT_NAME = 'jetpack_sync_callables_await';
32
33
	/**
34
	 * Whitelist for callables we want to sync.
35
	 *
36
	 * @access private
37
	 *
38
	 * @var array
39
	 */
40
	private $callable_whitelist;
41
42
	/**
43
	 * For some options, we should always send the change right away!
44
	 *
45
	 * @access public
46
	 *
47
	 * @var array
48
	 */
49
	const ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS = array(
50
		'jetpack_active_modules',
51
		'home', // option is home, callable is home_url.
52
		'siteurl',
53
		'jetpack_sync_error_idc',
54
		'paused_plugins',
55
		'paused_themes',
56
	);
57
58
	/**
59
	 * For some options, the callable key differs from the option name/key
60
	 *
61
	 * @access public
62
	 *
63
	 * @var array
64
	 */
65
	const OPTION_NAMES_TO_CALLABLE_NAMES = array(
66
		// @TODO: Audit the other option names for differences between the option names and callable names.
67
		'home'    => 'home_url',
68
		'siteurl' => 'site_url',
69
	);
70
71
	/**
72
	 * Sync module name.
73
	 *
74
	 * @access public
75
	 *
76
	 * @return string
77
	 */
78
	public function name() {
79
		return 'functions';
80
	}
81
82
	/**
83
	 * Set module defaults.
84
	 * Define the callable whitelist based on whether this is a single site or a multisite installation.
85
	 *
86
	 * @access public
87
	 */
88
	public function set_defaults() {
89
		if ( is_multisite() ) {
90
			$this->callable_whitelist = array_merge( Defaults::get_callable_whitelist(), Defaults::get_multisite_callable_whitelist() );
91
		} else {
92
			$this->callable_whitelist = Defaults::get_callable_whitelist();
93
		}
94
	}
95
96
	/**
97
	 * Initialize callables action listeners.
98
	 *
99
	 * @access public
100
	 *
101
	 * @param callable $callable Action handler callable.
102
	 */
103
	public function init_listeners( $callable ) {
104
		add_action( 'jetpack_sync_callable', $callable, 10, 2 );
105
		add_action( 'current_screen', array( $this, 'set_plugin_action_links' ), 9999 ); // Should happen very late.
106
107
		foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option ) {
108
			add_action( "update_option_{$option}", array( $this, 'unlock_sync_callable' ) );
109
			add_action( "delete_option_{$option}", array( $this, 'unlock_sync_callable' ) );
110
		}
111
112
		// Provide a hook so that hosts can send changes to certain callables right away.
113
		// Especially useful when a host uses constants to change home and siteurl.
114
		add_action( 'jetpack_sync_unlock_sync_callable', array( $this, 'unlock_sync_callable' ) );
115
116
		// get_plugins and wp_version
117
		// gets fired when new code gets installed, updates etc.
118
		add_action( 'upgrader_process_complete', array( $this, 'unlock_plugin_action_link_and_callables' ) );
119
		add_action( 'update_option_active_plugins', array( $this, 'unlock_plugin_action_link_and_callables' ) );
120
	}
121
122
	/**
123
	 * Initialize callables action listeners for full sync.
124
	 *
125
	 * @access public
126
	 *
127
	 * @param callable $callable Action handler callable.
128
	 */
129
	public function init_full_sync_listeners( $callable ) {
130
		add_action( 'jetpack_full_sync_callables', $callable );
131
	}
132
133
	/**
134
	 * Initialize the module in the sender.
135
	 *
136
	 * @access public
137
	 */
138
	public function init_before_send() {
139
		add_action( 'jetpack_sync_before_send_queue_sync', array( $this, 'maybe_sync_callables' ) );
140
141
		// Full sync.
142
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_callables', array( $this, 'expand_callables' ) );
143
	}
144
145
	/**
146
	 * Perform module cleanup.
147
	 * Deletes any transients and options that this module uses.
148
	 * Usually triggered when uninstalling the plugin.
149
	 *
150
	 * @access public
151
	 */
152
	public function reset_data() {
153
		delete_option( self::CALLABLES_CHECKSUM_OPTION_NAME );
154
		delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
155
156
		$url_callables = array( 'home_url', 'site_url', 'main_network_site_url' );
157
		foreach ( $url_callables as $callable ) {
158
			delete_option( Functions::HTTPS_CHECK_OPTION_PREFIX . $callable );
159
		}
160
	}
161
162
	/**
163
	 * Set the callable whitelist.
164
	 *
165
	 * @access public
166
	 *
167
	 * @param array $callables The new callables whitelist.
168
	 */
169
	public function set_callable_whitelist( $callables ) {
170
		$this->callable_whitelist = $callables;
171
	}
172
173
	/**
174
	 * Get the callable whitelist.
175
	 *
176
	 * @access public
177
	 *
178
	 * @return array The callables whitelist.
179
	 */
180
	public function get_callable_whitelist() {
181
		return $this->callable_whitelist;
182
	}
183
184
	/**
185
	 * Retrieve all callables as per the current callables whitelist.
186
	 *
187
	 * @access public
188
	 *
189
	 * @return array All callables.
190
	 */
191
	public function get_all_callables() {
192
		// get_all_callables should run as the master user always.
193
		$current_user_id = get_current_user_id();
194
		wp_set_current_user( \Jetpack_Options::get_option( 'master_user' ) );
195
		$callables = array_combine(
196
			array_keys( $this->get_callable_whitelist() ),
197
			array_map( array( $this, 'get_callable' ), array_values( $this->get_callable_whitelist() ) )
198
		);
199
		wp_set_current_user( $current_user_id );
200
		return $callables;
201
	}
202
203
	/**
204
	 * Invoke a particular callable.
205
	 * Used as a wrapper to standartize invocation.
206
	 *
207
	 * @access private
208
	 *
209
	 * @param callable $callable Callable to invoke.
210
	 * @return mixed Return value of the callable.
211
	 */
212
	private function get_callable( $callable ) {
213
		return call_user_func( $callable );
214
	}
215
216
	/**
217
	 * Enqueue the callable actions for full sync.
218
	 *
219
	 * @access public
220
	 *
221
	 * @param array   $config               Full sync configuration for this sync module.
222
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
223
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
224
	 * @return array Number of actions enqueued, and next module state.
225
	 */
226
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
227
		/**
228
		 * Tells the client to sync all callables to the server
229
		 *
230
		 * @since 4.2.0
231
		 *
232
		 * @param boolean Whether to expand callables (should always be true)
233
		 */
234
		do_action( 'jetpack_full_sync_callables', true );
235
236
		// The number of actions enqueued, and next module state (true == done).
237
		return array( 1, true );
238
	}
239
240
	/**
241
	 * Send the callable actions for full sync.
242
	 *
243
	 * @access public
244
	 *
245
	 * @param array $config Full sync configuration for this sync module.
246
	 * @param int   $send_until The timestamp until the current request can send.
247
	 * @param array $status This Module Full Sync Status.
248
	 *
249
	 * @return array This Module Full Sync Status.
250
	 */
251
	public function send_full_sync_actions( $config, $send_until, $status ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
252
		// we call this instead of do_action when sending immediately.
253
		$this->send_action( 'jetpack_full_sync_callables', array( true ) );
254
255
		// The number of actions enqueued, and next module state (true == done).
256
		return array( 'finished' => true );
257
	}
258
259
	/**
260
	 * Retrieve an estimated number of actions that will be enqueued.
261
	 *
262
	 * @access public
263
	 *
264
	 * @param array $config Full sync configuration for this sync module.
265
	 * @return array Number of items yet to be enqueued.
266
	 */
267
	public function estimate_full_sync_actions( $config ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
268
		return 1;
269
	}
270
271
	/**
272
	 * Retrieve the actions that will be sent for this module during a full sync.
273
	 *
274
	 * @access public
275
	 *
276
	 * @return array Full sync actions of this module.
277
	 */
278
	public function get_full_sync_actions() {
279
		return array( 'jetpack_full_sync_callables' );
280
	}
281
282
	/**
283
	 * Unlock callables so they would be available for syncing again.
284
	 *
285
	 * @access public
286
	 */
287
	public function unlock_sync_callable() {
288
		delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
289
	}
290
291
	/**
292
	 * Unlock callables and plugin action links.
293
	 *
294
	 * @access public
295
	 */
296
	public function unlock_plugin_action_link_and_callables() {
297
		delete_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME );
298
		delete_transient( 'jetpack_plugin_api_action_links_refresh' );
299
		add_filter( 'jetpack_check_and_send_callables', '__return_true' );
300
	}
301
302
	/**
303
	 * Parse and store the plugin action links if on the plugins page.
304
	 *
305
	 * @uses \DOMDocument
306
	 * @uses libxml_use_internal_errors
307
	 * @uses mb_convert_encoding
308
	 *
309
	 * @access public
310
	 */
311
	public function set_plugin_action_links() {
312
		if (
313
			! class_exists( '\DOMDocument' ) ||
314
			! function_exists( 'libxml_use_internal_errors' ) ||
315
			! function_exists( 'mb_convert_encoding' )
316
		) {
317
			return;
318
		}
319
320
		$current_screeen = get_current_screen();
321
322
		$plugins_action_links = array();
323
		// Is the transient lock in place?
324
		$plugins_lock = get_transient( 'jetpack_plugin_api_action_links_refresh', false );
325
		if ( ! empty( $plugins_lock ) && ( isset( $current_screeen->id ) && 'plugins' !== $current_screeen->id ) ) {
326
			return;
327
		}
328
		$plugins = array_keys( Functions::get_plugins() );
329
		foreach ( $plugins as $plugin_file ) {
330
			/**
331
			 *  Plugins often like to unset things but things break if they are not able to.
332
			 */
333
			$action_links = array(
334
				'deactivate' => '',
335
				'activate'   => '',
336
				'details'    => '',
337
				'delete'     => '',
338
				'edit'       => '',
339
			);
340
			/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
341
			$action_links = apply_filters( 'plugin_action_links', $action_links, $plugin_file, null, 'all' );
342
			/** This filter is documented in src/wp-admin/includes/class-wp-plugins-list-table.php */
343
			$action_links           = apply_filters( "plugin_action_links_{$plugin_file}", $action_links, $plugin_file, null, 'all' );
344
			$action_links           = array_filter( $action_links );
345
			$formatted_action_links = null;
346
			if ( ! empty( $action_links ) && count( $action_links ) > 0 ) {
347
				$dom_doc = new \DOMDocument();
348
				foreach ( $action_links as $action_link ) {
349
					// The @ is not enough to suppress errors when dealing with libxml,
350
					// we have to tell it directly how we want to handle errors.
351
					libxml_use_internal_errors( true );
352
					$dom_doc->loadHTML( mb_convert_encoding( $action_link, 'HTML-ENTITIES', 'UTF-8' ) );
353
					libxml_use_internal_errors( false );
354
355
					$link_elements = $dom_doc->getElementsByTagName( 'a' );
356
					if ( 0 === $link_elements->length ) {
357
						continue;
358
					}
359
360
					$link_element = $link_elements->item( 0 );
361
					// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
362
					if ( $link_element->hasAttribute( 'href' ) && $link_element->nodeValue ) {
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
363
						$link_url = trim( $link_element->getAttribute( 'href' ) );
364
365
						// Add the full admin path to the url if the plugin did not provide it.
366
						$link_url_scheme = wp_parse_url( $link_url, PHP_URL_SCHEME );
367
						if ( empty( $link_url_scheme ) ) {
368
							$link_url = admin_url( $link_url );
369
						}
370
371
						// phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
372
						$formatted_action_links[ $link_element->nodeValue ] = $link_url;
373
					}
374
				}
375
			}
376
			if ( $formatted_action_links ) {
377
				$plugins_action_links[ $plugin_file ] = $formatted_action_links;
378
			}
379
		}
380
		// Cache things for a long time.
381
		set_transient( 'jetpack_plugin_api_action_links_refresh', time(), DAY_IN_SECONDS );
382
		update_option( 'jetpack_plugin_api_action_links', $plugins_action_links );
383
	}
384
385
	/**
386
	 * Whether a certain callable should be sent.
387
	 *
388
	 * @access public
389
	 *
390
	 * @param array  $callable_checksums Callable checksums.
391
	 * @param string $name               Name of the callable.
392
	 * @param string $checksum           A checksum of the callable.
393
	 * @return boolean Whether to send the callable.
394
	 */
395
	public function should_send_callable( $callable_checksums, $name, $checksum ) {
396
		$idc_override_callables = array(
397
			'main_network_site',
398
			'home_url',
399
			'site_url',
400
		);
401
		if ( in_array( $name, $idc_override_callables, true ) && \Jetpack_Options::get_option( 'migrate_for_idc' ) ) {
402
			return true;
403
		}
404
405
		return ! $this->still_valid_checksum( $callable_checksums, $name, $checksum );
406
	}
407
408
	/**
409
	 * Sync the callables if we're supposed to.
410
	 *
411
	 * @access public
412
	 */
413
	public function maybe_sync_callables() {
414
415
		$callables = $this->get_all_callables();
416
		if ( ! apply_filters( 'jetpack_check_and_send_callables', false ) ) {
417
			if ( ! is_admin() ) {
418
				// If we're not an admin and we're not doing cron and this isn't WP_CLI, don't sync anything.
419
				if ( ! Settings::is_doing_cron() && ! Jetpack_Constants::get_constant( 'WP_CLI' ) ) {
420
					return;
421
				}
422
				// If we're not an admin and we are doing cron, sync the Callables that are always supposed to sync ( See https://github.com/Automattic/jetpack/issues/12924 ).
423
				$callables = $this->get_always_sent_callables();
424
			}
425
			if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) {
426
				return;
427
			}
428
		}
429
430
		if ( empty( $callables ) ) {
431
			return;
432
		}
433
434
		set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Defaults::$default_sync_callables_wait_time );
435
436
		$callable_checksums = (array) \Jetpack_Options::get_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, array() );
437
		$has_changed        = false;
438
		// Only send the callables that have changed.
439 View Code Duplication
		foreach ( $callables as $name => $value ) {
440
			$checksum = $this->get_check_sum( $value );
441
			// Explicitly not using Identical comparison as get_option returns a string.
442
			if ( ! is_null( $value ) && $this->should_send_callable( $callable_checksums, $name, $checksum ) ) {
443
				/**
444
				 * Tells the client to sync a callable (aka function) to the server
445
				 *
446
				 * @since 4.2.0
447
				 *
448
				 * @param string The name of the callable
449
				 * @param mixed The value of the callable
450
				 */
451
				do_action( 'jetpack_sync_callable', $name, $value );
452
				$callable_checksums[ $name ] = $checksum;
453
				$has_changed                 = true;
454
			} else {
455
				$callable_checksums[ $name ] = $checksum;
456
			}
457
		}
458
		if ( $has_changed ) {
459
			\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callable_checksums );
460
		}
461
462
	}
463
464
	/**
465
	 * Get the callables that should always be sent, e.g. on cron.
466
	 *
467
	 * @return array Callables that should always be sent
468
	 */
469
	protected function get_always_sent_callables() {
470
		$callables      = $this->get_all_callables();
471
		$cron_callables = array();
472
		foreach ( self::ALWAYS_SEND_UPDATES_TO_THESE_OPTIONS as $option_name ) {
473
			if ( array_key_exists( $option_name, $callables ) ) {
474
				$cron_callables[ $option_name ] = $callables[ $option_name ];
475
				continue;
476
			}
477
478
			// Check for the Callable name/key for the option, if different from option name.
479
			if ( array_key_exists( $option_name, self::OPTION_NAMES_TO_CALLABLE_NAMES ) ) {
480
				$callable_name = self::OPTION_NAMES_TO_CALLABLE_NAMES[ $option_name ];
481
				if ( array_key_exists( $callable_name, $callables ) ) {
482
					$cron_callables[ $callable_name ] = $callables[ $callable_name ];
483
				}
484
			}
485
		}
486
		return $cron_callables;
487
	}
488
489
	/**
490
	 * Expand the callables within a hook before they are serialized and sent to the server.
491
	 *
492
	 * @access public
493
	 *
494
	 * @param array $args The hook parameters.
495
	 * @return array $args The hook parameters.
496
	 */
497
	public function expand_callables( $args ) {
498
		if ( $args[0] ) {
499
			$callables           = $this->get_all_callables();
500
			$callables_checksums = array();
501
			foreach ( $callables as $name => $value ) {
502
				$callables_checksums[ $name ] = $this->get_check_sum( $value );
503
			}
504
			\Jetpack_Options::update_raw_option( self::CALLABLES_CHECKSUM_OPTION_NAME, $callables_checksums );
505
			return $callables;
506
		}
507
508
		return $args;
509
	}
510
511
	/**
512
	 * Return Total number of objects.
513
	 *
514
	 * @param array $config Full Sync config.
515
	 *
516
	 * @return int total
517
	 */
518
	public function total( $config ) {
519
		return count( $this->get_callable_whitelist() );
520
	}
521
522
}
523