_generate_related_post_image_params()   B
last analyzed

Complexity

Conditions 9
Paths 26

Size

Total Lines 65

Duplication

Lines 14
Ratio 21.54 %

Importance

Changes 0
Metric Value
cc 9
nc 26
nop 1
dl 14
loc 65
rs 7.208
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use Automattic\Jetpack\Assets;
4
use Automattic\Jetpack\Blocks;
5
use Automattic\Jetpack\Sync\Settings;
6
7
class Jetpack_RelatedPosts {
8
	const VERSION   = '20210604';
9
	const SHORTCODE = 'jetpack-related-posts';
10
11
	private static $instance     = null;
12
	private static $instance_raw = null;
13
14
	/**
15
	 * Creates and returns a static instance of Jetpack_RelatedPosts.
16
	 *
17
	 * @return Jetpack_RelatedPosts
18
	 */
19
	public static function init() {
20
		if ( ! self::$instance ) {
21
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
22
				self::$instance = WPCOM_RelatedPosts::init();
23
			} else {
24
				self::$instance = new Jetpack_RelatedPosts();
25
			}
26
		}
27
28
		return self::$instance;
29
	}
30
31
	/**
32
	 * Creates and returns a static instance of Jetpack_RelatedPosts_Raw.
33
	 *
34
	 * @return Jetpack_RelatedPosts
35
	 */
36
	public static function init_raw() {
37
		if ( ! self::$instance_raw ) {
38
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
39
				self::$instance_raw = WPCOM_RelatedPosts::init_raw();
40
			} else {
41
				self::$instance_raw = new Jetpack_RelatedPosts_Raw();
42
			}
43
		}
44
45
		return self::$instance_raw;
46
	}
47
48
	protected $_options;
49
	protected $_allow_feature_toggle;
50
	protected $_blog_charset;
51
	protected $_convert_charset;
52
	protected $_previous_post_id;
53
	protected $_found_shortcode = false;
54
55
	/**
56
	 * Constructor for Jetpack_RelatedPosts.
57
	 *
58
	 * @uses get_option, add_action, apply_filters
59
	 *
60
	 * @return null
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
61
	 */
62
	public function __construct() {
63
		$this->_blog_charset = get_option( 'blog_charset' );
64
		$this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
65
66
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
67
		add_action( 'wp', array( $this, 'action_frontend_init' ) );
68
69
		if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
70
			jetpack_require_lib( 'class.media-summary' );
71
		}
72
73
		// Add Related Posts to the REST API Post response.
74
		add_action( 'rest_api_init', array( $this, 'rest_register_related_posts' ) );
75
76
		Blocks::jetpack_register_block(
77
			'jetpack/related-posts',
78
			array(
79
				'render_callback' => array( $this, 'render_block' ),
80
			)
81
		);
82
	}
83
84
	protected function get_blog_id() {
85
		return Jetpack_Options::get_option( 'id' );
86
	}
87
88
	/**
89
	 * =================
90
	 * ACTIONS & FILTERS
91
	 * =================
92
	 */
93
94
	/**
95
	 * Add a checkbox field to Settings > Reading for enabling related posts.
96
	 *
97
	 * @action admin_init
98
	 * @uses add_settings_field, __, register_setting, add_action
99
	 * @return null
100
	 */
101
	public function action_admin_init() {
102
103
		// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
104
		add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
105
		register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
106
		add_action('admin_head', array( $this, 'print_setting_head' ) );
107
108
		if( 'options-reading.php' == $GLOBALS['pagenow'] ) {
109
			// Enqueue style for live preview on the reading settings page
110
			$this->_enqueue_assets( false, true );
111
		}
112
	}
113
114
	/**
115
	 * Load related posts assets if it's an eligible front end page or execute search and return JSON if it's an endpoint request.
116
	 *
117
	 * @global $_GET
118
	 * @action wp
119
	 * @uses add_shortcode, get_the_ID
120
	 * @returns null
121
	 */
122
	public function action_frontend_init() {
123
		// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
124
		add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html_unsupported' ) );
125
126
		if ( ! $this->_enabled_for_request() )
127
			return;
128
129
		if ( isset( $_GET['relatedposts'] ) ) {
130
			$excludes = $this->parse_numeric_get_arg( 'relatedposts_exclude' );
131
			$this->_action_frontend_init_ajax( $excludes );
132
		} else {
133
			if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
134
				$this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
135
				$this->_previous_post_id = (int) $_GET['relatedposts_origin'];
136
			}
137
138
			$this->_action_frontend_init_page();
139
		}
140
141
	}
142
143
	/**
144
	 * Render insertion point.
145
	 *
146
	 * @since 4.2.0
147
	 *
148
	 * @return string
149
	 */
150
	public function get_headline() {
151
		$options = $this->get_options();
152
153
		if ( $options['show_headline'] ) {
154
			$headline = sprintf(
155
				/** This filter is already documented in modules/sharedaddy/sharing-service.php */
156
				apply_filters( 'jetpack_sharing_headline_html', '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>', esc_html( $options['headline'] ), 'related-posts' ),
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with esc_html($options['headline']).

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...
157
				esc_html( $options['headline'] )
158
			);
159
		} else {
160
			$headline = '';
161
		}
162
		return $headline;
163
	}
164
165
	/**
166
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
167
	 * Will skip adding the target if the post content contains a Related Posts block, if the 'get_the_excerpt'
168
	 * hook is in the current filter list, or if the site is running an FSE/Site Editor theme.
169
	 *
170
	 * @filter the_content
171
	 *
172
	 * @param string $content Post content.
173
	 *
174
	 * @returns string
175
	 */
176
	public function filter_add_target_to_dom( $content ) {
177
		if ( has_block( 'jetpack/related-posts' ) || Blocks::is_fse_theme() ) {
178
			return $content;
179
		}
180
181
		if ( ! $this->_found_shortcode && ! doing_filter( 'get_the_excerpt' ) ) {
182
			if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
183
				$content .= "\n" . $this->get_server_rendered_html();
184
			} else {
185
				$content .= "\n" . $this->get_client_rendered_html();
186
			}
187
		}
188
189
		return $content;
190
	}
191
192
	/**
193
	 * Render static markup based on the Gutenberg block code
194
	 *
195
	 * @return string Rendered related posts HTML.
196
	 */
197
	public function get_server_rendered_html() {
198
		$rp_settings       = $this->get_options();
199
		$block_rp_settings = array(
200
			'displayThumbnails' => $rp_settings['show_thumbnails'],
201
			'showHeadline'      => $rp_settings['show_headline'],
202
			'displayDate'       => isset( $rp_settings['show_date'] ) ? (bool) $rp_settings['show_date'] : true,
203
			'displayContext'    => isset( $rp_settings['show_context'] ) && $rp_settings['show_context'],
204
			'postLayout'        => isset( $rp_settings['layout'] ) ? $rp_settings['layout'] : 'grid',
205
			'postsToShow'       => isset( $rp_settings['size'] ) ? $rp_settings['size'] : 3,
206
			/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
207
			'headline'          => apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() ),
208
		);
209
210
		return $this->render_block( $block_rp_settings );
211
	}
212
213
	/**
214
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
215
	 *
216
	 * @filter the_content
217
	 * @param string $content
218
	 * @uses has_shortcode
219
	 * @returns string
220
	 */
221
	public function test_for_shortcode( $content ) {
222
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
223
224
		return $content;
225
	}
226
227
	/**
228
	 * Returns the HTML for the related posts section.
229
	 *
230
	 * @uses esc_html__, apply_filters
231
	 * @returns string
232
	 */
