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 classes like Jetpack_Sync_Client 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 Jetpack_Sync_Client, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class Jetpack_Sync_Client { |
||
9 | |||
10 | const CONSTANTS_CHECKSUM_OPTION_NAME = 'jetpack_constants_sync_checksum'; |
||
11 | const CALLABLES_CHECKSUM_OPTION_NAME = 'jetpack_callables_sync_checksum'; |
||
12 | const SYNC_THROTTLE_OPTION_NAME = 'jetpack_sync_min_wait'; |
||
13 | const LAST_SYNC_TIME_OPTION_NAME = 'jetpack_last_sync_time'; |
||
14 | const CALLABLES_AWAIT_TRANSIENT_NAME = 'jetpack_sync_callables_await'; |
||
15 | const CONSTANTS_AWAIT_TRANSIENT_NAME = 'jetpack_sync_constants_await'; |
||
16 | |||
17 | private $checkout_memory_size; |
||
18 | private $upload_max_bytes; |
||
19 | private $upload_max_rows; |
||
20 | private $sync_queue; |
||
21 | private $full_sync_client; |
||
22 | private $codec; |
||
23 | private $options_whitelist; |
||
24 | private $constants_whitelist; |
||
25 | private $meta_types = array( 'post', 'comment' ); |
||
26 | private $callable_whitelist; |
||
27 | private $network_options_whitelist; |
||
28 | private $taxonomy_whitelist; |
||
29 | private $is_multisite; |
||
30 | |||
31 | // singleton functions |
||
32 | private static $instance; |
||
33 | |||
34 | public static function getInstance() { |
||
41 | |||
42 | // this is necessary because you can't use "new" when you declare instance properties >:( |
||
43 | protected function __construct() { |
||
47 | |||
48 | private function init() { |
||
178 | |||
179 | // TODO: Refactor to use one set whitelist function, with one is_whitelisted. |
||
180 | function set_options_whitelist( $options ) { |
||
183 | |||
184 | function get_options_whitelist() { |
||
187 | |||
188 | function set_constants_whitelist( $constants ) { |
||
191 | |||
192 | function get_callable_whitelist() { |
||
195 | |||
196 | function set_callable_whitelist( $callables ) { |
||
199 | |||
200 | function set_network_options_whitelist( $options ) { |
||
203 | |||
204 | function set_send_buffer_memory_size( $size ) { |
||
207 | |||
208 | // in bytes |
||
209 | function set_upload_max_bytes( $max_bytes ) { |
||
212 | |||
213 | // in rows |
||
214 | function set_upload_max_rows( $max_rows ) { |
||
217 | |||
218 | // in seconds |
||
219 | function set_min_sync_wait_time( $seconds ) { |
||
222 | |||
223 | function get_min_sync_wait_time() { |
||
226 | |||
227 | private function get_last_sync_time() { |
||
230 | |||
231 | private function set_last_sync_time() { |
||
234 | |||
235 | function set_taxonomy_whitelist( $taxonomies ) { |
||
238 | |||
239 | function is_whitelisted_option( $option ) { |
||
242 | |||
243 | function is_whitelisted_network_option( $option ) { |
||
246 | |||
247 | function set_codec( iJetpack_Sync_Codec $codec ) { |
||
250 | |||
251 | function set_full_sync_client( $full_sync_client ) { |
||
263 | |||
264 | function get_full_sync_client() { |
||
267 | |||
268 | function action_handler() { |
||
311 | |||
312 | function send_theme_info() { |
||
313 | global $_wp_theme_features; |
||
314 | |||
315 | $theme_support = array(); |
||
316 | |||
317 | foreach ( Jetpack_Sync_Defaults::$default_theme_support_whitelist as $theme_feature ) { |
||
318 | $has_support = current_theme_supports( $theme_feature ); |
||
319 | if ( $has_support ) { |
||
320 | $theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ]; |
||
321 | } |
||
322 | |||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Fires when the client needs to sync theme support info |
||
327 | * Only sends theme support attributes whitelisted in Jetpack_Sync_Defaults::$default_theme_support_whitelist |
||
328 | * |
||
329 | * @since 4.1.0 |
||
330 | * |
||
331 | * @param object the theme support hash |
||
332 | */ |
||
333 | do_action( 'jetpack_sync_current_theme_support', $theme_support ); |
||
334 | } |
||
335 | |||
336 | function send_wp_version( $update, $meta_data ) { |
||
337 | if ( 'update' === $meta_data['action'] && 'core' === $meta_data['type'] ) { |
||
338 | global $wp_version; |
||
339 | |||
340 | /** |
||
341 | * Fires when the client needs to sync WordPress version |
||
342 | * |
||
343 | * @since 4.1.0 |
||
344 | * |
||
345 | * @param string The WordPress version number |
||
346 | */ |
||
347 | do_action( 'jetpack_sync_wp_version', $wp_version ); |
||
348 | } |
||
349 | } |
||
350 | |||
351 | function save_term_handler( $term_id, $tt_id, $taxonomy ) { |
||
352 | if ( class_exists( 'WP_Term' ) ) { |
||
353 | $term_object = WP_Term::get_instance( $term_id, $taxonomy ); |
||
354 | } else { |
||
355 | $term_object = get_term_by( 'id', $term_id, $taxonomy ); |
||
356 | } |
||
357 | |||
358 | /** |
||
359 | * Fires when the client needs to sync a new term |
||
360 | * |
||
361 | * @since 4.1.0 |
||
362 | * |
||
363 | * @param object the Term object |
||
364 | */ |
||
365 | do_action( 'jetpack_sync_save_term', $term_object ); |
||
366 | } |
||
367 | |||
368 | function send_attachment_info( $attachment_id ) { |
||
369 | $attachment = get_post( $attachment_id ); |
||
370 | |||
371 | /** |
||
372 | * Fires when the client needs to sync an attachment for a post |
||
373 | * |
||
374 | * @since 4.1.0 |
||
375 | * |
||
376 | * @param int The attachment ID |
||
377 | * @param object The attachment |
||
378 | */ |
||
379 | do_action( 'jetpack_sync_save_add_attachment', $attachment_id, $attachment ); |
||
380 | } |
||
381 | |||
382 | function save_user_handler( $user_id, $old_user_data = null ) { |
||
383 | $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); |
||
384 | |||
385 | // Older versions of WP don't pass the old_user_data in ->data |
||
386 | if ( isset( $old_user_data->data ) ) { |
||
387 | $old_user = $old_user_data->data; |
||
388 | } else { |
||
389 | $old_user = $old_user_data; |
||
390 | } |
||
391 | |||
392 | if ( $old_user !== null ) { |
||
393 | unset( $old_user->user_pass ); |
||
394 | if ( serialize( $old_user ) === serialize( $user->data ) ) { |
||
395 | return; |
||
396 | } |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Fires when the client needs to sync an updated user |
||
401 | * |
||
402 | * @since 4.1.0 |
||
403 | * |
||
404 | * @param object The WP_User object |
||
405 | */ |
||
406 | do_action( 'jetpack_sync_save_user', $user ); |
||
407 | } |
||
408 | |||
409 | function save_user_role_handler( $user_id, $role, $old_roles = null ) { |
||
410 | $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); |
||
411 | |||
412 | /** |
||
413 | * Fires when the client needs to sync an updated user |
||
414 | * |
||
415 | * @since 4.1.0 |
||
416 | * |
||
417 | * @param object The WP_User object |
||
418 | */ |
||
419 | do_action( 'jetpack_sync_save_user', $user ); |
||
420 | } |
||
421 | |||
422 | function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) { |
||
423 | $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); |
||
424 | if ( $meta_key === $user->cap_key ) { |
||
425 | /** |
||
426 | * Fires when the client needs to sync an updated user |
||
427 | * |
||
428 | * @since 4.1.0 |
||
429 | * |
||
430 | * @param object The WP_User object |
||
431 | */ |
||
432 | do_action( 'jetpack_sync_save_user', $user ); |
||
433 | } |
||
434 | } |
||
435 | |||
436 | public function sanitize_user( $user ) { |
||
441 | |||
442 | |||
443 | function do_sync() { |
||
444 | // don't sync if importing |
||
445 | if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) { |
||
446 | $this->schedule_sync( "+1 minute" ); |
||
447 | |||
448 | return false; |
||
449 | } |
||
450 | |||
451 | // don't sync if we are throttled |
||
452 | $sync_wait = $this->get_min_sync_wait_time(); |
||
453 | $last_sync = $this->get_last_sync_time(); |
||
454 | |||
455 | if ( $last_sync && $sync_wait && $last_sync + $sync_wait > microtime( true ) ) { |
||
456 | return false; |
||
457 | } |
||
458 | |||
459 | $this->set_last_sync_time(); |
||
460 | $this->maybe_sync_constants(); |
||
461 | $this->maybe_sync_callables(); |
||
462 | |||
463 | if ( $this->sync_queue->size() === 0 ) { |
||
464 | return false; |
||
465 | } |
||
466 | |||
467 | $buffer = $this->sync_queue->checkout_with_memory_limit( $this->checkout_memory_size, $this->upload_max_rows ); |
||
468 | |||
469 | if ( ! $buffer ) { |
||
470 | // buffer has no items |
||
471 | return false; |
||
472 | } |
||
473 | |||
474 | if ( is_wp_error( $buffer ) ) { |
||
475 | // another buffer is currently sending |
||
476 | return false; |
||
477 | } |
||
478 | |||
479 | $upload_size = 0; |
||
480 | $items_to_send = array(); |
||
481 | |||
482 | // we estimate the total encoded size as we go by encoding each item individually |
||
483 | // this is expensive, but the only way to really know :/ |
||
484 | foreach ( $buffer->get_items() as $key => $item ) { |
||
485 | |||
486 | /** |
||
487 | * Modify the data within an action before it is serialized and sent to the server |
||
488 | * For example, during full sync this expands Post ID's into full Post objects, |
||
489 | * so that we don't have to serialize the whole object into the queue. |
||
490 | * |
||
491 | * @since 4.1.0 |
||
492 | * |
||
493 | * @param array The action parameters |
||
494 | */ |
||
495 | $item[1] = apply_filters( "jetpack_sync_before_send_" . $item[0], $item[1] ); |
||
496 | |||
497 | $encoded_item = $this->codec->encode( $item ); |
||
498 | $upload_size += strlen( $encoded_item ); |
||
499 | |||
500 | if ( $upload_size > $this->upload_max_bytes && count( $items_to_send ) > 0 ) { |
||
501 | break; |
||
502 | } |
||
503 | |||
504 | $items_to_send[ $key ] = $encoded_item; |
||
505 | } |
||
506 | |||
507 | /** |
||
508 | * Fires when data is ready to send to the server. |
||
509 | * Return false or WP_Error to abort the sync (e.g. if there's an error) |
||
510 | * The items will be automatically re-sent later |
||
511 | * |
||
512 | * @since 4.1 |
||
513 | * |
||
514 | * @param array $data The action buffer |
||
515 | */ |
||
516 | $result = apply_filters( 'jetpack_sync_client_send_data', $items_to_send ); |
||
517 | |||
518 | if ( ! $result || is_wp_error( $result ) ) { |
||
519 | // error_log("There was an error sending data:"); |
||
520 | // error_log(print_r($result, 1)); |
||
521 | $result = $this->sync_queue->checkin( $buffer ); |
||
522 | |||
523 | if ( is_wp_error( $result ) ) { |
||
524 | error_log( "Error checking in buffer: " . $result->get_error_message() ); |
||
525 | $this->sync_queue->force_checkin(); |
||
526 | } |
||
527 | // try again in 1 minute |
||
528 | $this->schedule_sync( "+1 minute" ); |
||
529 | } else { |
||
530 | |||
531 | // scan the sent data to see if a full sync started or finished |
||
532 | if ( $this->buffer_includes_action( $buffer, 'jetpack_full_sync_start' ) ) { |
||
533 | $this->full_sync_client->set_status_sending_started(); |
||
534 | } |
||
535 | |||
536 | if ( $this->buffer_includes_action( $buffer, 'jetpack_full_sync_end' ) ) { |
||
537 | $this->full_sync_client->set_status_sending_finished(); |
||
538 | } |
||
539 | |||
540 | $this->sync_queue->close( $buffer, $result ); |
||
541 | // check if there are any more events in the buffer |
||
542 | // if so, schedule a cron job to happen soon |
||
543 | if ( $this->sync_queue->has_any_items() ) { |
||
544 | $this->schedule_sync( "+1 minute" ); |
||
545 | } |
||
546 | } |
||
547 | } |
||
548 | |||
549 | private function buffer_includes_action( $buffer, $action_name ) { |
||
558 | |||
559 | function expand_wp_insert_post( $args ) { |
||
562 | |||
563 | // Expands wp_insert_post to include filtered content |
||
564 | function filter_post_content_and_add_links( $post ) { |
||
565 | if ( 0 < strlen( $post->post_password ) ) { |
||
566 | $post->post_password = 'auto-' . wp_generate_password( 10, false ); |
||
567 | } |
||
568 | /** This filter is already documented in core. wp-includes/post-template.php */ |
||
569 | $post->post_content_filtered = apply_filters( 'the_content', $post->post_content ); |
||
570 | $post->permalink = get_permalink( $post->ID ); |
||
571 | $post->shortlink = wp_get_shortlink( $post->ID ); |
||
572 | |||
573 | // legacy fields until we fully sync users |
||
574 | $extra = array(); |
||
575 | $extra['author_email'] = get_the_author_meta( 'email', $post->post_author ); |
||
576 | $extra['author_display_name'] = get_the_author_meta( 'display_name', $post->post_author ); |
||
577 | $extra['dont_email_post_to_subs'] = get_post_meta( $post->ID, '_jetpack_dont_email_post_to_subs', true ); |
||
578 | $post->extra = $extra; |
||
579 | |||
580 | return $post; |
||
581 | } |
||
582 | |||
583 | function expand_wp_comment_status_change( $args ) { |
||
586 | |||
587 | function expand_wp_insert_comment( $args ) { |
||
590 | |||
591 | function filter_comment_and_add_hc_meta( $comment ) { |
||
605 | |||
606 | private function schedule_sync( $when ) { |
||
609 | |||
610 | function force_sync_constants() { |
||
618 | |||
619 | function force_sync_options() { |
||
620 | /** |
||
621 | * Tells the client to sync all options to the server |
||
622 | * |
||
623 | * @since 4.1 |
||
624 | * |
||
625 | * @param boolean Whether to expand options (should always be true) |
||
626 | */ |
||
627 | do_action( 'jetpack_full_sync_options', true ); |
||
628 | } |
||
629 | |||
630 | function force_sync_network_options() { |
||
631 | /** |
||
632 | * Tells the client to sync all network options to the server |
||
633 | * |
||
634 | * @since 4.1 |
||
635 | * |
||
636 | * @param boolean Whether to expand options (should always be true) |
||
637 | */ |
||
638 | do_action( 'jetpack_full_sync_network_options', true ); |
||
639 | } |
||
640 | |||
641 | View Code Duplication | private function maybe_sync_constants() { |
|
642 | if ( get_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME ) ) { |
||
643 | return; |
||
644 | } |
||
645 | |||
646 | $constants = $this->get_all_constants(); |
||
647 | if ( empty( $constants ) ) { |
||
648 | return; |
||
649 | } |
||
650 | |||
651 | set_transient( self::CONSTANTS_AWAIT_TRANSIENT_NAME, microtime( true ), Jetpack_Sync_Defaults::$default_sync_constants_wait_time ); |
||
652 | |||
653 | // only send the constants that have changed |
||
654 | foreach ( $constants as $name => $value ) { |
||
655 | $checksum = $this->get_check_sum( $value ); |
||
656 | |||
657 | // explicitly not using Identical comparison as get_option returns a string |
||
658 | if ( $checksum != get_option( self::CONSTANTS_CHECKSUM_OPTION_NAME . "_$name" ) ) { |
||
659 | /** |
||
660 | * Tells the client to sync a constant to the server |
||
661 | * |
||
662 | * @since 4.1 |
||
663 | * |
||
664 | * @param string The name of the constant |
||
665 | * @param mixed The value of the constant |
||
666 | */ |
||
667 | do_action( 'jetpack_sync_constant', $name, $value ); |
||
668 | update_option( self::CONSTANTS_CHECKSUM_OPTION_NAME . "_$name", $checksum ); |
||
669 | } |
||
670 | } |
||
671 | } |
||
672 | |||
673 | private function get_all_constants() { |
||
679 | |||
680 | private function get_constant( $constant ) { |
||
687 | |||
688 | public function force_sync_callables() { |
||
696 | |||
697 | View Code Duplication | private function maybe_sync_callables() { |
|
698 | if ( get_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME ) ) { |
||
699 | return; |
||
700 | } |
||
701 | |||
702 | $callables = $this->get_all_callables(); |
||
703 | if ( empty( $callables ) ) { |
||
704 | return; |
||
705 | } |
||
706 | |||
707 | set_transient( self::CALLABLES_AWAIT_TRANSIENT_NAME, microtime( true ), Jetpack_Sync_Defaults::$default_sync_callables_wait_time ); |
||
708 | |||
709 | // only send the callables that have changed |
||
710 | foreach ( $callables as $name => $value ) { |
||
711 | $checksum = $this->get_check_sum( $value ); |
||
712 | // explicitly not using Identical comparison as get_option returns a string |
||
713 | if ( $checksum != get_option( self::CALLABLES_CHECKSUM_OPTION_NAME . "_$name" ) ) { |
||
714 | /** |
||
715 | * Tells the client to sync a callable (aka function) to the server |
||
716 | * |
||
717 | * @since 4.1 |
||
718 | * |
||
719 | * @param string The name of the callable |
||
720 | * @param mixed The value of the callable |
||
721 | */ |
||
722 | do_action( 'jetpack_sync_callable', $name, $value ); |
||
723 | update_option( self::CALLABLES_CHECKSUM_OPTION_NAME . "_$name", $checksum ); |
||
724 | } |
||
725 | } |
||
726 | } |
||
727 | |||
728 | private function get_all_callables() { |
||
734 | |||
735 | private function get_callable( $callable ) { |
||
738 | |||
739 | // Is public so that we don't have to store so much data all the options twice. |
||
740 | function get_all_options() { |
||
748 | |||
749 | function get_all_network_options() { |
||
757 | |||
758 | private function get_check_sum( $values ) { |
||
761 | |||
762 | View Code Duplication | function jetpack_sync_core_icon() { |
|
778 | |||
779 | function get_sync_queue() { |
||
782 | |||
783 | function reset_sync_queue() { |
||
786 | |||
787 | function set_defaults() { |
||
816 | |||
817 | function reset_data() { |
||
820 | } |
||
821 |