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 | const SETTINGS_OPTION_PREFIX = 'jetpack_sync_settings_'; |
||
17 | |||
18 | private static $valid_settings = array( 'dequeue_max_bytes' => true, 'upload_max_bytes' => true, 'upload_max_rows' => true, 'sync_wait_time' => true ); |
||
19 | |||
20 | private $dequeue_max_bytes; |
||
21 | private $upload_max_bytes; |
||
22 | private $upload_max_rows; |
||
23 | private $sync_queue; |
||
24 | private $full_sync_client; |
||
25 | private $codec; |
||
26 | private $options_whitelist; |
||
27 | private $constants_whitelist; |
||
28 | private $meta_types = array( 'post', 'comment' ); |
||
29 | private $callable_whitelist; |
||
30 | private $network_options_whitelist; |
||
31 | private $taxonomy_whitelist; |
||
32 | private $is_multisite; |
||
33 | |||
34 | // singleton functions |
||
35 | private static $instance; |
||
36 | |||
37 | public static function getInstance() { |
||
44 | |||
45 | // this is necessary because you can't use "new" when you declare instance properties >:( |
||
46 | protected function __construct() { |
||
50 | |||
51 | private function init() { |
||
52 | |||
53 | $handler = array( $this, 'action_handler' ); |
||
54 | |||
55 | /** |
||
56 | * Most of the following hooks are sent to the same $handler |
||
57 | * for immediate serialization and queuing be sent to the server. |
||
58 | * The only exceptions are actions which need additional processing. |
||
59 | */ |
||
60 | |||
61 | // constants |
||
62 | add_action( 'jetpack_sync_constant', $handler, 10, 2 ); |
||
63 | |||
64 | // callables |
||
65 | add_action( 'jetpack_sync_callable', $handler, 10, 2 ); |
||
66 | |||
67 | // posts |
||
68 | add_action( 'wp_insert_post', $handler, 10, 3 ); |
||
69 | add_action( 'deleted_post', $handler, 10 ); |
||
70 | add_filter( 'jetpack_sync_before_send_wp_insert_post', array( $this, 'expand_wp_insert_post' ) ); |
||
71 | |||
72 | add_action( 'jetpack_publicize_post', $handler ); |
||
73 | |||
74 | // attachments |
||
75 | |||
76 | add_action( 'edit_attachment', array( $this, 'send_attachment_info' ) ); |
||
77 | // Once we don't have to support 4.3 we can start using add_action( 'attachment_updated', $handler, 10, 3 ); instead |
||
78 | add_action( 'add_attachment', array( $this, 'send_attachment_info' ) ); |
||
79 | add_action( 'jetpack_sync_save_add_attachment', $handler, 10, 2 ); |
||
80 | |||
81 | // comments |
||
82 | add_action( 'wp_insert_comment', $handler, 10, 2 ); |
||
83 | add_action( 'deleted_comment', $handler, 10 ); |
||
84 | add_action( 'trashed_comment', $handler, 10 ); |
||
85 | add_action( 'spammed_comment', $handler, 10 ); |
||
86 | |||
87 | add_filter( 'jetpack_sync_before_send_wp_insert_comment', array( $this, 'expand_wp_insert_comment' ) ); |
||
88 | |||
89 | // even though it's messy, we implement these hooks because |
||
90 | // the edit_comment hook doesn't include the data |
||
91 | // so this saves us a DB read for every comment event |
||
92 | foreach ( array( '', 'trackback', 'pingback' ) as $comment_type ) { |
||
93 | foreach ( array( 'unapproved', 'approved' ) as $comment_status ) { |
||
94 | $comment_action_name = "comment_{$comment_status}_{$comment_type}"; |
||
95 | add_action( $comment_action_name, $handler, 10, 2 ); |
||
96 | add_filter( 'jetpack_sync_before_send_' . $comment_action_name, array( $this, 'expand_wp_insert_comment' ) ); |
||
97 | } |
||
98 | } |
||
99 | |||
100 | // options |
||
101 | add_action( 'added_option', $handler, 10, 2 ); |
||
102 | add_action( 'updated_option', $handler, 10, 3 ); |
||
103 | add_action( 'deleted_option', $handler, 10, 1 ); |
||
104 | |||
105 | // Sync Core Icon: Detect changes in Core's Site Icon and make it syncable. |
||
106 | add_action( 'add_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); |
||
107 | add_action( 'update_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); |
||
108 | add_action( 'delete_option_site_icon', array( $this, 'jetpack_sync_core_icon' ) ); |
||
109 | |||
110 | // wordpress version |
||
111 | add_action( 'upgrader_process_complete', array( $this, 'send_wp_version' ), 10, 2 ); |
||
112 | add_action( 'jetpack_sync_wp_version', $handler ); |
||
113 | |||
114 | // themes |
||
115 | add_action( 'switch_theme', array( $this, 'send_theme_info' ) ); |
||
116 | add_action( 'jetpack_sync_current_theme_support', $handler, 10 ); // custom hook, see meta-hooks below |
||
117 | |||
118 | // post-meta, and in the future - other meta? |
||
119 | foreach ( $this->meta_types as $meta_type ) { |
||
120 | add_action( "added_{$meta_type}_meta", $handler, 10, 4 ); |
||
121 | add_action( "updated_{$meta_type}_meta", $handler, 10, 4 ); |
||
122 | add_action( "deleted_{$meta_type}_meta", $handler, 10, 4 ); |
||
123 | } |
||
124 | |||
125 | // terms |
||
126 | add_action( 'created_term', array( $this, 'save_term_handler' ), 10, 3 ); |
||
127 | add_action( 'edited_term', array( $this, 'save_term_handler' ), 10, 3 ); |
||
128 | add_action( 'jetpack_sync_save_term', $handler, 10, 4 ); |
||
129 | add_action( 'delete_term', $handler, 10, 4 ); |
||
130 | add_action( 'set_object_terms', $handler, 10, 6 ); |
||
131 | add_action( 'deleted_term_relationships', $handler, 10, 2 ); |
||
132 | |||
133 | // users |
||
134 | add_action( 'user_register', array( $this, 'save_user_handler' ) ); |
||
135 | add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 ); |
||
136 | add_action( 'add_user_to_blog', array( $this, 'save_user_handler' ) ); |
||
137 | add_action( 'jetpack_sync_save_user', $handler, 10, 2 ); |
||
138 | |||
139 | add_action( 'deleted_user', $handler, 10, 2 ); |
||
140 | add_filter( 'jetpack_sync_before_send_jetpack_sync_save_user', array( $this, 'expand_user' ), 10, 2 ); |
||
141 | add_action( 'remove_user_from_blog', $handler, 10, 2 ); |
||
142 | |||
143 | // user roles |
||
144 | add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 ); |
||
145 | add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 ); |
||
146 | add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 ); |
||
147 | |||
148 | // user capabilities |
||
149 | add_action( 'added_user_meta', array( $this, 'save_user_cap_handler' ), 10, 4 ); |
||
150 | add_action( 'updated_user_meta', array( $this, 'save_user_cap_handler' ), 10, 4 ); |
||
151 | add_action( 'deleted_user_meta', array( $this, 'save_user_cap_handler' ), 10, 4 ); |
||
152 | |||
153 | // updates |
||
154 | add_action( 'set_site_transient_update_plugins', $handler, 10, 1 ); |
||
155 | add_action( 'set_site_transient_update_themes', $handler, 10, 1 ); |
||
156 | add_action( 'set_site_transient_update_core', $handler, 10, 1 ); |
||
157 | add_filter( 'jetpack_sync_before_enqueue_set_site_transient_update_plugins', array( $this, 'filter_update_keys' ), 10, 2 ); |
||
158 | |||
159 | // plugins |
||
160 | add_action( 'upgrader_process_complete', $handler, 10, 2 ); |
||
161 | add_filter( 'jetpack_sync_before_send_upgrader_process_complete', array( $this, 'expand_upgrader_process_complete' ) ); |
||
162 | add_action( 'deleted_plugin', $handler, 10, 2 ); |
||
163 | add_action( 'activated_plugin', $handler, 10, 2 ); |
||
164 | add_action( 'deactivated_plugin', $handler, 10, 2 ); |
||
165 | |||
166 | |||
167 | // multi site network options |
||
168 | if ( $this->is_multisite ) { |
||
169 | add_action( 'add_site_option', $handler, 10, 2 ); |
||
170 | add_action( 'update_site_option', $handler, 10, 3 ); |
||
171 | add_action( 'delete_site_option', $handler, 10, 1 ); |
||
172 | } |
||
173 | |||
174 | // synthetic actions for full sync |
||
175 | add_action( 'jetpack_full_sync_start', $handler ); |
||
176 | add_action( 'jetpack_full_sync_end', $handler ); |
||
177 | add_action( 'jetpack_full_sync_options', $handler ); |
||
178 | add_action( 'jetpack_full_sync_posts', $handler ); // also sends post meta |
||
179 | add_action( 'jetpack_full_sync_comments', $handler ); // also send comments meta |
||
180 | add_action( 'jetpack_full_sync_constants', $handler ); |
||
181 | add_action( 'jetpack_full_sync_callables', $handler ); |
||
182 | add_action( 'jetpack_full_sync_updates', $handler ); |
||
183 | |||
184 | add_action( 'jetpack_full_sync_users', $handler ); |
||
185 | add_action( 'jetpack_full_sync_terms', $handler, 10, 2 ); |
||
186 | if ( is_multisite() ) { |
||
187 | add_action( 'jetpack_full_sync_network_options', $handler ); |
||
188 | } |
||
189 | |||
190 | // Module Activation |
||
191 | add_action( 'jetpack_activate_module', $handler ); |
||
192 | add_action( 'jetpack_deactivate_module', $handler ); |
||
193 | |||
194 | /** |
||
195 | * Sync all pending actions with server |
||
196 | */ |
||
197 | add_action( 'jetpack_sync_actions', array( $this, 'do_sync' ) ); |
||
198 | } |
||
199 | |||
200 | // removes unnecessary keys from synced updates data |
||
201 | function filter_update_keys( $args ) { |
||
202 | $updates = $args[0]; |
||
203 | |||
204 | if ( isset( $updates->no_update ) ) { |
||
205 | unset( $updates->no_update ); |
||
206 | } |
||
207 | |||
208 | return $args; |
||
209 | } |
||
210 | |||
211 | function set_options_whitelist( $options ) { |
||
212 | $this->options_whitelist = $options; |
||
213 | } |
||
214 | |||
215 | function get_options_whitelist() { |
||
216 | return $this->options_whitelist; |
||
217 | } |
||
218 | |||
219 | function set_constants_whitelist( $constants ) { |
||
220 | $this->constants_whitelist = $constants; |
||
221 | } |
||
222 | |||
223 | function get_constants_whitelist() { |
||
224 | return $this->constants_whitelist; |
||
225 | } |
||
226 | |||
227 | function set_callable_whitelist( $callables ) { |
||
228 | $this->callable_whitelist = $callables; |
||
229 | } |
||
230 | |||
231 | function get_callable_whitelist() { |
||
232 | return $this->callable_whitelist; |
||
233 | } |
||
234 | |||
235 | function set_network_options_whitelist( $options ) { |
||
236 | $this->network_options_whitelist = $options; |
||
237 | } |
||
238 | |||
239 | function get_network_options_whitelist() { |
||
240 | return $this->network_options_whitelist; |
||
241 | } |
||
242 | |||
243 | function set_dequeue_max_bytes( $size ) { |
||
244 | $this->dequeue_max_bytes = $size; |
||
245 | } |
||
246 | |||
247 | // in bytes |
||
248 | function set_upload_max_bytes( $max_bytes ) { |
||
249 | $this->upload_max_bytes = $max_bytes; |
||
250 | } |
||
251 | |||
252 | // in rows |
||
253 | function set_upload_max_rows( $max_rows ) { |
||
254 | $this->upload_max_rows = $max_rows; |
||
255 | } |
||
256 | |||
257 | // in seconds |
||
258 | function set_sync_wait_time( $seconds ) { |
||
259 | update_option( self::SYNC_THROTTLE_OPTION_NAME, $seconds, true ); |
||
260 | } |
||
261 | |||
262 | function get_sync_wait_time() { |
||
263 | return get_option( self::SYNC_THROTTLE_OPTION_NAME ); |
||
264 | } |
||
265 | |||
266 | private function get_last_sync_time() { |
||
267 | return (double) get_option( self::LAST_SYNC_TIME_OPTION_NAME ); |
||
268 | } |
||
269 | |||
270 | private function set_last_sync_time() { |
||
271 | return update_option( self::LAST_SYNC_TIME_OPTION_NAME, microtime( true ), true ); |
||
272 | } |
||
273 | |||
274 | function set_taxonomy_whitelist( $taxonomies ) { |
||
275 | $this->taxonomy_whitelist = $taxonomies; |
||
276 | } |
||
277 | |||
278 | function is_whitelisted_option( $option ) { |
||
279 | return in_array( $option, $this->options_whitelist ); |
||
280 | } |
||
281 | |||
282 | function is_whitelisted_network_option( $option ) { |
||
283 | return $this->is_multisite && in_array( $option, $this->network_options_whitelist ); |
||
284 | } |
||
285 | |||
286 | function set_codec( iJetpack_Sync_Codec $codec ) { |
||
287 | $this->codec = $codec; |
||
288 | } |
||
289 | |||
290 | function set_full_sync_client( $full_sync_client ) { |
||
291 | if ( $this->full_sync_client ) { |
||
292 | remove_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) ); |
||
293 | } |
||
294 | |||
295 | $this->full_sync_client = $full_sync_client; |
||
296 | |||
297 | /** |
||
298 | * Sync all objects in the database with the server |
||
299 | */ |
||
300 | add_action( 'jetpack_sync_full', array( $this->full_sync_client, 'start' ) ); |
||
301 | } |
||
302 | |||
303 | function get_full_sync_client() { |
||
304 | return $this->full_sync_client; |
||
305 | } |
||
306 | |||
307 | function action_handler() { |
||
308 | $current_filter = current_filter(); |
||
309 | $args = func_get_args(); |
||
310 | if ( in_array( $current_filter, array( 'deleted_option', 'added_option', 'updated_option' ) ) |
||
311 | && |
||
312 | ! $this->is_whitelisted_option( $args[0] ) |
||
313 | ) { |
||
314 | return; |
||
315 | } |
||
316 | |||
317 | if ( in_array( $current_filter, array( 'delete_site_option', 'add_site_option', 'update_site_option' ) ) |
||
318 | && |
||
319 | ! $this->is_whitelisted_network_option( $args[0] ) |
||
320 | ) { |
||
321 | return; |
||
322 | } |
||
323 | |||
324 | if ( $current_filter == 'upgrader_process_complete' ) { |
||
325 | array_shift( $args ); |
||
326 | } |
||
327 | |||
328 | // don't sync private meta |
||
329 | if ( preg_match( '/^(added|updated|deleted)_.*_meta$/', $current_filter ) |
||
330 | && $args[2][0] === '_' |
||
331 | && ! in_array( $args[2], Jetpack_Sync_Defaults::$default_whitelist_meta_keys ) |
||
|
|||
332 | ) { |
||
333 | return; |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Modify the data within an action before it is enqueued locally. |
||
338 | * |
||
339 | * @since 4.2.0 |
||
340 | * |
||
341 | * @param array The action parameters |
||
342 | */ |
||
343 | $args = apply_filters( "jetpack_sync_before_enqueue_$current_filter", $args ); |
||
344 | |||
345 | // if we add any items to the queue, we should |
||
346 | // try to ensure that our script can't be killed before |
||
347 | // they are sent |
||
348 | if ( function_exists( 'ignore_user_abort' ) ) { |
||
349 | ignore_user_abort( true ); |
||
350 | } |
||
351 | |||
352 | $this->sync_queue->add( array( |
||
353 | $current_filter, |
||
354 | $args, |
||
355 | get_current_user_id(), |
||
356 | microtime( true ) |
||
357 | ) ); |
||
358 | } |
||
359 | |||
360 | function send_theme_info() { |
||
361 | global $_wp_theme_features; |
||
362 | |||
363 | $theme_support = array(); |
||
364 | |||
365 | foreach ( Jetpack_Sync_Defaults::$default_theme_support_whitelist as $theme_feature ) { |
||
366 | $has_support = current_theme_supports( $theme_feature ); |
||
367 | if ( $has_support ) { |
||
368 | $theme_support[ $theme_feature ] = $_wp_theme_features[ $theme_feature ]; |
||
369 | } |
||
370 | |||
371 | } |
||
372 | |||
373 | /** |
||
374 | * Fires when the client needs to sync theme support info |
||
375 | * Only sends theme support attributes whitelisted in Jetpack_Sync_Defaults::$default_theme_support_whitelist |
||
376 | * |
||
377 | * @since 4.2.0 |
||
378 | * |
||
379 | * @param object the theme support hash |
||
380 | */ |
||
381 | do_action( 'jetpack_sync_current_theme_support', $theme_support ); |
||
382 | return 1; // The number of actions enqueued |
||
383 | } |
||
384 | |||
385 | function send_wp_version( $update, $meta_data ) { |
||
386 | if ( 'update' === $meta_data['action'] && 'core' === $meta_data['type'] ) { |
||
387 | $this->force_sync_callables(); |
||
388 | } |
||
389 | } |
||
390 | |||
391 | function save_term_handler( $term_id, $tt_id, $taxonomy ) { |
||
407 | |||
408 | function send_attachment_info( $attachment_id ) { |
||
421 | |||
422 | function save_user_handler( $user_id, $old_user_data = null ) { |
||
423 | |||
424 | // ensure we only sync users who are members of the current blog |
||
425 | if ( ! is_user_member_of_blog( $user_id, get_current_blog_id() ) ) { |
||
426 | return; |
||
427 | } |
||
428 | |||
429 | $user = $this->sanitize_user( get_user_by( 'id', $user_id ) ); |
||
430 | |||
431 | // Older versions of WP don't pass the old_user_data in ->data |
||
432 | if ( isset( $old_user_data->data ) ) { |
||
433 | $old_user = $old_user_data->data; |
||
434 | } else { |
||
435 | $old_user = $old_user_data; |
||
436 | } |
||
437 | |||
438 | if ( $old_user !== null ) { |
||
439 | unset( $old_user->user_pass ); |
||
440 | if ( serialize( $old_user ) === serialize( $user->data ) ) { |
||
441 | return; |
||
442 | } |
||
443 | } |
||
444 | /** |
||
445 | * Fires when the client needs to sync an updated user |
||
446 | * |
||
447 | * @since 4.2.0 |
||
453 | |||
454 | function save_user_role_handler( $user_id, $role, $old_roles = null ) { |
||
466 | |||
467 | function save_user_cap_handler( $meta_id, $user_id, $meta_key, $capabilities ) { |
||
490 | |||
491 | public function sanitize_user( $user ) { |
||
495 | |||
496 | public function sanitize_user_and_expand( $user ) { |
||
500 | |||
501 | public function add_to_user( $user ) { |
||
505 | |||
506 | public function expand_user( $args ) { |
||
510 | |||
511 | function do_sync() { |
||
630 | |||
631 | function expand_wp_insert_post( $args ) { |
||
634 | |||
635 | function expand_upgrader_process_complete( $args ) { |
||
642 | |||
643 | // Expands wp_insert_post to include filtered content |
||
644 | function filter_post_content_and_add_links( $post ) { |
||
681 | |||
682 | |||
683 | function expand_wp_comment_status_change( $args ) { |
||
686 | |||
687 | function expand_wp_insert_comment( $args ) { |
||
690 | |||
691 | function filter_comment( $comment ) { |
||
716 | |||
717 | private function schedule_sync( $when ) { |
||
720 | |||
721 | function force_sync_constants() { |
||
727 | |||
728 | function full_sync_constants() { |
||
739 | |||
740 | function force_sync_options() { |
||
751 | |||
752 | function force_sync_network_options() { |
||
763 | |||
764 | public function full_sync_callables() { |
||
775 | |||
776 | public function full_sync_updates() { |
||
787 | |||
788 | View Code Duplication | private function maybe_sync_constants() { |
|
821 | // public so that we don't have to store an option for each constant |
||
822 | function get_all_constants() { |
||
828 | |||
829 | private function get_constant( $constant ) { |
||
834 | |||
835 | public function get_all_updates() { |
||
842 | |||
843 | public function force_sync_callables() { |
||
848 | |||
849 | View Code Duplication | private function maybe_sync_callables() { |
|
882 | |||
883 | private function still_valid_checksum( $sums_to_check, $name, $new_sum ) { |
||
889 | |||
890 | public function get_all_callables() { |
||
896 | |||
897 | private function get_callable( $callable ) { |
||
900 | |||
901 | // Is public so that we don't have to store so much data all the options twice. |
||
902 | function get_all_options() { |
||
910 | |||
911 | function get_all_network_options() { |
||
919 | |||
920 | private function get_check_sum( $values ) { |
||
923 | |||
924 | function jetpack_sync_core_icon() { |
||
940 | |||
941 | function get_sync_queue() { |
||
944 | |||
945 | function reset_sync_queue() { |
||
948 | |||
949 | function get_settings() { |
||
957 | |||
958 | function update_settings( $new_settings ) { |
||
964 | |||
965 | function update_options_whitelist() { |
||
969 | |||
970 | function set_defaults() { |
||
996 | |||
997 | function reset_data() { |
||
1016 | |||
1017 | function uninstall() { |
||
1027 | } |
||
1028 |
This check looks for access to properties that are not accessible from the current context.
If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.