233
	public function get_client_rendered_html() {
234
		if ( Settings::is_syncing() ) {
235
			return '';
236
		}
237
238
		// For client-side rendering, enqueue both the styles and the scripts for fetching related posts.
239
		// This supports related posts added via the shortcode, or via the hook for non-AMP requests.
240
		$this->_enqueue_assets( true, true );
241
242
		/**
243
		 * Filter the Related Posts headline.
244
		 *
245
		 * @module related-posts
246
		 *
247
		 * @since 3.0.0
248
		 *
249
		 * @param string $headline Related Posts heading.
250
		 */
251
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
252
253
		if ( $this->_previous_post_id ) {
254
			$exclude = "data-exclude='{$this->_previous_post_id}'";
255
		} else {
256
			$exclude = "";
257
		}
258
259
		return <<<EOT
260
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
261
	$headline
262
</div>
263
EOT;
264
	}
265
266
	/**
267
	 * Returns the HTML for the related posts section if it's running in the loop or other instances where we don't support related posts.
268
	 *
269
	 * @returns string
270
	 */
271
	public function get_client_rendered_html_unsupported() {
272
		if ( Settings::is_syncing() ) {
273
			return '';
274
		}
275
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
276
	}
277
278
	/**
279
	 * ===============
280
	 * GUTENBERG BLOCK
281
	 * ===============
282
	 */
283
284
	/**
285
	 * Echoes out items for the Gutenberg block
286
	 *
287
	 * @param array $related_post The post oject.
288
	 * @param array $block_attributes The block attributes.
289
	 */
