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 |