Completed
Push — cfinke-contact-form-recheck-re... ( 5e3f17...ff441a )
by Jeremy
112:09 queued 105:13
created

Comments   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 474
Duplicated Lines 3.38 %

Coupling/Cohesion

Components 3
Dependencies 3

Importance

Changes 0
Metric Value
wmc 46
lcom 3
cbo 3
dl 16
loc 474
rs 8.72
c 0
b 0
f 0

24 Methods

Rating   Name   Duplication   Size   Complexity  
A name() 0 3 1
A id_field() 0 3 1
A table_name() 0 3 1
A get_object_by_id() 0 11 3
A init_listeners() 0 46 3
A handle_comment_contents_modification() 0 28 4
A init_full_sync_listeners() 0 3 1
A get_whitelisted_comment_types() 0 13 1
A filter_blacklisted_post_types() 0 10 3
A only_allow_white_listed_comment_type_transitions() 0 9 2
A only_allow_white_listed_comment_types() 0 17 5
A is_comment_type_allowed() 0 8 2
A init_before_send() 0 19 3
A enqueue_full_sync_actions() 0 4 1
A estimate_full_sync_actions() 16 16 2
A get_where_sql() 0 7 2
A get_full_sync_actions() 0 3 1
A count_full_sync_actions() 0 3 1
A expand_wp_comment_status_change() 0 3 1
A expand_wp_insert_comment() 0 3 1
A filter_comment() 0 26 2
A is_whitelisted_comment_meta() 0 3 1
A filter_meta() 0 7 3
A expand_comment_ids() 0 17 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 Comments 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 Comments, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Comments sync module.
4
 *
5
 * @package automattic/jetpack-sync
6
 */
7
8
namespace Automattic\Jetpack\Sync\Modules;
9
10
use Automattic\Jetpack\Sync\Settings;
11
use Automattic\Jetpack\Sync\Modules;
12
13
/**
14
 * Class to handle sync for comments.
15
 */