290
	public function render_block_item( $related_post, $block_attributes ) {
291
		$instance_id = 'related-posts-item-' . uniqid();
292
		$label_id    = $instance_id . '-label';
293
294
		$item_markup = sprintf(
295
			'<ul id="%1$s" aria-labelledby="%2$s" class="jp-related-posts-i2__post" role="menuitem">',
296
			esc_attr( $instance_id ),
297
			esc_attr( $label_id )
298
		);
299
300
		$item_markup .= sprintf(
301
			'<li class="jp-related-posts-i2__post-link"><a id="%1$s" href="%2$s" %4$s>%3$s</a></li>',
302
			esc_attr( $label_id ),
303
			esc_url( $related_post['url'] ),
304
			esc_attr( $related_post['title'] ),
305
			( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' )
306
		);
307
308
		if ( ! empty( $block_attributes['show_thumbnails'] ) && ! empty( $related_post['img']['src'] ) ) {
309
			$img_link = sprintf(
310
				'<li class="jp-related-posts-i2__post-img-link"><a href="%1$s" %2$s><img src="%3$s" width="%4$s" height="%5$s" alt="%6$s" /></a></li>',
311
				esc_url( $related_post['url'] ),
312
				( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' ),
313
				esc_url( $related_post['img']['src'] ),
314
				esc_attr( $related_post['img']['width'] ),
315
				esc_attr( $related_post['img']['height'] ),
316
				esc_attr( $related_post['img']['alt_text'] )
317
			);
318
319
			$item_markup .= $img_link;
320
		}
321
322
		if ( $block_attributes['show_date'] ) {
323
			$date_tag = sprintf(
324
				'<li class="jp-related-posts-i2__post-date">%1$s</li>',
325
				esc_html( $related_post['date'] )
326
			);
327
328
			$item_markup .= $date_tag;
329
		}
330
331
		if ( ( $block_attributes['show_context'] ) && ! empty( $related_post['context'] ) ) {
332
			$context_tag = sprintf(
333
				'<li class="jp-related-posts-i2__post-context">%1$s</li>',
334
				esc_html( $related_post['context'] )
335
			);
336
337
			$item_markup .= $context_tag;
338
		}
339
340
		$item_markup .= '</ul>';
341
342
		return $item_markup;
343
	}
344
345
	/**
346
	 * Render a related posts row.
347
	 *
348
	 * @param array $posts The posts to render into the row.
349
	 * @param array $block_attributes Block attributes.
350
	 */
351
	public function render_block_row( $posts, $block_attributes ) {
352
		$rows_markup = '';
353
		foreach ( $posts as $post ) {
354
			$rows_markup .= $this->render_block_item( $post, $block_attributes );
355
		}
356
		return sprintf(
357
			'<div class="jp-related-posts-i2__row" data-post-count="%1$s">%2$s</div>',
358
			count( $posts ),
359
			$rows_markup
360
		);
361
	}
362
363
	/**
364
	 * Render the related posts markup.
365
	 *
366
	 * @param array $attributes Block attributes.
367
	 * @return string
368
	 */
369
	public function render_block( $attributes ) {
370
		// Enqueue styles for Related Posts. We do not need to enqueue the scripts, as the related posts are
371
		// fetched server-side.
372
		$this->_enqueue_assets( false, true );
373
374
		$block_attributes = array(
375
			'headline'        => isset( $attributes['headline'] ) ? $attributes['headline'] : null,
376
			'show_thumbnails' => isset( $attributes['displayThumbnails'] ) && $attributes['displayThumbnails'],
377
			'show_date'       => isset( $attributes['displayDate'] ) ? (bool) $attributes['displayDate'] : true,
378
			'show_context'    => isset( $attributes['displayContext'] ) && $attributes['displayContext'],
379
			'layout'          => isset( $attributes['postLayout'] ) && 'list' === $attributes['postLayout'] ? $attributes['postLayout'] : 'grid',
380
			'size'            => ! empty( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 3,
381
		);
382
383
		$excludes = $this->parse_numeric_get_arg( 'relatedposts_origin' );
384
385
		$related_posts = $this->get_for_post_id(
386
			get_the_ID(),
387
			array(
388
				'size'             => $block_attributes['size'],
389
				'exclude_post_ids' => $excludes,
390
			)
391
		);
392
393
		$display_lower_row = $block_attributes['size'] > 3;
394
395
		if ( empty( $related_posts ) ) {
396
			return '';
397
		}
398
399
		switch ( count( $related_posts ) ) {
400
			case 2:
401
			case 4:
402
			case 5:
403
				$top_row_end = 2;
404
				break;
405
406
			default:
407
				$top_row_end = 3;
408
				break;
409
		}
410
411
		$upper_row_posts = array_slice( $related_posts, 0, $top_row_end );
412
		$lower_row_posts = array_slice( $related_posts, $top_row_end );
413
414
		$rows_markup = $this->render_block_row( $upper_row_posts, $block_attributes );
415
		if ( $display_lower_row ) {
416
			$rows_markup .= $this->render_block_row( $lower_row_posts, $block_attributes );
417
		}
418
419
		return sprintf(
420
			'<nav class="jp-relatedposts-i2" data-layout="%1$s">%2$s%3$s</nav>',
421
			esc_attr( $block_attributes['layout'] ),
422
			$block_attributes['headline'],
423
			$rows_markup
424
		);
425
	}
426
427
	/**
428
	 * ========================
429
	 * PUBLIC UTILITY FUNCTIONS
430
	 * ========================
431
	 */
432
433
	/**
434
	 * Parse a numeric GET variable to an array of values.
435
	 *
436
	 * @since 6.9.0
437
	 *
438
	 * @uses absint
439
	 *
440
	 * @param string $arg Name of the GET variable.
441
	 * @return array $result Parsed value(s)
442
	 */
443
	public function parse_numeric_get_arg( $arg ) {
444
		$result = array();
445
446
		if ( isset( $_GET[ $arg ] ) ) {
447
			if ( is_string( $_GET[ $arg ] ) ) {
448
				$result = explode( ',', $_GET[ $arg ] );
449
			} elseif ( is_array( $_GET[ $arg ] ) ) {
450
				$result = array_values( $_GET[ $arg ] );
451
			}
452
453
			$result = array_unique( array_filter( array_map( 'absint', $result ) ) );
454
		}
455
456
		return $result;
457
	}
458
459
	/**
460
	 * Gets options set for Jetpack_RelatedPosts and merge with defaults.
461
	 *
462
	 * @uses Jetpack_Options::get_option, apply_filters
463
	 * @return array
464
	 */
465
	public function get_options() {
466
		if ( null === $this->_options ) {
467
			$this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
468
			if ( ! is_array( $this->_options ) )
469
				$this->_options = array();
470
			if ( ! isset( $this->_options['enabled'] ) )
471
				$this->_options['enabled'] = true;
472
			if ( ! isset( $this->_options['show_headline'] ) )
473
				$this->_options['show_headline'] = true;
474
			if ( ! isset( $this->_options['show_thumbnails'] ) )
475
				$this->_options['show_thumbnails'] = false;
476
			if ( ! isset( $this->_options['show_date'] ) ) {
477
				$this->_options['show_date'] = true;
478
			}
479
			if ( ! isset( $this->_options['show_context'] ) ) {
480
				$this->_options['show_context'] = true;
481
			}
482
			if ( ! isset( $this->_options['layout'] ) ) {
483
				$this->_options['layout'] = 'grid';
484
			}
485
			if ( ! isset( $this->_options['headline'] ) ) {
486
				$this->_options['headline'] = esc_html__( 'Related', 'jetpack' );
487
			}
488
			if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
489
				$this->_options['size'] = 3;
490
491
			/**
492
			 * Filter Related Posts basic options.
493
			 *
494
			 * @module related-posts
495
			 *
496
			 * @since 2.8.0
497
			 *
498
			 * @param array $this->_options Array of basic Related Posts options.
499
			 */
500
			$this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
501
		}
502
503
		return $this->_options;
504
	}
505
506
	public function get_option( $option_name ) {
507
		$options = $this->get_options();
508
509
		if ( isset( $options[ $option_name ] ) ) {
510
			return $options[ $option_name ];
511
		}
512
513
		return false;
514
	}
515
516
	/**
517
	 * Parses input and returns normalized options array.
518
	 *
519
	 * @param array $input
520
	 * @uses self::get_options
521
	 * @return array
522
	 */
523
	public function parse_options( $input ) {
524
		$current = $this->get_options();
525
526
		if ( !is_array( $input ) )
527
			$input = array();
528
529
		if (
530
			! isset( $input['enabled'] )
531
			|| isset( $input['show_date'] )
532
			|| isset( $input['show_context'] )
533
			|| isset( $input['layout'] )
534
			|| isset( $input['headline'] )
535
			) {
536
			$input['enabled'] = '1';
537
		}
538
539
		if ( '1' == $input['enabled'] ) {
540
			$current['enabled'] = true;
541
			$current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
542
			$current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
543
			$current['show_date'] = ( isset( $input['show_date'] ) && '1' == $input['show_date'] );
544
			$current['show_context'] = ( isset( $input['show_context'] ) && '1' == $input['show_context'] );
545
			$current['layout'] = isset( $input['layout'] ) && in_array( $input['layout'], array( 'grid', 'list' ), true ) ? $input['layout'] : 'grid';
546
			$current['headline'] = isset( $input['headline'] ) ? $input['headline'] : esc_html__( 'Related', 'jetpack' );
547
		} else {
548
			$current['enabled'] = false;
549
		}
550
551
		if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
552
			$current['size'] = (int)$input['size'];
553
		else
554
			$current['size'] = null;
555
556
		return $current;
557
	}
558
559
	/**
560
	 * HTML for admin settings page.
561
	 *
562
	 * @uses self::get_options, checked, esc_html__
563
	 * @returns null
564
	 */
565
	public function print_setting_html() {
566
		$options = $this->get_options();
567
568
		$ui_settings_template = <<<EOT
569
<p class="description">%s</p>
570
<ul id="settings-reading-relatedposts-customize">
571
	<li>
572
		<label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
573
	</li>
574
	<li>
575
		<label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
576
	</li>
577
	<li>
578
		<label><input name="jetpack_relatedposts[show_date]" type="checkbox" value="1" %s /> %s</label>
579
	</li>
580
	<li>
581
		<label><input name="jetpack_relatedposts[show_context]" type="checkbox" value="1" %s /> %s</label>
582
	</li>
583
</ul>
584
<div id='settings-reading-relatedposts-preview'>
585
	%s
586
	<div id="jp-relatedposts" class="jp-relatedposts"></div>
587
</div>
588
EOT;
589
		$ui_settings = sprintf(
590
			$ui_settings_template,
591
			esc_html__( 'The following settings will impact all related posts on your site, except for those you created via the block editor:', 'jetpack' ),
592
			checked( $options['show_headline'], true, false ),
593
			esc_html__( 'Highlight related content with a heading', 'jetpack' ),
594
			checked( $options['show_thumbnails'], true, false ),
595
			esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
596
			checked( $options['show_date'], true, false ),
597
			esc_html__( 'Show entry date', 'jetpack' ),
598
			checked( $options['show_context'], true, false ),
599
			esc_html__( 'Show context (category or tag)', 'jetpack' ),
600
			esc_html__( 'Preview:', 'jetpack' )
601
		);
602
603
		if ( !$this->_allow_feature_toggle() ) {
604
			$template = <<<EOT
605
<input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
606
%s
607
EOT;
608
			printf(
609
				$template,
610
				$ui_settings
611
			);
612
		} else {
613
			$template = <<<EOT
614
<ul id="settings-reading-relatedposts">
615
	<li>
616
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
617
	</li>
618
	<li>
619
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
620
		%s
621
	</li>
622
</ul>
623
EOT;
624
			printf(
625
				$template,
626
				checked( $options['enabled'], false, false ),
627
				esc_html__( 'Hide related content after posts', 'jetpack' ),
628
				checked( $options['enabled'], true, false ),
629
				esc_html__( 'Show related content after posts', 'jetpack' ),
630
				$ui_settings
631
			);
632
		}
633
	}
634
635
	/**
636
	 * Head JS/CSS for admin settings page.
637
	 *
638
	 * @uses esc_html__
639
	 * @returns null
640
	 */
641
	public function print_setting_head() {
642
643
		// only dislay the Related Posts JavaScript on the Reading Settings Admin Page
644
		$current_screen =  get_current_screen();
645
646
		if ( is_null( $current_screen ) ) {
647
			return;
648
		}
649
650
		if( 'options-reading' != $current_screen->id )
651
			return;
652
653
		$related_headline = sprintf(
654
			'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
655
			esc_html__( 'Related', 'jetpack' )
656
		);
657
658
		$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
659
		$related_with_images = <<<EOT
660
<div class="jp-relatedposts-items jp-relatedposts-items-visual">
661
	<div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
662
		<a $href_params>
663
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/cat-blog.png" width="350" alt="Big iPhone/iPad Update Now Available" scale="0">
664
		</a>
665
		<h4 class="jp-relatedposts-post-title">
666
			<a $href_params>Big iPhone/iPad Update Now Available</a>
667
		</h4>
668
		<p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
669
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
670
	</div>
671
	<div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
672
		<a $href_params>
673
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/devices.jpg" width="350" alt="The WordPress for Android App Gets a Big Facelift" scale="0">
674
		</a>
675
		<h4 class="jp-relatedposts-post-title">
676
			<a $href_params>The WordPress for Android App Gets a Big Facelift</a>
677
		</h4>
678
		<p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
679
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
680
	</div>
681
	<div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
682
		<a $href_params>
683
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg" width="350" alt="Upgrade Focus: VideoPress For Weddings" scale="0">
684
		</a>
685
		<h4 class="jp-relatedposts-post-title">
686
			<a $href_params>Upgrade Focus: VideoPress For Weddings</a>
687
		</h4>
688
		<p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
689
		<p class="jp-relatedposts-post-context">In "Upgrade"</p>
690
	</div>
691
</div>
692
EOT;
693
		$related_with_images = str_replace( "\n", '', $related_with_images );
694
		$related_without_images = <<<EOT
695
<div class="jp-relatedposts-items jp-relatedposts-items-minimal">
696
	<p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
697
		<span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
698
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
699
	</p>
700
	<p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
701
		<span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
702
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
703
	</p>
704
	<p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
705
		<span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
706
		<span class="jp-relatedposts-post-context">In "Upgrade"</span>
707
	</p>
708
</div>
709
EOT;
710
		$related_without_images = str_replace( "\n", '', $related_without_images );
711
712
		if ( $this->_allow_feature_toggle() ) {
713
			$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
714
		} else {
715
			$extra_css = '';
716
		}
717
718
		echo <<<EOT
719
<style type="text/css">
720
	#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
721
	#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
722
	$extra_css
723
</style>
724
<script type="text/javascript">
725
	jQuery( document ).ready( function($) {
726
		var update_ui = function() {
727
			var is_enabled = true;
728
			if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
729
				if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
730
					is_enabled = false;
731
				}
732
			}
733
			if ( is_enabled ) {
734
				$( '#settings-reading-relatedposts-customize' )
735
					.removeClass( 'disabled' )
736
					.find( 'input' )
737
					.attr( 'disabled', false );
738
				$( '#settings-reading-relatedposts-preview' )
739
					.removeClass( 'disabled' );
740
			} else {
741
				$( '#settings-reading-relatedposts-customize' )
742
					.addClass( 'disabled' )
743
					.find( 'input' )
744
					.attr( 'disabled', true );
745
				$( '#settings-reading-relatedposts-preview' )
746
					.addClass( 'disabled' );
747
			}
748
		};
749
750
		var update_preview = function() {
751
			var html = '';
752
			if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
753
				html += '$related_headline';
754
			}
755
			if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
756
				html += '$related_with_images';
757
			} else {
758
				html += '$related_without_images';
759
			}
760
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).html( html );
761
			if ( $( 'input[name="jetpack_relatedposts[show_date]"]:checked' ).length ) {
762
				$( '.jp-relatedposts-post-title' ).each( function() {
763
					$( this ).after( $( '<span>August 8, 2005</span>' ) );
764
				} );
765
			}
766
			if ( $( 'input[name="jetpack_relatedposts[show_context]"]:checked' ).length ) {
767
				$( '.jp-relatedposts-post-context' ).show();
768
			} else {
769
				$( '.jp-relatedposts-post-context' ).hide();
770
			}
771
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).show();
772
		};
