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 Users 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 Users, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class Users extends Module { |
||
18 | /** |
||
19 | * Maximum number of users to sync initially. |
||
20 | * |
||
21 | * @var int |
||
22 | */ |
||
23 | const MAX_INITIAL_SYNC_USERS = 100; |
||
24 | |||
25 | /** |
||
26 | * User flags we care about. |
||
27 | * |
||
28 | * @access protected |
||
29 | * |
||
30 | * @var array |
||
31 | */ |
||
32 | protected $flags = array(); |
||
33 | |||
34 | /** |
||
35 | * Sync module name. |
||
36 | * |
||
37 | * @access public |
||
38 | * |
||
39 | * @return string |
||
40 | */ |
||
41 | public function name() { |
||
42 | return 'users'; |
||
43 | } |
||
44 | |||
45 | /** |
||
46 | * The table in the database. |
||
47 | * |
||
48 | * @access public |
||
49 | * |
||
50 | * @return string |
||
51 | */ |
||
52 | public function table_name() { |
||
53 | return 'usermeta'; |
||
54 | } |
||
55 | |||
56 | /** |
||
57 | * The id field in the database. |
||
58 | * |
||
59 | * @access public |
||
60 | * |
||
61 | * @return string |
||
62 | */ |
||
63 | public function id_field() { |
||
64 | return 'user_id'; |
||
65 | } |
||
66 | |||
67 | /** |
||
68 | * Retrieve a user by its ID. |
||
69 | * This is here to support the backfill API. |
||
70 | * |
||
71 | * @access public |
||
72 | * |
||
73 | * @param string $object_type Type of the sync object. |
||
74 | * @param int $id ID of the sync object. |
||
75 | * @return \WP_User|bool Filtered \WP_User object, or false if the object is not a user. |
||
76 | */ |
||
77 | public function get_object_by_id( $object_type, $id ) { |
||
78 | if ( 'user' === $object_type ) { |
||
79 | $user = get_user_by( 'id', (int) $id ); |
||
80 | if ( $user ) { |
||
81 | return $this->sanitize_user_and_expand( $user ); |
||
82 | } |
||
83 | } |
||
84 | |||
85 | return false; |
||
86 | } |
||
87 | |||
88 | /** |
||
89 | * Initialize users action listeners. |
||
90 | * |
||
91 | * @access public |
||
92 | * |
||
93 | * @param callable $callable Action handler callable. |
||
94 | */ |
||
95 | public function init_listeners( $callable ) { |
||
96 | // Users. |
||
97 | add_action( 'user_register', array( $this, 'user_register_handler' ) ); |
||
98 | add_action( 'profile_update', array( $this, 'save_user_handler' ), 10, 2 ); |
||
99 | |||
100 | add_action( 'add_user_to_blog', array( $this, 'add_user_to_blog_handler' ) ); |
||
101 | add_action( 'jetpack_sync_add_user', $callable, 10, 2 ); |
||
102 | |||
103 | add_action( 'jetpack_sync_register_user', $callable, 10, 2 ); |
||
104 | add_action( 'jetpack_sync_save_user', $callable, 10, 2 ); |
||
105 | |||
106 | add_action( 'jetpack_sync_user_locale', $callable, 10, 2 ); |
||
107 | add_action( 'jetpack_sync_user_locale_delete', $callable, 10, 1 ); |
||
108 | |||
109 | add_action( 'deleted_user', array( $this, 'deleted_user_handler' ), 10, 2 ); |
||
110 | add_action( 'jetpack_deleted_user', $callable, 10, 3 ); |
||
111 | add_action( 'remove_user_from_blog', array( $this, 'remove_user_from_blog_handler' ), 10, 2 ); |
||
112 | add_action( 'jetpack_removed_user_from_blog', $callable, 10, 2 ); |
||
113 | |||
114 | // User roles. |
||
115 | add_action( 'add_user_role', array( $this, 'save_user_role_handler' ), 10, 2 ); |
||
116 | add_action( 'set_user_role', array( $this, 'save_user_role_handler' ), 10, 3 ); |
||
117 | add_action( 'remove_user_role', array( $this, 'save_user_role_handler' ), 10, 2 ); |
||
118 | |||
119 | // User capabilities. |
||
120 | add_action( 'added_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 ); |
||
121 | add_action( 'updated_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 ); |
||
122 | add_action( 'deleted_user_meta', array( $this, 'maybe_save_user_meta' ), 10, 4 ); |
||
123 | |||
124 | // User authentication. |
||
125 | add_filter( 'authenticate', array( $this, 'authenticate_handler' ), 1000, 3 ); |
||
126 | add_action( 'wp_login', array( $this, 'wp_login_handler' ), 10, 2 ); |
||
127 | |||
128 | add_action( 'jetpack_wp_login', $callable, 10, 3 ); |
||
129 | |||
130 | add_action( 'wp_logout', $callable, 10, 0 ); |
||
131 | add_action( 'wp_masterbar_logout', $callable, 10, 1 ); |
||
132 | |||
133 | // Add on init. |
||
134 | add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_add_user', array( $this, 'expand_action' ) ); |
||
135 | add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_register_user', array( $this, 'expand_action' ) ); |
||
136 | add_filter( 'jetpack_sync_before_enqueue_jetpack_sync_save_user', array( $this, 'expand_action' ) ); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Initialize users action listeners for full sync. |
||
141 | * |
||
142 | * @access public |
||
143 | * |
||
144 | * @param callable $callable Action handler callable. |
||
145 | */ |
||
146 | public function init_full_sync_listeners( $callable ) { |
||
147 | add_action( 'jetpack_full_sync_users', $callable ); |
||
148 | } |
||
149 | |||
150 | /** |
||
151 | * Initialize the module in the sender. |
||
152 | * |
||
153 | * @access public |
||
154 | */ |
||
155 | public function init_before_send() { |
||
156 | add_filter( 'jetpack_sync_before_send_jetpack_wp_login', array( $this, 'expand_login_username' ), 10, 1 ); |
||
157 | add_filter( 'jetpack_sync_before_send_wp_logout', array( $this, 'expand_logout_username' ), 10, 2 ); |
||
158 | |||
159 | // Full sync. |
||
160 | add_filter( 'jetpack_sync_before_send_jetpack_full_sync_users', array( $this, 'expand_users' ) ); |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * Retrieve a user by a user ID or object. |
||
165 | * |
||
166 | * @access private |
||
167 | * |
||
168 | * @param mixed $user User object or ID. |
||
169 | * @return \WP_User User object, or `null` if user invalid/not found. |
||
170 | */ |
||
171 | private function get_user( $user ) { |
||
172 | if ( is_numeric( $user ) ) { |
||
173 | $user = get_user_by( 'id', $user ); |
||
174 | } |
||
175 | if ( $user instanceof \WP_User ) { |
||
|
|||
176 | return $user; |
||
177 | } |
||
178 | return null; |
||
179 | } |
||
180 | |||
181 | /** |
||
182 | * Sanitize a user object. |
||
183 | * Removes the password from the user object because we don't want to sync it. |
||
184 | * |
||
185 | * @access public |
||
186 | * |
||
187 | * @todo Refactor `serialize`/`unserialize` to `wp_json_encode`/`wp_json_decode`. |
||
188 | * |
||
189 | * @param \WP_User $user User object. |
||
190 | * @return \WP_User Sanitized user object. |
||
191 | */ |
||
192 | public function sanitize_user( $user ) { |
||
193 | $user = $this->get_user( $user ); |
||
194 | // This creates a new user object and stops the passing of the object by reference. |
||
195 | // // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize |
||
196 | $user = unserialize( serialize( $user ) ); |
||
197 | |||
198 | if ( is_object( $user ) && is_object( $user->data ) ) { |
||
199 | unset( $user->data->user_pass ); |
||
200 | } |
||
201 | return $user; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Expand a particular user. |
||
206 | * |
||
207 | * @access public |
||
208 | * |
||
209 | * @param \WP_User $user User object. |
||
210 | * @return \WP_User Expanded user object. |
||
211 | */ |
||
212 | public function expand_user( $user ) { |
||
213 | if ( ! is_object( $user ) ) { |
||
214 | return null; |
||
215 | } |
||
216 | $user->allowed_mime_types = get_allowed_mime_types( $user ); |
||
217 | $user->allcaps = $this->get_real_user_capabilities( $user ); |
||
218 | |||
219 | // Only set the user locale if it is different from the site locale. |
||
220 | if ( get_locale() !== get_user_locale( $user->ID ) ) { |
||
221 | $user->locale = get_user_locale( $user->ID ); |
||
222 | } |
||
223 | |||
224 | return $user; |
||
225 | } |
||
226 | |||
227 | /** |
||
228 | * Retrieve capabilities we care about for a particular user. |
||
229 | * |
||
230 | * @access public |
||
231 | * |
||
232 | * @param \WP_User $user User object. |
||
233 | * @return array User capabilities. |
||
234 | */ |
||
235 | public function get_real_user_capabilities( $user ) { |
||
236 | $user_capabilities = array(); |
||
237 | if ( is_wp_error( $user ) ) { |
||
238 | return $user_capabilities; |
||
239 | } |
||
240 | foreach ( Defaults::get_capabilities_whitelist() as $capability ) { |
||
241 | if ( user_can( $user, $capability ) ) { |
||
242 | $user_capabilities[ $capability ] = true; |
||
243 | } |
||
244 | } |
||
245 | return $user_capabilities; |
||
246 | } |
||
247 | |||
248 | /** |
||
249 | * Retrieve, expand and sanitize a user. |
||
250 | * Can be directly used in the sync user action handlers. |
||
251 | * |
||
252 | * @access public |
||
253 | * |
||
254 | * @param mixed $user User ID or user object. |
||
255 | * @return \WP_User Expanded and sanitized user object. |
||
256 | */ |
||
257 | public function sanitize_user_and_expand( $user ) { |
||
258 | $user = $this->get_user( $user ); |
||
259 | $user = $this->expand_user( $user ); |
||
260 | return $this->sanitize_user( $user ); |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Expand the user within a hook before it is serialized and sent to the server. |
||
265 | * |
||
266 | * @access public |
||
267 | * |
||
268 | * @param array $args The hook arguments. |
||
269 | * @return array $args The hook arguments. |
||
270 | */ |
||
271 | public function expand_action( $args ) { |
||
272 | // The first argument is always the user. |
||
273 | list( $user ) = $args; |
||
274 | if ( $user ) { |
||
275 | $args[0] = $this->sanitize_user_and_expand( $user ); |
||
276 | return $args; |
||
277 | } |
||
278 | |||
279 | return false; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Expand the user username at login before being sent to the server. |
||
284 | * |
||
285 | * @access public |
||
286 | * |
||
287 | * @param array $args The hook arguments. |
||
288 | * @return array $args Expanded hook arguments. |
||
289 | */ |
||
290 | public function expand_login_username( $args ) { |
||
291 | list( $login, $user, $flags ) = $args; |
||
292 | $user = $this->sanitize_user( $user ); |
||
293 | |||
294 | return array( $login, $user, $flags ); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * Expand the user username at logout before being sent to the server. |
||
299 | * |
||
300 | * @access public |
||
301 | * |
||
302 | * @param array $args The hook arguments. |
||
303 | * @param int $user_id ID of the user. |
||
304 | * @return array $args Expanded hook arguments. |
||
305 | */ |
||
306 | public function expand_logout_username( $args, $user_id ) { |
||
307 | $user = get_userdata( $user_id ); |
||
308 | $user = $this->sanitize_user( $user ); |
||
309 | |||
310 | $login = ''; |
||
311 | if ( is_object( $user ) && is_object( $user->data ) ) { |
||
312 | $login = $user->data->user_login; |
||
313 | } |
||
314 | |||
315 | // If we don't have a user here lets not send anything. |
||
316 | if ( empty( $login ) ) { |
||
317 | return false; |
||
318 | } |
||
319 | |||
320 | return array( $login, $user ); |
||
321 | } |
||
322 | |||
323 | /** |
||
324 | * Additional processing is needed for wp_login so we introduce this wrapper handler. |
||
325 | * |
||
326 | * @access public |
||
327 | * |
||
328 | * @param string $user_login The user login. |
||
329 | * @param \WP_User $user The user object. |
||
330 | */ |
||
331 | public function wp_login_handler( $user_login, $user ) { |
||
332 | /** |
||
333 | * Fires when a user is logged into a site. |
||
334 | * |
||
335 | * @since 7.2.0 |
||
336 | * |
||
337 | * @param int $user_id The user ID. |
||
338 | * @param \WP_User $user The User Object of the user that currently logged in. |
||
339 | * @param array $params Any Flags that have been added during login. |
||
340 | */ |
||
341 | do_action( 'jetpack_wp_login', $user->ID, $user, $this->get_flags( $user->ID ) ); |
||
342 | $this->clear_flags( $user->ID ); |
||
343 | } |
||
344 | |||
345 | /** |
||
346 | * A hook for the authenticate event that checks the password strength. |
||
347 | * |
||
348 | * @access public |
||
349 | * |
||
350 | * @param \WP_Error|\WP_User $user The user object, or an error. |
||
351 | * @param string $username The username. |
||
352 | * @param string $password The password used to authenticate. |
||
353 | * @return \WP_Error|\WP_User the same object that was passed into the function. |
||
354 | */ |
||
355 | public function authenticate_handler( $user, $username, $password ) { |
||
356 | // In case of cookie authentication we don't do anything here. |
||
357 | if ( empty( $password ) ) { |
||
358 | return $user; |
||
359 | } |
||
360 | |||
361 | // We are only interested in successful authentication events. |
||
362 | if ( is_wp_error( $user ) || ! ( $user instanceof \WP_User ) ) { |
||
363 | return $user; |
||
364 | } |
||
365 | |||
366 | $password_checker = new Password_Checker( $user->ID ); |
||
367 | |||
368 | $test_results = $password_checker->test( $password, true ); |
||
369 | |||
370 | // If the password passes tests, we don't do anything. |
||
371 | if ( empty( $test_results['test_results']['failed'] ) ) { |
||
372 | return $user; |
||
373 | } |
||
374 | |||
375 | $this->add_flags( |
||
376 | $user->ID, |
||
377 | array( |
||
378 | 'warning' => 'The password failed at least one strength test.', |
||
379 | 'failures' => $test_results['test_results']['failed'], |
||
380 | ) |
||
381 | ); |
||
382 | |||
383 | return $user; |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * Handler for after the user is deleted. |
||
388 | * |
||
389 | * @access public |
||
390 | * |
||
391 | * @param int $deleted_user_id ID of the deleted user. |
||
392 | * @param int $reassigned_user_id ID of the user the deleted user's posts are reassigned to (if any). |
||
393 | */ |
||
394 | public function deleted_user_handler( $deleted_user_id, $reassigned_user_id = '' ) { |
||
407 | |||
408 | /** |
||
409 | * Handler for user registration. |
||
410 | * |
||
411 | * @access public |
||
412 | * |
||
413 | * @param int $user_id ID of the deleted user. |
||
414 | */ |
||
415 | View Code Duplication | public function user_register_handler( $user_id ) { |
|
435 | |||
436 | /** |
||
437 | * Handler for user addition to the current blog. |
||
438 | * |
||
439 | * @access public |
||
440 | * |
||
441 | * @param int $user_id ID of the user. |
||
442 | */ |
||
443 | View Code Duplication | public function add_user_to_blog_handler( $user_id ) { |
|
463 | |||
464 | /** |
||
465 | * Handler for user save. |
||
466 | * |
||
467 | * @access public |
||
468 | * |
||
469 | * @param int $user_id ID of the user. |
||
470 | * @param \WP_User $old_user_data User object before the changes. |
||
471 | */ |
||
472 | public function save_user_handler( $user_id, $old_user_data = null ) { |
||
512 | |||
513 | /** |
||
514 | * Handler for user role change. |
||
515 | * |
||
516 | * @access public |
||
517 | * |
||
518 | * @param int $user_id ID of the user. |
||
519 | * @param string $role New user role. |
||
520 | * @param array $old_roles Previous user roles. |
||
521 | */ |
||
522 | public function save_user_role_handler( $user_id, $role, $old_roles = null ) { |
||
541 | |||
542 | /** |
||
543 | * Retrieve current flags for a particular user. |
||
544 | * |
||
545 | * @access public |
||
546 | * |
||
547 | * @param int $user_id ID of the user. |
||
548 | * @return array Current flags of the user. |
||
549 | */ |
||
550 | public function get_flags( $user_id ) { |
||
556 | |||
557 | /** |
||
558 | * Clear the flags of a particular user. |
||
559 | * |
||
560 | * @access public |
||
561 | * |
||
562 | * @param int $user_id ID of the user. |
||
563 | */ |
||
564 | public function clear_flags( $user_id ) { |
||
569 | |||
570 | /** |
||
571 | * Add flags to a particular user. |
||
572 | * |
||
573 | * @access public |
||
574 | * |
||
575 | * @param int $user_id ID of the user. |
||
576 | * @param array $flags New flags to add for the user. |
||
577 | */ |
||
578 | public function add_flags( $user_id, $flags ) { |
||
581 | |||
582 | /** |
||
583 | * Save the user meta, if we're interested in it. |
||
584 | * Also uses the time to add flags for the user. |
||
585 | * |
||
586 | * @access public |
||
587 | * |
||
588 | * @param int $meta_id ID of the meta object. |
||
589 | * @param int $user_id ID of the user. |
||
590 | * @param string $meta_key Meta key. |
||
591 | * @param mixed $value Meta value. |
||
592 | */ |
||
593 | public function maybe_save_user_meta( $meta_id, $user_id, $meta_key, $value ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
||
614 | |||
615 | /** |
||
616 | * Enqueue the users actions for full sync. |
||
617 | * |
||
618 | * @access public |
||
619 | * |
||
620 | * @param array $config Full sync configuration for this sync module. |
||
621 | * @param int $max_items_to_enqueue Maximum number of items to enqueue. |
||
622 | * @param boolean $state True if full sync has finished enqueueing this module, false otherwise. |
||
623 | * @return array Number of actions enqueued, and next module state. |
||
624 | */ |
||
625 | public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) { |
||
630 | |||
631 | /** |
||
632 | * Retrieve an estimated number of actions that will be enqueued. |
||
633 | * |
||
634 | * @access public |
||
635 | * |
||
636 | * @todo Refactor to prepare the SQL query before executing it. |
||
637 | * |
||
638 | * @param array $config Full sync configuration for this sync module. |
||
639 | * @return array Number of items yet to be enqueued. |
||
640 | */ |
||
641 | View Code Duplication | public function estimate_full_sync_actions( $config ) { |
|
656 | |||
657 | /** |
||
658 | * Retrieve the WHERE SQL clause based on the module config. |
||
659 | * |
||
660 | * @access public |
||
661 | * |
||
662 | * @param array $config Full sync configuration for this sync module. |
||
663 | * @return string WHERE SQL clause, or `null` if no comments are specified in the module config. |
||
664 | */ |
||
665 | View Code Duplication | public function get_where_sql( $config ) { |
|
677 | |||
678 | /** |
||
679 | * Retrieve the actions that will be sent for this module during a full sync. |
||
680 | * |
||
681 | * @access public |
||
682 | * |
||
683 | * @return array Full sync actions of this module. |
||
684 | */ |
||
685 | public function get_full_sync_actions() { |
||
688 | |||
689 | /** |
||
690 | * Retrieve initial sync user config. |
||
691 | * |
||
692 | * @access public |
||
693 | * |
||
694 | * @todo Refactor the SQL query to call $wpdb->prepare() before execution. |
||
695 | * |
||
696 | * @return array|boolean IDs of users to initially sync, or false if tbe number of users exceed the maximum. |
||
697 | */ |
||
698 | public function get_initial_sync_user_config() { |
||
710 | |||
711 | /** |
||
712 | * Expand the users within a hook before they are serialized and sent to the server. |
||
713 | * |
||
714 | * @access public |
||
715 | * |
||
716 | * @param array $args The hook arguments. |
||
717 | * @return array $args The hook arguments. |
||
718 | */ |
||
719 | public function expand_users( $args ) { |
||
736 | |||
737 | /** |
||
738 | * Handler for user removal from a particular blog. |
||
739 | * |
||
740 | * @access public |
||
741 | * |
||
742 | * @param int $user_id ID of the user. |
||
743 | * @param int $blog_id ID of the blog. |
||
744 | */ |
||
745 | public function remove_user_from_blog_handler( $user_id, $blog_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable |
||
764 | |||
765 | /** |
||
766 | * Whether we're adding a new user to a blog in this request. |
||
767 | * |
||
768 | * @access protected |
||
769 | * |
||
770 | * @return boolean |
||
771 | */ |
||
772 | protected function is_add_new_user_to_blog() { |
||
775 | |||
776 | /** |
||
777 | * Whether we're adding an existing user to a blog in this request. |
||
778 | * |
||
779 | * @access protected |
||
780 | * |
||
781 | * @return boolean |
||
782 | */ |
||
783 | protected function is_add_user_to_blog() { |
||
786 | |||
787 | /** |
||
788 | * Whether we're removing a user from a blog in this request. |
||
789 | * |
||
790 | * @access protected |
||
791 | * |
||
792 | * @return boolean |
||
793 | */ |
||
794 | protected function is_delete_user() { |
||
797 | |||
798 | /** |
||
799 | * Whether we're creating a user or adding a new user to a blog. |
||
800 | * |
||
801 | * @access protected |
||
802 | * |
||
803 | * @return boolean |
||
804 | */ |
||
805 | protected function is_create_user() { |
||
814 | |||
815 | /** |
||
816 | * Retrieve the ID of the user the removed user's posts are reassigned to (if any). |
||
817 | * |
||
818 | * @return int ID of the user that got reassigned as the author of the posts. |
||
819 | */ |
||
820 | protected function get_reassigned_network_user_id() { |
||
833 | |||
834 | /** |
||
835 | * Checks if one or more function names is in debug_backtrace. |
||
836 | * |
||
837 | * @access protected |
||
838 | * |
||
839 | * @param array|string $names Mixed string name of function or array of string names of functions. |
||
840 | * @return bool |
||
841 | */ |
||
842 | protected function is_function_in_backtrace( $names ) { |
||
865 | } |
||
866 |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.json
file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.json
to be in the root folder of your repository.Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the
require
orrequire-dev
section?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceof
checks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.