16
class Comments extends Module {
17
	/**
18
	 * Sync module name.
19
	 *
20
	 * @access public
21
	 *
22
	 * @return string
23
	 */
24
	public function name() {
25
		return 'comments';
26
	}
27
28
	/**
29
	 * The id field in the database.
30
	 *
31
	 * @access public
32
	 *
33
	 * @return string
34
	 */
35
	public function id_field() {
36
		return 'comment_ID';
37
	}
38
39
	/**
40
	 * The table in the database.
41
	 *
42
	 * @access public
43
	 *
44
	 * @return string
45
	 */
46
	public function table_name() {
47
		return 'comments';
48
	}
49
50
	/**
51
	 * Retrieve a comment by its ID.
52
	 *
53
	 * @access public
54
	 *
55
	 * @param string $object_type Type of the sync object.
56
	 * @param int    $id          ID of the sync object.
57
	 * @return \WP_Comment|bool Filtered \WP_Comment object, or false if the object is not a comment.
58
	 */
59
	public function get_object_by_id( $object_type, $id ) {
60
		$comment_id = intval( $id );
61
		if ( 'comment' === $object_type ) {
62
			$comment = get_comment( $comment_id );
63
			if ( $comment ) {
64
				return $this->filter_comment( $comment );
65
			}
66
		}
67
68
		return false;
69
	}
70
71
	/**
72
	 * Initialize comments action listeners.
73
	 * Also responsible for initializing comment meta listeners.
74
	 *
75
	 * @access public
76
	 *
77
	 * @param callable $callable Action handler callable.
78
	 */
79
	public function init_listeners( $callable ) {
80
		add_action( 'wp_insert_comment', $callable, 10, 2 );
81
		add_action( 'deleted_comment', $callable );
82
		add_action( 'trashed_comment', $callable );
83
		add_action( 'spammed_comment', $callable );
84
		add_action( 'trashed_post_comments', $callable, 10, 2 );
85
		add_action( 'untrash_post_comments', $callable );
86
		add_action( 'comment_approved_to_unapproved', $callable );
87
		add_action( 'comment_unapproved_to_approved', $callable );
88
		add_action( 'jetpack_modified_comment_contents', $callable, 10, 2 );
89
		add_action( 'untrashed_comment', $callable, 10, 2 );
90
		add_action( 'unspammed_comment', $callable, 10, 2 );
91
		add_filter( 'wp_update_comment_data', array( $this, 'handle_comment_contents_modification' ), 10, 3 );
92
93
		// comment actions.
94
		add_filter( 'jetpack_sync_before_enqueue_wp_insert_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
95
		add_filter( 'jetpack_sync_before_enqueue_deleted_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
96
		add_filter( 'jetpack_sync_before_enqueue_trashed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
97
		add_filter( 'jetpack_sync_before_enqueue_untrashed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
98
		add_filter( 'jetpack_sync_before_enqueue_spammed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
99
		add_filter( 'jetpack_sync_before_enqueue_unspammed_comment', array( $this, 'only_allow_white_listed_comment_types' ) );
100
101
		// comment status transitions.
102
		add_filter( 'jetpack_sync_before_enqueue_comment_approved_to_unapproved', array( $this, 'only_allow_white_listed_comment_type_transitions' ) );
103
		add_filter( 'jetpack_sync_before_enqueue_comment_unapproved_to_approved', array( $this, 'only_allow_white_listed_comment_type_transitions' ) );
104
105
		// Post Actions.
106
		add_filter( 'jetpack_sync_before_enqueue_trashed_post_comments', array( $this, 'filter_blacklisted_post_types' ) );
107
		add_filter( 'jetpack_sync_before_enqueue_untrash_post_comments', array( $this, 'filter_blacklisted_post_types' ) );
108
109
		/**
110
		 * Even though it's messy, we implement these hooks because
111
		 * the edit_comment hook doesn't include the data
112
		 * so this saves us a DB read for every comment event.
113
		 */
114
		foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
115
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
116
				$comment_action_name = "comment_{$comment_status}_{$comment_type}";
117
				add_action( $comment_action_name, $callable, 10, 2 );
118
			}
119
		}
120
121
		// Listen for meta changes.
122
		$this->init_listeners_for_meta_type( 'comment', $callable );
123
		$this->init_meta_whitelist_handler( 'comment', array( $this, 'filter_meta' ) );
124
	}
125
126
	/**
127
	 * Handler for any comment content updates.
128
	 *
129
	 * @access public
130
	 *
131
	 * @param array $new_comment              The new, processed comment data.
132
	 * @param array $old_comment              The old, unslashed comment data.
133
	 * @param array $new_comment_with_slashes The new, raw comment data.
134
	 * @return array The new, processed comment data.
135
	 */
136
	public function handle_comment_contents_modification( $new_comment, $old_comment, $new_comment_with_slashes ) {
137
		$changes        = array();
138
		$content_fields = array(
139
			'comment_author',
140
			'comment_author_email',
141
			'comment_author_url',
142
			'comment_content',
143
		);
144
		foreach ( $content_fields as $field ) {
145
			if ( $new_comment_with_slashes[ $field ] !== $old_comment[ $field ] ) {
146
				$changes[ $field ] = array( $new_comment[ $field ], $old_comment[ $field ] );
147
			}
148
		}
149
150
		if ( ! empty( $changes ) ) {
151
			/**
152
			 * Signals to the sync listener that this comment's contents were modified and a sync action
153
			 * reflecting the change(s) to the content should be sent
154
			 *
155
			 * @since 4.9.0
156
			 *
157
			 * @param int $new_comment['comment_ID'] ID of comment whose content was modified
158
			 * @param mixed $changes Array of changed comment fields with before and after values
159
			 */
160
			do_action( 'jetpack_modified_comment_contents', $new_comment['comment_ID'], $changes );
161
		}
162
		return $new_comment;
163
	}
164
165
	/**
166
	 * Initialize comments action listeners for full sync.
167
	 *
168
	 * @access public
169
	 *
170
	 * @param callable $callable Action handler callable.
171
	 */
172
	public function init_full_sync_listeners( $callable ) {
173
		add_action( 'jetpack_full_sync_comments', $callable ); // Also send comments meta.
174
	}
175
176
	/**
177
	 * Gets a filtered list of comment types that sync can hook into.
178
	 *
179
	 * @access public
180
	 *
181
	 * @return array Defaults to [ '', 'trackback', 'pingback' ].
182
	 */
183
	public function get_whitelisted_comment_types() {
184
		/**
185
		 * Comment types present in this list will sync their status changes to WordPress.com.
186
		 *
187
		 * @since 7.6.0
188
		 *
189
		 * @param array A list of comment types.
190
		 */
191
		return apply_filters(
192
			'jetpack_sync_whitelisted_comment_types',
193
			array( '', 'comment', 'trackback', 'pingback' )
194
		);
195
	}
196
197
	/**
198
	 * Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
199
	 *
200
	 * @param array $args Arguments passed to wp_insert_comment, deleted_comment, spammed_comment, etc.
201
	 *
202
	 * @return bool or array $args Arguments passed to wp_insert_comment, deleted_comment, spammed_comment, etc.
203
	 */
204
	public function only_allow_white_listed_comment_types( $args ) {
205
		$comment = false;
206
207
		if ( isset( $args[1] ) ) {
208
			// comment object is available.
209
			$comment = $args[1];
210
		} elseif ( is_numeric( $args[0] ) ) {
211
			// comment_id is available.
212
			$comment = get_comment( $args[0] );
213
		}
214
215
		if ( false !== $comment && ! in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true ) ) {
216
			return false;
217
		}
218
219
		return $args;
220
	}
221
222
	/**
223
	 * Filter all blacklisted post types.
224
	 *
225
	 * @param array $args Hook arguments.
226
	 * @return array|false Hook arguments, or false if the post type is a blacklisted one.
227
	 */
228
	public function filter_blacklisted_post_types( $args ) {
229
		$post_id      = $args[0];
230
		$posts_module = Modules::get_module( 'posts' );
231
232
		if ( false !== $posts_module && ! $posts_module->is_post_type_allowed( $post_id ) ) {
233
			return false;
234
		}
235
236
		return $args;
237
	}
238
239
	/**
240
	 * Prevents any comment types that are not in the whitelist from being enqueued and sent to WordPress.com.
241
	 *
242
	 * @param array $args Arguments passed to wp_{old_status}_to_{new_status}.
243
	 *
244
	 * @return bool or array $args Arguments passed to wp_{old_status}_to_{new_status}
245
	 */
246
	public function only_allow_white_listed_comment_type_transitions( $args ) {
247
		$comment = $args[0];
248
249
		if ( ! in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true ) ) {
250
			return false;
251
		}
252
253
		return $args;
254
	}
255
256
	/**
257
	 * Whether a comment type is allowed.
258
	 * A comment type is allowed if it's present in the comment type whitelist.
259
	 *
260
	 * @param int $comment_id ID of the comment.
261
	 * @return boolean Whether the comment type is allowed.
262
	 */
263
	public function is_comment_type_allowed( $comment_id ) {
264
		$comment = get_comment( $comment_id );
265
266
		if ( isset( $comment->comment_type ) ) {
267
			return in_array( $comment->comment_type, $this->get_whitelisted_comment_types(), true );
268
		}
269
		return false;
270
	}
271
272
	/**
273
	 * Initialize the module in the sender.
274
	 *
275
	 * @access public
276
	 */
277
	public function init_before_send() {
278
		add_filter( 'jetpack_sync_before_send_wp_insert_comment', array( $this, 'expand_wp_insert_comment' ) );
279
280
		foreach ( $this->get_whitelisted_comment_types() as $comment_type ) {
281
			foreach ( array( 'unapproved', 'approved' ) as $comment_status ) {
282
				$comment_action_name = "comment_{$comment_status}_{$comment_type}";
283
				add_filter(
284
					'jetpack_sync_before_send_' . $comment_action_name,
285
					array(
286
						$this,
287
						'expand_wp_insert_comment',
288
					)
289
				);
290
			}
291
		}
292
293
		// Full sync.
294
		add_filter( 'jetpack_sync_before_send_jetpack_full_sync_comments', array( $this, 'expand_comment_ids' ) );
295
	}
296
297
	/**
298
	 * Enqueue the comments actions for full sync.
299
	 *
300
	 * @access public
301
	 *
302
	 * @param array   $config               Full sync configuration for this sync module.
303
	 * @param int     $max_items_to_enqueue Maximum number of items to enqueue.
304
	 * @param boolean $state                True if full sync has finished enqueueing this module, false otherwise.
305
	 * @return array Number of actions enqueued, and next module state.
306
	 */
307
	public function enqueue_full_sync_actions( $config, $max_items_to_enqueue, $state ) {
308
		global $wpdb;
309
		return $this->enqueue_all_ids_as_action( 'jetpack_full_sync_comments', $wpdb->comments, 'comment_ID', $this->get_where_sql( $config ), $max_items_to_enqueue, $state );
310
	}
311
312
	/**
313
	 * Retrieve an estimated number of actions that will be enqueued.
314
	 *
315
	 * @access public
316
	 *
317
	 * @param array $config Full sync configuration for this sync module.
318
	 * @return int Number of items yet to be enqueued.
319
	 */
320 View Code Duplication
	public function estimate_full_sync_actions( $config ) {
321
		global $wpdb;
322
323
		$query = "SELECT count(*) FROM $wpdb->comments";
324
325
		$where_sql = $this->get_where_sql( $config );
326
		if ( $where_sql ) {
327
			$query .= ' WHERE ' . $where_sql;
328
		}
329
330
		// TODO: Call $wpdb->prepare on the following query.
331
		// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
332
		$count = $wpdb->get_var( $query );
333
334
		return (int) ceil( $count / self::ARRAY_CHUNK_SIZE );
335
	}
336
337
	/**
338
	 * Retrieve the WHERE SQL clause based on the module config.
339
	 *
340
	 * @access public
341
	 *
342
	 * @param array $config Full sync configuration for this sync module.
343
	 * @return string WHERE SQL clause, or `null` if no comments are specified in the module config.
344
	 */
345
	public function get_where_sql( $config ) {
346
		if ( is_array( $config ) ) {
347
			return 'comment_ID IN (' . implode( ',', array_map( 'intval', $config ) ) . ')';
348
		}
349
350
		return '1=1';
351
	}
352
353
	/**
354
	 * Retrieve the actions that will be sent for this module during a full sync.
355
	 *
356
	 * @access public
357
	 *
358
	 * @return array Full sync actions of this module.
359
	 */
360
	public function get_full_sync_actions() {
361
		return array( 'jetpack_full_sync_comments' );
362
	}
363
364
	/**
365
	 * Count all the actions that are going to be sent.
366
	 *
367
	 * @access public
368
	 *
369
	 * @param array $action_names Names of all the actions that will be sent.
370
	 * @return int Number of actions.
371
	 */
372
	public function count_full_sync_actions( $action_names ) {
373
		return $this->count_actions( $action_names, array( 'jetpack_full_sync_comments' ) );
374
	}
375
376
	/**
377
	 * Expand the comment status change before the data is serialized and sent to the server.
378
	 *
379
	 * @access public
380
	 * @todo This is not used currently - let's implement it.
381
	 *
382
	 * @param array $args The hook parameters.
383
	 * @return array The expanded hook parameters.
384
	 */
385
	public function expand_wp_comment_status_change( $args ) {
386
		return array( $args[0], $this->filter_comment( $args[1] ) );
387
	}
388
389
	/**
390
	 * Expand the comment creation before the data is serialized and sent to the server.
391
	 *
392
	 * @access public
393
	 *
394
	 * @param array $args The hook parameters.
395
	 * @return array The expanded hook parameters.
396
	 */
397
	public function expand_wp_insert_comment( $args ) {
398
		return array( $args[0], $this->filter_comment( $args[1] ) );
399
	}
400
401
	/**
402
	 * Filter a comment object to the fields we need.
403
	 *
404
	 * @access public
405
	 *
406
	 * @param \WP_Comment $comment The unfiltered comment object.
407
	 * @return \WP_Comment Filtered comment object.
408
	 */
409
	public function filter_comment( $comment ) {
410
		/**
411
		 * Filters whether to prevent sending comment data to .com
412
		 *
413
		 * Passing true to the filter will prevent the comment data from being sent
414
		 * to the WordPress.com.
415
		 * Instead we pass data that will still enable us to do a checksum against the
416
		 * Jetpacks data but will prevent us from displaying the data on in the API as well as
417
		 * other services.
418
		 *
419
		 * @since 4.2.0
420
		 *
421
		 * @param boolean false prevent post data from bing synced to WordPress.com
422
		 * @param mixed $comment WP_COMMENT object
423
		 */
424
		if ( apply_filters( 'jetpack_sync_prevent_sending_comment_data', false, $comment ) ) {
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $comment.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
425
			$blocked_comment                   = new \stdClass();
426
			$blocked_comment->comment_ID       = $comment->comment_ID;
427
			$blocked_comment->comment_date     = $comment->comment_date;
428
			$blocked_comment->comment_date_gmt = $comment->comment_date_gmt;
429
			$blocked_comment->comment_approved = 'jetpack_sync_blocked';
430
			return $blocked_comment;
431
		}
432
433
		return $comment;
434
	}
435
436
	/**
437
	 * Whether a certain comment meta key is whitelisted for sync.
438
	 *
439
	 * @access public
440
	 *
441
	 * @param string $meta_key Comment meta key.
442
	 * @return boolean Whether the meta key is whitelisted.
443
	 */
444
	public function is_whitelisted_comment_meta( $meta_key ) {
445
		return in_array( $meta_key, Settings::get_setting( 'comment_meta_whitelist' ), true );
446
	}
447
448
	/**
449
	 * Handler for filtering out non-whitelisted comment meta.
450
	 *
451
	 * @access public
452
	 *
453
	 * @param array $args Hook args.
454
	 * @return array|boolean False if not whitelisted, the original hook args otherwise.
455
	 */
456
	public function filter_meta( $args ) {
457
		if ( $this->is_comment_type_allowed( $args[1] ) && $this->is_whitelisted_comment_meta( $args[2] ) ) {
458
			return $args;
459
		}
460
461
		return false;
462
	}
463
464
	/**
465
	 * Expand the comment IDs to comment objects and meta before being serialized and sent to the server.
466
	 *
467
	 * @access public
468
	 *
469
	 * @param array $args The hook parameters.
470
	 * @return array The expanded hook parameters.
471
	 */
472
	public function expand_comment_ids( $args ) {
473
		list( $comment_ids, $previous_interval_end ) = $args;
474
		$comments                                    = get_comments(
475
			array(
476
				'include_unapproved' => true,
477
				'comment__in'        => $comment_ids,
478
				'orderby'            => 'comment_ID',
479
				'order'              => 'DESC',
480
			)
481
		);
482
483
		return array(
484
			$comments,
485
			$this->get_metadata( $comment_ids, 'comment', Settings::get_setting( 'comment_meta_whitelist' ) ),
486
			$previous_interval_end,
487
		);
488
	}
489
}
490