773
774
		// Update on load
775
		update_preview();
776
		update_ui();
777
778
		// Update on change
779
		$( '#settings-reading-relatedposts-customize input' )
780
			.change( update_preview );
781
		$( '#settings-reading-relatedposts' )
782
			.find( 'input.tog' )
783
			.change( update_ui );
784
	});
785
</script>
786
EOT;
787
	}
788
789
	/**
790
	 * Gets an array of related posts that match the given post_id.
791
	 *
792
	 * @param int   $post_id Post which we want to find related posts for.
793
	 * @param array $args - params to use when building Elasticsearch filters to narrow down the search domain.
794
	 * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
795
	 * @return array
796
	 */
797
	public function get_for_post_id( $post_id, array $args ) {
798
		$options = $this->get_options();
799
800
		if ( ! empty( $args['size'] ) ) {
801
			$options['size'] = $args['size'];
802
		}
803
804
		if (
805
			! $options['enabled']
806
			|| 0 === (int) $post_id
807
			|| empty( $options['size'] )
808
		) {
809
			return array();
810
		}
811
812
		$defaults = array(
813
			'size'             => (int) $options['size'],
814
			'post_type'        => get_post_type( $post_id ),
815
			'post_formats'     => array(),
816
			'has_terms'        => array(),
817
			'date_range'       => array(),
818
			'exclude_post_ids' => array(),
819
		);
820
		$args     = wp_parse_args( $args, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,?,{"size":"...ude_post_ids":"array"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
821
		/**
822
		 * Filter the arguments used to retrieve a list of Related Posts.
823
		 *
824
		 * @module related-posts
825
		 *
826
		 * @since 2.8.0
827
		 *
828
		 * @param array $args Array of options to retrieve Related Posts.
829
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
830
		 */
831
		$args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
832
833
		$filters = $this->_get_es_filters_from_args( $post_id, $args );
834
		/**
835
		 * Filter Elasticsearch options used to calculate Related Posts.
836
		 *
837
		 * @module related-posts
838
		 *
839
		 * @since 2.8.0
840
		 *
841
		 * @param array $filters Array of Elasticsearch filters based on the post_id and args.
842
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
843
		 */
844
		$filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
845
846
		$results = $this->_get_related_posts( $post_id, $args['size'], $filters );
847
		/**
848
		 * Filter the array of related posts matched by Elasticsearch.
849
		 *
850
		 * @module related-posts
851
		 *
852
		 * @since 2.8.0
853
		 *
854
		 * @param array $results Array of related posts matched by Elasticsearch.
855
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
856
		 */
857
		return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
858
	}
859
860
	/**
861
	 * =========================
862
	 * PRIVATE UTILITY FUNCTIONS
863
	 * =========================
864
	 */
865
866
	/**
867
	 * Creates an array of Elasticsearch filters based on the post_id and args.
868
	 *
869
	 * @param int $post_id
870
	 * @param array $args
871
	 * @uses apply_filters, get_post_types, get_post_format_strings
872
	 * @return array
873
	 */
874
	protected function _get_es_filters_from_args( $post_id, array $args ) {
875
		$filters = array();
876
877
		/**
878
		 * Filter the terms used to search for Related Posts.
879
		 *
880
		 * @module related-posts
881
		 *
882
		 * @since 2.8.0
883
		 *
884
		 * @param array $args['has_terms'] Array of terms associated to the Related Posts.
885
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
886
		 */
887
		$args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
888
		if ( ! empty( $args['has_terms'] ) ) {
889
			foreach( (array)$args['has_terms'] as $term ) {
890
				if ( mb_strlen( $term->taxonomy ) ) {
891 View Code Duplication
					switch ( $term->taxonomy ) {
892
						case 'post_tag':
893
							$tax_fld = 'tag.slug';
894
							break;
895
						case 'category':
896
							$tax_fld = 'category.slug';
897
							break;
898
						default:
899
							$tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
900
							break;
901
					}
902
					$filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
903
				}
904
			}
905
		}
906
907
		/**
908
		 * Filter the Post Types where we search Related Posts.
909
		 *
910
		 * @module related-posts
911
		 *
912
		 * @since 2.8.0
913
		 *
914
		 * @param array $args['post_type'] Array of Post Types.
915
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
916
		 */
917
		$args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
918
		$valid_post_types = get_post_types();
919
		if ( is_array( $args['post_type'] ) ) {
920
			$sanitized_post_types = array();
921
			foreach ( $args['post_type'] as $pt ) {
922
				if ( in_array( $pt, $valid_post_types ) )
923
					$sanitized_post_types[] = $pt;
924
			}
925
			if ( ! empty( $sanitized_post_types ) )
926
				$filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
927
		} else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
928
			$filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
929
		}
930
931
		/**
932
		 * Filter the Post Formats where we search Related Posts.
933
		 *
934
		 * @module related-posts
935
		 *
936
		 * @since 3.3.0
937
		 *
938
		 * @param array $args['post_formats'] Array of Post Formats.
939
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
940
		 */
941
		$args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
942
		$valid_post_formats = get_post_format_strings();
943
		$sanitized_post_formats = array();
944
		foreach ( $args['post_formats'] as $pf ) {
945
			if ( array_key_exists( $pf, $valid_post_formats ) ) {
946
				$sanitized_post_formats[] = $pf;
947
			}
948
		}
949
		if ( ! empty( $sanitized_post_formats ) ) {
950
			$filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
951
		}
952
953
		/**
954
		 * Filter the date range used to search Related Posts.
955
		 *
956
		 * @module related-posts
957
		 *
958
		 * @since 2.8.0
959
		 *
960
		 * @param array $args['date_range'] Array of a month interval where we search Related Posts.
961
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
962
		 */
963
		$args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
964
		if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
965
			$args['date_range'] = array_map( 'intval', $args['date_range'] );
966
			if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
967
				$filters[] = array(
968
					'range' => array(
969
						'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
970
					)
971
				);
972
			}
973
		}
974
975
		/**
976
		 * Filter the Post IDs excluded from appearing in Related Posts.
977
		 *
978
		 * @module related-posts
979
		 *
980
		 * @since 2.9.0
981
		 *
982
		 * @param array $args['exclude_post_ids'] Array of Post IDs.
983
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
984
		 */
985
		$args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
986
		if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
987
			$excluded_post_ids = array();
988
			foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
989
				$exclude_post_id = (int)$exclude_post_id;
990
				if ( $exclude_post_id > 0 )
991
					$excluded_post_ids[] = $exclude_post_id;
992
			}
993
			$filters[] = array( 'not' => array( 'terms' => array( 'post_id' => $excluded_post_ids ) ) );
994
		}
995
996
		return $filters;
997
	}
998
999
	/**
1000
	 * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
1001
	 *
1002
	 * @param array $date_range
1003
	 * @return array
1004
	 */
1005
	protected function _get_coalesced_range( array $date_range ) {
1006
		$now = time();
1007
		$coalesce_time = $this->get_blog_id() % 86400;
1008
		$current_time = $now - strtotime( 'today', $now );
1009
1010
		if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
1011
			// Move back 1 period
1012
			return array(
1013
				'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1014
				'to'   => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1015
			);
1016
		} else {
1017
			// Use current period
1018
			return array(
1019
				'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1020
				'to'   => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1021
			);
1022
		}
1023
	}
1024
1025
	/**
1026
	 * Generate and output ajax response for related posts API call.
1027
	 * NOTE: Calls exit() to end all further processing after payload has been outputed.
1028
	 *
1029
	 * @param array $excludes array of post_ids to exclude
1030
	 * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
1031
	 * @return null
1032
	 */
1033
	protected function _action_frontend_init_ajax( array $excludes ) {
1034
		define( 'DOING_AJAX', true );
1035
1036
		header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
1037
		send_nosniff_header();
1038
1039
		$options = $this->get_options();
1040
1041
		if ( isset( $_GET['jetpackrpcustomize'] ) ) {
1042
1043
			// If we're in the customizer, add dummy content.
1044
			$date_now = current_time( get_option( 'date_format' ) );
1045
			$related_posts = array(
1046
				array(
1047
					'id'       => - 1,
1048
					'url'      => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
1049
					'url_meta' => array(
1050
						'origin'   => 0,
1051
						'position' => 0
1052
					),
1053
					'title'    => esc_html__( 'Big iPhone/iPad Update Now Available', 'jetpack' ),
1054
					'date'     => $date_now,
1055
					'format'   => false,
1056
					'excerpt'  => esc_html__( 'It is that time of the year when devices are shiny again.', 'jetpack' ),
1057
					'rel'      => 'nofollow',
1058
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1059
					'img'      => array(
1060
						'src'    => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
1061
						'width'  => 350,
1062
						'height' => 200
1063
					),
1064
					'classes'  => array()
1065
				),
1066
				array(
1067
					'id'       => - 1,
1068
					'url'      => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
1069
					'url_meta' => array(
1070
						'origin'   => 0,
1071
						'position' => 0
1072
					),
1073
					'title'    => esc_html__( 'The WordPress for Android App Gets a Big Facelift', 'jetpack' ),
1074
					'date'     => $date_now,
1075
					'format'   => false,
1076
					'excerpt'  => esc_html__( 'Writing is new again in Android with the new WordPress app.', 'jetpack' ),
1077
					'rel'      => 'nofollow',
1078
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1079
					'img'      => array(
1080
						'src'    => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
1081
						'width'  => 350,
1082
						'height' => 200
1083
					),
1084
					'classes'  => array()
1085
				),
1086
				array(
1087
					'id'       => - 1,
1088
					'url'      => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
1089
					'url_meta' => array(
1090
						'origin'   => 0,
1091
						'position' => 0
1092
					),
1093
					'title'    => esc_html__( 'Upgrade Focus, VideoPress for weddings', 'jetpack' ),
1094
					'date'     => $date_now,
1095
					'format'   => false,
1096
					'excerpt'  => esc_html__( 'Weddings are in the spotlight now with VideoPress for weddings.', 'jetpack' ),
1097
					'rel'      => 'nofollow',
1098
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1099
					'img'      => array(
1100
						'src'    => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
1101
						'width'  => 350,
1102
						'height' => 200
1103
					),
1104
					'classes'  => array()
1105
				),
1106
			);
1107
1108
			for ( $total = 0; $total < $options['size'] - 3; $total++ ) {
1109
				$related_posts[] = $related_posts[ $total ];
1110
			}
1111
1112
			$current_post = get_post();
1113
1114
			// Exclude current post after filtering to make sure it's excluded and not lost during filtering.
1115
			$excluded_posts = array_merge(
1116
				/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
1117
				apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', array() ),
1118
				array( $current_post->ID )
1119
			);
1120
1121
			// Fetch posts with featured image.
1122
			$with_post_thumbnails = get_posts( array(
1123
				'posts_per_page'   => $options['size'],
1124
				'post__not_in'     => $excluded_posts,
1125
				'post_type'        => $current_post->post_type,
1126
				'meta_key'         => '_thumbnail_id',
1127
				'suppress_filters' => false,
1128
			) );
1129
1130
			// If we don't have enough, fetch posts without featured image.
1131
			if ( 0 < ( $more = $options['size'] - count( $with_post_thumbnails ) ) ) {
1132
				$no_post_thumbnails = get_posts( array(
1133
					'posts_per_page'  => $more,
1134
					'post__not_in'    => $excluded_posts,
1135
					'post_type'       => $current_post->post_type,
1136
					'meta_query' => array(
1137
						array(
1138
							'key'     => '_thumbnail_id',
1139
							'compare' => 'NOT EXISTS',
1140
						),
1141
					),
1142
					'suppress_filters' => false,
1143
				) );
1144
			} else {
1145
				$no_post_thumbnails = array();
1146
			}
1147
1148
			foreach ( array_merge( $with_post_thumbnails, $no_post_thumbnails ) as $index => $real_post ) {
1149
				$related_posts[ $index ]['id']      = $real_post->ID;
1150
				$related_posts[ $index ]['url']     = esc_url( get_permalink( $real_post ) );
1151
				$related_posts[ $index ]['title']   = $this->_to_utf8( $this->_get_title( $real_post->post_title, $real_post->post_content ) );
1152
				$related_posts[ $index ]['date']    = get_the_date( '', $real_post );
1153
				$related_posts[ $index ]['excerpt'] = html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $real_post->post_excerpt, $real_post->post_content ) ), ENT_QUOTES, 'UTF-8' );
1154
				$related_posts[ $index ]['img']     = $this->_generate_related_post_image_params( $real_post->ID );
1155
				$related_posts[ $index ]['context'] = $this->_generate_related_post_context( $real_post->ID );
1156
			}
1157
		} else {
1158
			$related_posts = $this->get_for_post_id(
1159
				get_the_ID(),
1160
				array(
1161
					'exclude_post_ids' => $excludes,
1162
				)
1163
			);
1164
		}
1165
1166
		$response = array(
1167
			'version' => self::VERSION,
1168
			'show_thumbnails' => (bool) $options['show_thumbnails'],
1169
			'show_date' => (bool) $options['show_date'],
1170
			'show_context' => (bool) $options['show_context'],
1171
			'layout' => (string) $options['layout'],
1172
			'headline' => (string) $options['headline'],
1173
			'items' => array(),
1174
		);
1175
1176
		if ( count( $related_posts ) == $options['size'] )
1177
			$response['items'] = $related_posts;
1178
1179
		echo json_encode( $response );
1180
1181
		exit();
1182
	}
1183
1184
	/**
1185
	 * Returns a UTF-8 encoded array of post information for the given post_id
1186
	 *
1187
	 * @param int $post_id
1188
	 * @param int $position
1189
	 * @param int $origin The post id that this is related to
1190
	 * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
1191
	 * @return array
1192
	 */
1193
	public function get_related_post_data_for_post( $post_id, $position, $origin ) {
1194
		$post = get_post( $post_id );
1195
1196
		return array(
1197
			'id' => $post->ID,
1198
			'url' => get_permalink( $post->ID ),
1199
			'url_meta' => array( 'origin' => $origin, 'position' => $position ),
1200
			'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),
1201
			'date' => get_the_date( '', $post->ID ),
1202
			'format' => get_post_format( $post->ID ),
1203
			'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
1204
			/**
1205
			 * Filters the rel attribute for the Related Posts' links.
1206
			 *
1207
			 * @module related-posts
1208
			 *
1209
			 * @since 3.7.0
1210
			 * @since 7.9.0 - Change Default value to empty.
1211
			 *
1212
			 * @param string $link_rel Link rel attribute for Related Posts' link. Default is empty.
1213
			 * @param int    $post->ID Post ID.
1214
			 */
1215
			'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', '', $post->ID ),
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post->ID.

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...
1216
			/**
1217
			 * Filter the context displayed below each Related Post.
1218
			 *
1219
			 * @module related-posts
1220
			 *
1221
			 * @since 3.0.0
1222
			 *
1223
			 * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
1224
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1225
			 */
1226
			'context' => apply_filters(
1227
				'jetpack_relatedposts_filter_post_context',
1228
				$this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
1229
				$post->ID
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post->ID.

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...
1230
			),
1231
			'img' => $this->_generate_related_post_image_params( $post->ID ),
1232
			/**
1233
			 * Filter the post css classes added on HTML markup.
1234
			 *
1235
			 * @module related-posts
1236
			 *
1237
			 * @since 3.8.0
1238
			 *
1239
			 * @param array array() CSS classes added on post HTML markup.
1240
			 * @param string $post_id Post ID.
1241
			 */
1242
			'classes' => apply_filters(
1243
				'jetpack_relatedposts_filter_post_css_classes',
1244
				array(),
1245
				$post->ID
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post->ID.

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...
1246
			),
1247
		);
1248
	}
1249
1250
	/**
1251
	 * Returns either the title or a small excerpt to use as title for post.
1252
	 *
1253
	 * @param string $post_title
1254
	 * @param string $post_content
1255
	 * @uses strip_shortcodes, wp_trim_words, __
1256
	 * @return string
1257
	 */
1258
	protected function _get_title( $post_title, $post_content ) {
1259
		if ( ! empty( $post_title ) ) {
1260
			return wp_strip_all_tags( $post_title );
1261
		}
1262
1263
		$post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
1264
		if ( ! empty( $post_title ) ) {
1265
			return $post_title;
1266
		}
1267
1268
		return __( 'Untitled Post', 'jetpack' );
1269
	}
1270
1271
	/**
1272
	 * Returns a plain text post excerpt for title attribute of links.
1273
	 *
1274
	 * @param string $post_excerpt
1275
	 * @param string $post_content
1276
	 * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
1277
	 * @return string
1278
	 */
1279
	protected function _get_excerpt( $post_excerpt, $post_content ) {
1280
		if ( empty( $post_excerpt ) )
1281
			$excerpt = $post_content;
1282
		else
1283
			$excerpt = $post_excerpt;
1284
1285
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
1286
	}
1287
1288
	/**
1289
	 * Generates the thumbnail image to be used for the post. Uses the
1290
	 * image as returned by Jetpack_PostImages::get_image()
1291
	 *
1292
	 * @param int $post_id
1293
	 * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
1294
	 * @return string
1295
	 */
1296
	protected function _generate_related_post_image_params( $post_id ) {
1297
		$options = $this->get_options();
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1298
		$image_params = array(
1299
			'alt_text' => '',
1300
			'src'      => '',
1301
			'width'    => 0,
1302
			'height'   => 0,
1303
		);
1304
1305
		/**
1306
		 * Filter the size of the Related Posts images.
1307
		 *
1308
		 * @module related-posts
1309
		 *
1310
		 * @since 2.8.0
1311
		 *
1312
		 * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
1313
		 */
1314
		$thumbnail_size = apply_filters(
1315
			'jetpack_relatedposts_filter_thumbnail_size',
1316
			array( 'width' => 350, 'height' => 200 )
1317
		);
1318
		if ( !is_array( $thumbnail_size ) ) {
1319
			$thumbnail_size = array(
1320
				'width' => (int)$thumbnail_size,
1321
				'height' => (int)$thumbnail_size
1322
			);
1323
		}
1324
1325
		// Try to get post image
1326
		if ( class_exists( 'Jetpack_PostImages' ) ) {
1327
			$img_url    = '';
1328
			$post_image = Jetpack_PostImages::get_image(
1329
				$post_id,
1330
				$thumbnail_size
1331
			);
1332
1333
			if ( is_array($post_image) ) {
1334
				$img_url = $post_image['src'];
1335
			} elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
1336
				$media = Jetpack_Media_Summary::get( $post_id );
1337
1338
				if ( is_array($media) && !empty( $media['image'] ) ) {
1339
					$img_url = $media['image'];
1340
				}
1341
			}
1342
1343 View Code Duplication
			if ( ! empty( $img_url ) ) {
1344
				if ( ! empty( $post_image['alt_text'] ) ) {
1345
					$image_params['alt_text'] = $post_image['alt_text'];
1346
				} else {
1347
					$image_params['alt_text'] = '';
1348
				}
1349
				$image_params['width']  = $thumbnail_size['width'];
1350
				$image_params['height'] = $thumbnail_size['height'];
1351
				$image_params['src']    = Jetpack_PostImages::fit_image_url(
1352
					$img_url,
1353
					$thumbnail_size['width'],
1354
					$thumbnail_size['height']
1355
				);
1356
			}
1357
		}
1358
1359
		return $image_params;
1360
	}
1361
1362
	/**
1363
	 * Returns the string UTF-8 encoded
1364
	 *
1365
	 * @param string $text
1366
	 * @return string
1367
	 */
1368
	protected function _to_utf8( $text ) {
1369
		if ( $this->_convert_charset ) {
1370
			return iconv( $this->_blog_charset, 'UTF-8', $text );
1371
		} else {
1372
			return $text;
1373
		}
1374
	}
1375
1376
	/**
1377
	 * =============================================
1378
	 * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
1379
	 * =============================================
1380
	 */
1381
1382
	/**
1383
	 * Workhorse method to return array of related posts matched by Elasticsearch.
1384
	 *
1385
	 * @param int $post_id
1386
	 * @param int $size
1387
	 * @param array $filters
1388
	 * @uses wp_remote_post, is_wp_error, get_option, wp_remote_retrieve_body, get_post, add_query_arg, remove_query_arg, get_permalink, get_post_format, apply_filters
1389
	 * @return array
1390
	 */
1391
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1392
		$hits = $this->_filter_non_public_posts(
1393
			$this->_get_related_post_ids(
1394
				$post_id,
1395
				$size,
1396
				$filters
1397
			)
1398
		);
1399
1400
		/**
1401
		 * Filter the Related Posts matched by Elasticsearch.
1402
		 *
1403
		 * @module related-posts
1404
		 *
1405
		 * @since 2.9.0
1406
		 *
1407
		 * @param array $hits Array of Post IDs matched by Elasticsearch.
1408
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1409
		 */
1410
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
1411
1412
		$related_posts = array();
1413
		foreach ( $hits as $i => $hit ) {
1414
			$related_posts[] = $this->get_related_post_data_for_post( $hit['id'], $i, $post_id );
1415
		}
1416
		return $related_posts;
1417
	}
1418
1419
	/**
1420
	 * Get array of related posts matched by Elasticsearch.
1421
	 *
1422
	 * @param int $post_id
1423
	 * @param int $size
1424
	 * @param array $filters
1425
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
1426
	 * @return array
1427
	 */
1428
	protected function _get_related_post_ids( $post_id, $size, array $filters ) {
1429
		$now_ts = time();
1430
		$cache_meta_key = '_jetpack_related_posts_cache';
1431
1432
		$body = array(
1433
			'size' => (int) $size,
1434
		);
1435
1436
		if ( !empty( $filters ) )
1437
			$body['filter'] = array( 'and' => $filters );
1438
1439
		// Build cache key
1440
		$cache_key = md5( serialize( $body ) );
1441
1442
		// Load all cached values
1443
		if ( wp_using_ext_object_cache() ) {
1444
			$transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
1445
			$cache = get_transient( $transient_name );
1446
			if ( false !== $cache ) {
1447
				return $cache;
1448
			}
1449
		} else {
1450
			$cache = get_post_meta( $post_id, $cache_meta_key, true );
1451
1452
			if ( empty( $cache ) )
1453
				$cache = array();
1454
1455
1456
			// Cache is valid! Return cached value.
1457
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
1458
				return $cache[ $cache_key ][ 'payload' ];
1459
			}
1460
		}
1461
1462
		$response = wp_remote_post(
1463
			"https://public-api.wordpress.com/rest/v1/sites/{$this->get_blog_id()}/posts/$post_id/related/",
1464
			array(
1465
				'timeout' => 10,
1466
				'user-agent' => 'jetpack_related_posts',
1467
				'sslverify' => true,
1468
				'body' => $body,
1469
			)
1470
		);
1471
1472
		// Oh no... return nothing don't cache errors.
1473
		if ( is_wp_error( $response ) ) {
1474
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
1475
				return $cache[ $cache_key ][ 'payload' ]; // return stale
1476
			else
1477
				return array();
1478
		}
1479
1480
		$results = json_decode( wp_remote_retrieve_body( $response ), true );
1481
		$related_posts = array();
1482
		if ( is_array( $results ) && !empty( $results['hits'] ) ) {
1483
			foreach( $results['hits'] as $hit ) {
1484
				$related_posts[] = array(
1485
					'id' => $hit['fields']['post_id'],
1486
				);
1487
			}
1488
		}
1489
1490
		// An empty array might indicate no related posts or that posts
1491
		// are not yet synced to WordPress.com, so we cache for only 1
1492
		// minute in this case
1493
		if ( empty( $related_posts ) ) {
1494
			$cache_ttl = 60;
1495
		} else {
1496
			$cache_ttl = 12 * HOUR_IN_SECONDS;
1497
		}
1498
1499
		// Update cache
1500
		if ( wp_using_ext_object_cache() ) {
1501
			set_transient( $transient_name, $related_posts, $cache_ttl );
0 ignored issues
show
Bug introduced by
The variable $transient_name does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1502
		} else {
1503
			// Copy all valid cache values
1504
			$new_cache = array();
1505
			foreach ( $cache as $k => $v ) {
1506
				if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
1507
					$new_cache[ $k ] = $v;
1508
				}
1509
			}
1510
1511
			// Set new cache value
1512
			$cache_expires = $cache_ttl + $now_ts;
1513
			$new_cache[ $cache_key ] = array(
1514
				'expires' => $cache_expires,
1515
				'payload' => $related_posts,
1516
			);
1517
			update_post_meta( $post_id, $cache_meta_key, $new_cache );
1518
		}
1519
1520
		return $related_posts;
1521
	}
1522
1523
	/**
1524
	 * Filter out any hits that are not public anymore.
1525
	 *
1526
	 * @param array $related_posts
1527
	 * @uses get_post_stati, get_post_status
1528
	 * @return array
1529
	 */
1530
	protected function _filter_non_public_posts( array $related_posts ) {
1531
		$public_stati = get_post_stati( array( 'public' => true ) );
1532
1533
		$filtered = array();
1534
		foreach ( $related_posts as $hit ) {
1535
			if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
1536
				$filtered[] = $hit;
1537
			}
1538
		}
1539
		return $filtered;
1540
	}
1541
1542
	/**
1543
	 * Generates a context for the related content (second line in related post output).
1544
	 * Order of importance:
1545
	 *   - First category (Not 'Uncategorized')
1546
	 *   - First post tag
1547
	 *   - Number of comments
1548
	 *
1549
	 * @param int $post_id
1550
	 * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
1551
	 * @return string
1552
	 */
1553
	protected function _generate_related_post_context( $post_id ) {
1554
		$categories = get_the_category( $post_id );
1555 View Code Duplication
		if ( is_array( $categories ) ) {
1556
			foreach ( $categories as $category ) {
1557
				if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
1558
					$post_cat_context = sprintf(
1559
						esc_html_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1560
						$category->name
1561
					);
1562
					/**
1563
					 * Filter the "In Category" line displayed in the post context below each Related Post.
1564
					 *
1565
					 * @module related-posts
1566
					 *
1567
					 * @since 3.2.0
1568
					 *
1569
					 * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
1570
					 * @param array $category Array containing information about the category.
1571
					 */
1572
					return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $category.

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...
1573
				}
1574
			}
1575
		}
1576
1577
		$tags = get_the_terms( $post_id, 'post_tag' );
1578 View Code Duplication
		if ( is_array( $tags ) ) {
1579
			foreach ( $tags as $tag ) {
1580
				if ( '' != trim( $tag->name ) ) {
1581
					$post_tag_context = sprintf(
1582
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1583
						$tag->name
1584
					);
1585
					/**
1586
					 * Filter the "In Tag" line displayed in the post context below each Related Post.
1587
					 *
1588
					 * @module related-posts
1589
					 *
1590
					 * @since 3.2.0
1591
					 *
1592
					 * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
1593
					 * @param array $tag Array containing information about the tag.
1594
					 */
1595
					return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $tag.

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...
1596
				}
1597
			}
1598
		}
1599
1600
		$comment_count = get_comments_number( $post_id );
1601
		if ( $comment_count > 0 ) {
1602
			return sprintf(
1603
				_n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
1604
				number_format_i18n( $comment_count )
1605
			);
1606
		}
1607
1608
		return __( 'Similar post', 'jetpack' );
1609
	}
1610
1611
	/**
1612
	 * Logs clicks for clickthrough analysis and related result tuning.
1613
	 *
1614
	 * @return null
1615
	 */
1616
	protected function _log_click( $post_id, $to_post_id, $link_position ) {
1617
1618
	}
1619
1620
	/**
1621
	 * Determines if the current post is able to use related posts.
1622
	 *
1623
	 * @uses self::get_options, is_admin, is_single, apply_filters
1624
	 * @return bool
1625
	 */
1626
	protected function _enabled_for_request() {
1627
		$enabled = is_single()
1628
			&& ! is_attachment()
1629
			&& ! is_admin()
1630
			&& ! is_embed()
1631
			&& ( ! $this->_allow_feature_toggle() || $this->get_option( 'enabled' ) );
1632
1633
		/**
1634
		 * Filter the Enabled value to allow related posts to be shown on pages as well.
1635
		 *
1636
		 * @module related-posts
1637
		 *
1638
		 * @since 3.3.0
1639
		 *
1640
		 * @param bool $enabled Should Related Posts be enabled on the current page.
1641
		 */
1642
		return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
1643
	}
1644
1645
	/**
1646
	 * Adds filters.
1647
	 *
1648
	 * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
1649
	 * @return null
1650
	 */
1651
	protected function _action_frontend_init_page() {
1652
		$this->_setup_shortcode();
1653
1654
		add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
1655
	}
1656
1657
	/**
1658
	 * Enqueues assets needed to do async loading of related posts.
1659
	 *
1660
	 * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
1661
	 * @return null
1662
	 */
1663
	protected function _enqueue_assets( $script, $style ) {
1664
		$dependencies = is_customize_preview() ? array( 'customize-base' ) : array();
1665
		if ( $script ) {
1666
			wp_enqueue_script(
1667
				'jetpack_related-posts',
1668
				Assets::get_file_url_for_environment(
1669
					'_inc/build/related-posts/related-posts.min.js',
1670
					'modules/related-posts/related-posts.js'
1671
				),
1672
				$dependencies,
1673
				self::VERSION
1674
			);
1675
			$related_posts_js_options = array(
1676
				/**
1677
				 * Filter each Related Post Heading structure.
1678
				 *
1679
				 * @since 4.0.0
1680
				 *
1681
				 * @param string $str Related Post Heading structure. Default to h4.
1682
				 */
1683
				'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
1684
			);
1685
			wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
1686
		}
1687
		if ( $style ){
1688
			wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
1689
			wp_style_add_data( 'jetpack_related-posts', 'rtl', 'replace' );
1690
			add_action( 'amp_post_template_css', array( $this, 'render_amp_reader_mode_css' ) );
1691
		}
1692
	}
1693
1694
	public function render_amp_reader_mode_css() {
1695
		echo file_get_contents( plugin_dir_path( __FILE__ ) . 'related-posts.css' );
1696
	}
1697
1698
	/**
1699
	 * Sets up the shortcode processing.
1700
	 *
1701
	 * @uses add_filter, add_shortcode
1702
	 * @return null
1703
	 */
1704
	protected function _setup_shortcode() {
1705
		add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
1706
1707
		add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html' ) );
1708
	}
1709
1710
	protected function _allow_feature_toggle() {
1711
		if ( null === $this->_allow_feature_toggle ) {
1712
			/**
1713
			 * Filter the display of the Related Posts toggle in Settings > Reading.
1714
			 *
1715
			 * @module related-posts
1716
			 *
1717
			 * @since 2.8.0
1718
			 *
1719
			 * @param bool false Display a feature toggle. Default to false.
1720
			 */
1721
			$this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
1722
		}
1723
		return $this->_allow_feature_toggle;
1724
	}
1725
1726
	/**
1727
	 * ===================================================
1728
	 * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
1729
	 * ===================================================
1730
	 */
1731
1732
	/**
1733
	 * Add Related Posts to the REST API Post response.
1734
	 *
1735
	 * @since 4.4.0
1736
	 *
1737
	 * @action rest_api_init
1738
	 * @uses register_rest_field, self::rest_get_related_posts
1739
	 * @return null
1740
	 */
1741
	public function rest_register_related_posts() {
1742
		/** This filter is already documented in class.json-api-endpoints.php */
1743
		$post_types = apply_filters( 'rest_api_allowed_post_types', array( 'post', 'page', 'revision' ) );
1744
		foreach ( $post_types as $post_type ) {
1745
			register_rest_field(
1746
				$post_type,
1747
				'jetpack-related-posts',
1748
				array(
1749
					'get_callback'    => array( $this, 'rest_get_related_posts' ),
1750
					'update_callback' => null,
1751
					'schema'          => null,
1752
				)
1753
			);
1754
		}
1755
	}
1756
1757
	/**
1758
	 * Build an array of Related Posts.
1759
	 * By default returns cached results that are stored for up to 12 hours.
1760
	 *
1761
	 * @since 4.4.0
1762
	 *
1763
	 * @param array $object Details of current post.
1764
	 * @param string $field_name Name of field.
1765
	 * @param WP_REST_Request $request Current request
1766
	 *
1767
	 * @uses self::get_for_post_id
1768
	 *
1769
	 * @return array
1770
	 */
1771
	public function rest_get_related_posts( $object, $field_name, $request ) {
1772
		return $this->get_for_post_id( $object['id'], array( 'size' => 6 ) );
1773
	}
1774
}
1775
1776
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
1777
	protected $_query_name;
1778
1779
	/**
1780
	 * Allows callers of this class to tag each query with a unique name for tracking purposes.
1781
	 *
1782
	 * @param string $name
1783
	 * @return Jetpack_RelatedPosts_Raw
1784
	 */
1785
	public function set_query_name( $name ) {
1786
		$this->_query_name = (string) $name;
1787
		return $this;
1788
	}
1789
1790
	/**
1791
	 * The raw related posts class can be used by other plugins or themes
1792
	 * to get related content. This class wraps the existing RelatedPosts
1793
	 * logic thus we never want to add anything to the DOM or do anything
1794
	 * for event hooks. We will also not present any settings for this
1795
	 * class and keep it enabled as calls to this class is done
1796
	 * programmatically.
1797
	 */
1798
	public function action_admin_init() {}
1799
	public function action_frontend_init() {}
1800
	public function get_options() {
1801
		return array(
1802
			'enabled' => true,
1803
		);
1804
	}
1805
1806
	/**
1807
	 * Workhorse method to return array of related posts ids matched by Elasticsearch.
1808
	 *
1809
	 * @param int $post_id
1810
	 * @param int $size
1811
	 * @param array $filters
1812
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
1813
	 * @return array
1814
	 */
1815
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1816
		$hits = $this->_filter_non_public_posts(
1817
			$this->_get_related_post_ids(
1818
				$post_id,
1819
				$size,
1820
				$filters
1821
			)
1822
		);
1823
1824
		/** This filter is already documented in modules/related-posts/related-posts.php */
1825
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $post_id.

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...
1826
1827
		return $hits;
1828
	}
1829
}
1830