Completed
Push — update/import-sync-detection ( 0bf98c...8808a0 )
by
unknown
25:48 queued 17:49
created

Jetpack_RelatedPosts::_enabled_for_request()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 6
nop 0
dl 0
loc 20
rs 9.2888
c 0
b 0
f 0
1
<?php
2
class Jetpack_RelatedPosts {
3
	const VERSION   = '20190204';
4
	const SHORTCODE = 'jetpack-related-posts';
5
6
	private static $instance     = null;
7
	private static $instance_raw = null;
8
9
	/**
10
	 * Creates and returns a static instance of Jetpack_RelatedPosts.
11
	 *
12
	 * @return Jetpack_RelatedPosts
13
	 */
14
	public static function init() {
15
		if ( ! self::$instance ) {
16
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
17
				self::$instance = WPCOM_RelatedPosts::init();
18
			} else {
19
				self::$instance = new Jetpack_RelatedPosts();
20
			}
21
		}
22
23
		return self::$instance;
24
	}
25
26
	/**
27
	 * Creates and returns a static instance of Jetpack_RelatedPosts_Raw.
28
	 *
29
	 * @return Jetpack_RelatedPosts
30
	 */
31
	public static function init_raw() {
32
		if ( ! self::$instance_raw ) {
33
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
34
				self::$instance_raw = WPCOM_RelatedPosts::init_raw();
35
			} else {
36
				self::$instance_raw = new Jetpack_RelatedPosts_Raw();
37
			}
38
		}
39
40
		return self::$instance_raw;
41
	}
42
43
	protected $_options;
44
	protected $_allow_feature_toggle;
45
	protected $_blog_charset;
46
	protected $_convert_charset;
47
	protected $_previous_post_id;
48
	protected $_found_shortcode = false;
49
50
	/**
51
	 * Constructor for Jetpack_RelatedPosts.
52
	 *
53
	 * @uses get_option, add_action, apply_filters
54
	 * @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...
55
	 */
56
	public function __construct() {
57
		$this->_blog_charset = get_option( 'blog_charset' );
58
		$this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
59
60
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
61
		add_action( 'wp', array( $this, 'action_frontend_init' ) );
62
63
		if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
64
			jetpack_require_lib( 'class.media-summary' );
65
		}
66
67
		// Add Related Posts to the REST API Post response.
68
		add_action( 'rest_api_init', array( $this, 'rest_register_related_posts' ) );
69
70
		jetpack_register_block(
71
			'jetpack/related-posts',
72
			array(
73
				'render_callback' => array( $this, 'render_block' ),
74
			)
75
		);
76
	}
77
78
	protected function get_blog_id() {
79
		return Jetpack_Options::get_option( 'id' );
80
	}
81
82
	/**
83
	 * =================
84
	 * ACTIONS & FILTERS
85
	 * =================
86
	 */
87
88
	/**
89
	 * Add a checkbox field to Settings > Reading for enabling related posts.
90
	 *
91
	 * @action admin_init
92
	 * @uses add_settings_field, __, register_setting, add_action
93
	 * @return null
94
	 */
95
	public function action_admin_init() {
96
97
		// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
98
		add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
99
		register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
100
		add_action('admin_head', array( $this, 'print_setting_head' ) );
101
102
		if( 'options-reading.php' == $GLOBALS['pagenow'] ) {
103
			// Enqueue style for live preview on the reading settings page
104
			$this->_enqueue_assets( false, true );
105
		}
106
	}
107
108
	/**
109
	 * Load related posts assets if it's a elegiable front end page or execute search and return JSON if it's an endpoint request.
110
	 *
111
	 * @global $_GET
112
	 * @action wp
113
	 * @uses add_shortcode, get_the_ID
114
	 * @returns null
115
	 */
116
	public function action_frontend_init() {
117
		// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
118
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html_unsupported' ) );
119
120
		if ( ! $this->_enabled_for_request() )
121
			return;
122
123
		if ( isset( $_GET['relatedposts'] ) ) {
124
			$excludes = $this->parse_numeric_get_arg( 'relatedposts_exclude' );
125
			$this->_action_frontend_init_ajax( $excludes );
126
		} else {
127
			if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
128
				$this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
129
				$this->_previous_post_id = (int) $_GET['relatedposts_origin'];
130
			}
131
132
			$this->_action_frontend_init_page();
133
		}
134
135
	}
136
137
	/**
138
	 * Render insertion point.
139
	 *
140
	 * @since 4.2.0
141
	 *
142
	 * @return string
143
	 */
144
	public function get_headline() {
145
		$options = $this->get_options();
146
147
		if ( $options['show_headline'] ) {
148
			$headline = sprintf(
149
				/** This filter is already documented in modules/sharedaddy/sharing-service.php */
150
				apply_filters( 'jetpack_sharing_headline_html', '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>', esc_html( $options['headline'] ), 'related-posts' ),
151
				esc_html( $options['headline'] )
152
			);
153
		} else {
154
			$headline = '';
155
		}
156
		return $headline;
157
	}
158
159
	/**
160
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
161
	 * Will skip adding the target if the post content contains a Related Posts block.
162
	 *
163
	 * @filter the_content
164
	 * @param string $content
165
	 * @returns string
166
	 */
167
	public function filter_add_target_to_dom( $content ) {
168
		if ( has_block( 'jetpack/related-posts', $content ) ) {
169
			return $content;
170
		}
171
172
		if ( ! $this->_found_shortcode ) {
173
			$content .= "\n" . $this->get_target_html();
174
		}
175
176
		return $content;
177
	}
178
179
	/**
180
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
181
	 *
182
	 * @filter the_content
183
	 * @param string $content
184
	 * @uses has_shortcode
185
	 * @returns string
186
	 */
187
	public function test_for_shortcode( $content ) {
188
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
189
190
		return $content;
191
	}
192
193
	/**
194
	 * Returns the HTML for the related posts section.
195
	 *
196
	 * @uses esc_html__, apply_filters
197
	 * @returns string
198
	 */
199
	public function get_target_html() {
200
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
201
		if ( Jetpack_Sync_Settings::is_syncing() ) {
202
			return '';
203
		}
204
205
		/**
206
		 * Filter the Related Posts headline.
207
		 *
208
		 * @module related-posts
209
		 *
210
		 * @since 3.0.0
211
		 *
212
		 * @param string $headline Related Posts heading.
213
		 */
214
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
215
216
		if ( $this->_previous_post_id ) {
217
			$exclude = "data-exclude='{$this->_previous_post_id}'";
218
		} else {
219
			$exclude = "";
220
		}
221
222
		return <<<EOT
223
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
224
	$headline
225
</div>
226
EOT;
227
	}
228
229
	/**
230
	 * 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.
231
	 *
232
	 * @returns string
233
	 */
234
	public function get_target_html_unsupported() {
235
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
236
		if ( Jetpack_Sync_Settings::is_syncing() ) {
237
			return '';
238
		}
239
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
240
	}
241
242
	/**
243
	 * ===============
244
	 * GUTENBERG BLOCK
245
	 * ===============
246
	 */
247
248
	/**
249
	 * Echoes out items for the Gutenberg block
250
	 *
251
	 * @param array $related_post The post oject.
252
	 * @param array $block_attributes The block attributes.
253
	 */
254
	public function render_block_item( $related_post, $block_attributes ) {
255
		$instance_id = 'related-posts-item-' . uniqid();
256
		$label_id    = $instance_id . '-label';
257
		?>
258
		<div
259
			aria-labelledby="<?php echo esc_attr( $label_id ); ?>"
260
			role="menuitem"
261
			data-post-id="<?php echo esc_attr( $related_post['id'] ); ?>"
262
			data-post-format="<?php echo esc_attr( ! empty( $related_post['format'] ) ? $related_post['format'] : 'false' ); ?>"
263
			class="jp-related-posts-i2__post"
264
			id="<?php echo esc_attr( $instance_id ); ?>"
265
		>
266
			<a
267
				href="<?php echo esc_url( $related_post['url'] ); ?>"
268
				title="<?php echo esc_attr( $related_post['title'] ); ?>"
269
				rel="<?php echo esc_attr( $related_post['rel'] ); ?>"
270
				data-origin="<?php echo esc_attr( $related_post['url_meta']['origin'] ); ?>"
271
				data-position="<?php echo esc_attr( $related_post['url_meta']['position'] ); ?>"
272
				class="jp-related-posts-i2__post-link"
273
				id="<?php echo esc_attr( $label_id ); ?>"
274
			>
275
				<?php echo esc_html( $related_post['title'] ); ?>
276
			</a>
277
			<?php if ( ! empty( $block_attributes['show_thumbnails'] ) && ! empty( $related_post['img']['src'] ) ) : ?>
278
			<a
279
				href="<?php echo esc_url( $related_post['url'] ); ?>"
280
				title="<?php echo esc_attr( $related_post['title'] ); ?>"
281
				rel="<?php echo esc_attr( $related_post['rel'] ); ?>"
282
				data-origin="<?php echo esc_attr( $related_post['url_meta']['origin'] ); ?>"
283
				data-position="<?php echo esc_attr( $related_post['url_meta']['position'] ); ?>"
284
				class="jp-related-posts-i2__post-img-link"
285
			>
286
				<img
287
					class="jp-related-posts-i2__post-img"
288
					src="<?php echo esc_url( $related_post['img']['src'] ); ?>"
289
					width="<?php echo esc_attr( $related_post['img']['width'] ); ?>"
290
					alt="<?php echo esc_attr( $related_post['title'] ); ?>"
291
				/>
292
			</a>
293
			<?php endif; ?>
294
			<?php if ( $block_attributes['show_date'] ) : ?>
295
				<div class="jp-related-posts-i2__post-date has-small-font-size">
296
					<?php echo esc_html( $related_post['date'] ); ?>
297
				</div>
298
			<?php endif; ?>
299
			<?php
300
			if (
301
				( $block_attributes['show_context'] ) &&
302
				! empty( $related_post['context'] )
303
			) :
304
				?>
305
				<div class="jp-related-posts-i2__post-context has-small-font-size">
306
					<?php echo esc_html( $related_post['context'] ); ?>
307
				</div>
308
			<?php endif; ?>
309
		</div>
310
		<?php
311
	}
312
313
	/**
314
	 * Render a related posts row.
315
	 *
316
	 * @param array $posts The posts to render into the row.
317
	 * @param array $block_attributes Block attributes.
318
	 */
319
	public function render_block_row( $posts, $block_attributes ) {
320
		?>
321
		<div
322
			class="jp-related-posts-i2__row"
323
			data-post-count="<?php echo count( $posts ); ?>"
324
		>
325
		<?php
326
		foreach ( $posts as $post ) {
327
			$this->render_block_item( $post, $block_attributes );
328
		}
329
		?>
330
		</div>
331
		<?php
332
	}
333
334
	/**
335
	 * Render the related posts markup.
336
	 *
337
	 * @param array $attributes Block attributes.
338
	 * @return string
339
	 */
340
	public function render_block( $attributes ) {
341
		$block_attributes = array(
342
			'show_thumbnails' => isset( $attributes['displayThumbnails'] ) && $attributes['displayThumbnails'],
343
			'show_date'       => isset( $attributes['displayDate'] ) ? (bool) $attributes['displayDate'] : true,
344
			'show_context'    => isset( $attributes['displayContext'] ) && $attributes['displayContext'],
345
			'layout'          => isset( $attributes['postLayout'] ) && 'list' === $attributes['postLayout'] ? $attributes['postLayout'] : 'grid',
346
			'size'            => ! empty( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 3,
347
		);
348
349
		$excludes      = $this->parse_numeric_get_arg( 'relatedposts_origin' );
350
		$related_posts = $this->get_for_post_id(
351
			get_the_ID(),
352
			array(
353
				'size'             => $block_attributes['size'],
354
				'exclude_post_ids' => $excludes,
355
			)
356
		);
357
358
		$display_lower_row = $block_attributes['size'] > 3;
359
360
		if ( empty( $related_posts ) ) {
361
			return '';
362
		}
363
364
		switch ( count( $related_posts ) ) {
365
			case 2:
366
			case 4:
367
			case 5:
368
				$top_row_end = 2;
369
				break;
370
371
			default:
372
				$top_row_end = 3;
373
				break;
374
		}
375
376
		$upper_row_posts = array_slice( $related_posts, 0, $top_row_end );
377
		$lower_row_posts = array_slice( $related_posts, $top_row_end );
378
379
		ob_start();
380
		?>
381
		<nav
382
				class="jp-relatedposts-i2"
383
				data-layout="<?php echo esc_attr( $block_attributes['layout'] ); ?>"
384
		>
385
			<?php
386
			$this->render_block_row( $upper_row_posts, $block_attributes );
387
			if ( $display_lower_row ) {
388
				$this->render_block_row( $lower_row_posts, $block_attributes );
389
			}
390
			?>
391
		</nav>
392
		<?php
393
		$html = ob_get_clean();
394
395
		$target_to_dom_priority = has_filter(
396
			'the_content',
397
			array( $this, 'filter_add_target_to_dom' )
398
		);
399
		remove_filter(
400
			'the_content',
401
			array( $this, 'filter_add_target_to_dom' ),
402
			$target_to_dom_priority
403
		);
404
405
		/*
406
		Below is a hack to get the block content to render correctly.
407
408
		This functionality should be covered in /inc/blocks.php but due to an error,
409
		this has not been fixed as of this writing.
410
411
		Alda has submitted a patch to Core in order to have this issue fixed at
412
		https://core.trac.wordpress.org/attachment/ticket/45495/do_blocks.diff and
413
		hopefully it makes to to the final RC of WP 5.1.
414
		*/
415
		$priority = has_filter( 'the_content', 'wpautop' );
416
		remove_filter( 'the_content', 'wpautop', $priority );
417
		add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
418
419
		return $html;
420
	}
421
422
	/**
423
	 * ========================
424
	 * PUBLIC UTILITY FUNCTIONS
425
	 * ========================
426
	 */
427
428
	/**
429
	 * Parse a numeric GET variable to an array of values.
430
	 *
431
	 * @since 6.9.0
432
	 *
433
	 * @uses absint
434
	 *
435
	 * @param string $arg Name of the GET variable
436
	 * @return array $result Parsed value(s)
437
	 */
438
	public function parse_numeric_get_arg( $arg ) {
439
		$result = array();
440
441
		if ( isset( $_GET[ $arg ] ) ) {
442
			if ( is_string( $_GET[ $arg ] ) ) {
443
				$result = explode( ',', $_GET[ $arg ] );
444
			} elseif ( is_array( $_GET[ $arg ] ) ) {
445
				$result = array_values( $_GET[ $arg ] );
446
			}
447
448
			$result = array_unique( array_filter( array_map( 'absint', $result ) ) );
449
		}
450
451
		return $result;
452
	}
453
454
	/**
455
	 * Gets options set for Jetpack_RelatedPosts and merge with defaults.
456
	 *
457
	 * @uses Jetpack_Options::get_option, apply_filters
458
	 * @return array
459
	 */
460
	public function get_options() {
461
		if ( null === $this->_options ) {
462
			$this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
463
			if ( ! is_array( $this->_options ) )
464
				$this->_options = array();
465
			if ( ! isset( $this->_options['enabled'] ) )
466
				$this->_options['enabled'] = true;
467
			if ( ! isset( $this->_options['show_headline'] ) )
468
				$this->_options['show_headline'] = true;
469
			if ( ! isset( $this->_options['show_thumbnails'] ) )
470
				$this->_options['show_thumbnails'] = false;
471
			if ( ! isset( $this->_options['show_date'] ) ) {
472
				$this->_options['show_date'] = true;
473
			}
474
			if ( ! isset( $this->_options['show_context'] ) ) {
475
				$this->_options['show_context'] = true;
476
			}
477
			if ( ! isset( $this->_options['layout'] ) ) {
478
				$this->_options['layout'] = 'grid';
479
			}
480
			if ( ! isset( $this->_options['headline'] ) ) {
481
				$this->_options['headline'] = esc_html__( 'Related', 'jetpack' );
482
			}
483
			if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
484
				$this->_options['size'] = 3;
485
486
			/**
487
			 * Filter Related Posts basic options.
488
			 *
489
			 * @module related-posts
490
			 *
491
			 * @since 2.8.0
492
			 *
493
			 * @param array $this->_options Array of basic Related Posts options.
494
			 */
495
			$this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
496
		}
497
498
		return $this->_options;
499
	}
500
501
	public function get_option( $option_name ) {
502
		$options = $this->get_options();
503
504
		if ( isset( $options[ $option_name ] ) ) {
505
			return $options[ $option_name ];
506
		}
507
508
		return false;
509
	}
510
511
	/**
512
	 * Parses input and returns normalized options array.
513
	 *
514
	 * @param array $input
515
	 * @uses self::get_options
516
	 * @return array
517
	 */
518
	public function parse_options( $input ) {
519
		$current = $this->get_options();
520
521
		if ( !is_array( $input ) )
522
			$input = array();
523
524
		if (
525
			! isset( $input['enabled'] )
526
			|| isset( $input['show_date'] )
527
			|| isset( $input['show_context'] )
528
			|| isset( $input['layout'] )
529
			|| isset( $input['headline'] )
530
			) {
531
			$input['enabled'] = '1';
532
		}
533
534
		if ( '1' == $input['enabled'] ) {
535
			$current['enabled'] = true;
536
			$current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
537
			$current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
538
			$current['show_date'] = ( isset( $input['show_date'] ) && '1' == $input['show_date'] );
539
			$current['show_context'] = ( isset( $input['show_context'] ) && '1' == $input['show_context'] );
540
			$current['layout'] = isset( $input['layout'] ) && in_array( $input['layout'], array( 'grid', 'list' ), true ) ? $input['layout'] : 'grid';
541
			$current['headline'] = isset( $input['headline'] ) ? $input['headline'] : esc_html__( 'Related', 'jetpack' );
542
		} else {
543
			$current['enabled'] = false;
544
		}
545
546
		if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
547
			$current['size'] = (int)$input['size'];
548
		else
549
			$current['size'] = null;
550
551
		return $current;
552
	}
553
554
	/**
555
	 * HTML for admin settings page.
556
	 *
557
	 * @uses self::get_options, checked, esc_html__
558
	 * @returns null
559
	 */
560
	public function print_setting_html() {
561
		$options = $this->get_options();
562
563
		$ui_settings_template = <<<EOT
564
<p class="description">%s</p>
565
<ul id="settings-reading-relatedposts-customize">
566
	<li>
567
		<label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
568
	</li>
569
	<li>
570
		<label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
571
	</li>
572
	<li>
573
		<label><input name="jetpack_relatedposts[show_date]" type="checkbox" value="1" %s /> %s</label>
574
	</li>
575
	<li>
576
		<label><input name="jetpack_relatedposts[show_context]" type="checkbox" value="1" %s /> %s</label>
577
	</li>
578
</ul>
579
<div id='settings-reading-relatedposts-preview'>
580
	%s
581
	<div id="jp-relatedposts" class="jp-relatedposts"></div>
582
</div>
583
EOT;
584
		$ui_settings = sprintf(
585
			$ui_settings_template,
586
			esc_html__( 'The following settings will impact all related posts on your site, except for those you created via the block editor:', 'jetpack' ),
587
			checked( $options['show_headline'], true, false ),
588
			esc_html__( 'Highlight related content with a heading', 'jetpack' ),
589
			checked( $options['show_thumbnails'], true, false ),
590
			esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
591
			checked( $options['show_date'], true, false ),
592
			esc_html__( 'Show entry date', 'jetpack' ),
593
			checked( $options['show_context'], true, false ),
594
			esc_html__( 'Show context (category or tag)', 'jetpack' ),
595
			esc_html__( 'Preview:', 'jetpack' )
596
		);
597
598
		if ( !$this->_allow_feature_toggle() ) {
599
			$template = <<<EOT
600
<input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
601
%s
602
EOT;
603
			printf(
604
				$template,
605
				$ui_settings
606
			);
607
		} else {
608
			$template = <<<EOT
609
<ul id="settings-reading-relatedposts">
610
	<li>
611
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
612
	</li>
613
	<li>
614
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
615
		%s
616
	</li>
617
</ul>
618
EOT;
619
			printf(
620
				$template,
621
				checked( $options['enabled'], false, false ),
622
				esc_html__( 'Hide related content after posts', 'jetpack' ),
623
				checked( $options['enabled'], true, false ),
624
				esc_html__( 'Show related content after posts', 'jetpack' ),
625
				$ui_settings
626
			);
627
		}
628
	}
629
630
	/**
631
	 * Head JS/CSS for admin settings page.
632
	 *
633
	 * @uses esc_html__
634
	 * @returns null
635
	 */
636
	public function print_setting_head() {
637
638
		// only dislay the Related Posts JavaScript on the Reading Settings Admin Page
639
		$current_screen =  get_current_screen();
640
641
		if ( is_null( $current_screen ) ) {
642
			return;
643
		}
644
645
		if( 'options-reading' != $current_screen->id )
646
			return;
647
648
		$related_headline = sprintf(
649
			'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
650
			esc_html__( 'Related', 'jetpack' )
651
		);
652
653
		$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
654
		$related_with_images = <<<EOT
655
<div class="jp-relatedposts-items jp-relatedposts-items-visual">
656
	<div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
657
		<a $href_params>
658
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&amp;h=200&amp;crop=1" width="350" alt="Big iPhone/iPad Update Now Available" scale="0">
659
		</a>
660
		<h4 class="jp-relatedposts-post-title">
661
			<a $href_params>Big iPhone/iPad Update Now Available</a>
662
		</h4>
663
		<p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
664
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
665
	</div>
666
	<div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
667
		<a $href_params>
668
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&amp;h=200&amp;crop=1" width="350" alt="The WordPress for Android App Gets a Big Facelift" scale="0">
669
		</a>
670
		<h4 class="jp-relatedposts-post-title">
671
			<a $href_params>The WordPress for Android App Gets a Big Facelift</a>
672
		</h4>
673
		<p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
674
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
675
	</div>
676
	<div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
677
		<a $href_params>
678
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&amp;h=200&amp;crop=1" width="350" alt="Upgrade Focus: VideoPress For Weddings" scale="0">
679
		</a>
680
		<h4 class="jp-relatedposts-post-title">
681
			<a $href_params>Upgrade Focus: VideoPress For Weddings</a>
682
		</h4>
683
		<p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
684
		<p class="jp-relatedposts-post-context">In "Upgrade"</p>
685
	</div>
686
</div>
687
EOT;
688
		$related_with_images = str_replace( "\n", '', $related_with_images );
689
		$related_without_images = <<<EOT
690
<div class="jp-relatedposts-items jp-relatedposts-items-minimal">
691
	<p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
692
		<span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
693
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
694
	</p>
695
	<p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
696
		<span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
697
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
698
	</p>
699
	<p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
700
		<span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
701
		<span class="jp-relatedposts-post-context">In "Upgrade"</span>
702
	</p>
703
</div>
704
EOT;
705
		$related_without_images = str_replace( "\n", '', $related_without_images );
706
707
		if ( $this->_allow_feature_toggle() ) {
708
			$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
709
		} else {
710
			$extra_css = '';
711
		}
712
713
		echo <<<EOT
714
<style type="text/css">
715
	#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
716
	#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
717
	$extra_css
718
</style>
719
<script type="text/javascript">
720
	jQuery( document ).ready( function($) {
721
		var update_ui = function() {
722
			var is_enabled = true;
723
			if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
724
				if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
725
					is_enabled = false;
726
				}
727
			}
728
			if ( is_enabled ) {
729
				$( '#settings-reading-relatedposts-customize' )
730
					.removeClass( 'disabled' )
731
					.find( 'input' )
732
					.attr( 'disabled', false );
733
				$( '#settings-reading-relatedposts-preview' )
734
					.removeClass( 'disabled' );
735
			} else {
736
				$( '#settings-reading-relatedposts-customize' )
737
					.addClass( 'disabled' )
738
					.find( 'input' )
739
					.attr( 'disabled', true );
740
				$( '#settings-reading-relatedposts-preview' )
741
					.addClass( 'disabled' );
742
			}
743
		};
744
745
		var update_preview = function() {
746
			var html = '';
747
			if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
748
				html += '$related_headline';
749
			}
750
			if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
751
				html += '$related_with_images';
752
			} else {
753
				html += '$related_without_images';
754
			}
755
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).html( html );
756
			if ( $( 'input[name="jetpack_relatedposts[show_date]"]:checked' ).length ) {
757
				$( '.jp-relatedposts-post-title' ).each( function() {
758
					$( this ).after( $( '<span>August 8, 2005</span>' ) );
759
				} );
760
			}
761
			if ( $( 'input[name="jetpack_relatedposts[show_context]"]:checked' ).length ) {
762
				$( '.jp-relatedposts-post-context' ).show();
763
			} else {
764
				$( '.jp-relatedposts-post-context' ).hide();
765
			}
766
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).show();
767
		};
768
769
		// Update on load
770
		update_preview();
771
		update_ui();
772
773
		// Update on change
774
		$( '#settings-reading-relatedposts-customize input' )
775
			.change( update_preview );
776
		$( '#settings-reading-relatedposts' )
777
			.find( 'input.tog' )
778
			.change( update_ui );
779
	});
780
</script>
781
EOT;
782
	}
783
784
	/**
785
	 * Gets an array of related posts that match the given post_id.
786
	 *
787
	 * @param int $post_id
788
	 * @param array $args - params to use when building Elasticsearch filters to narrow down the search domain.
789
	 * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
790
	 * @return array
791
	 */
792
	public function get_for_post_id( $post_id, array $args ) {
793
		$options = $this->get_options();
794
795
		if ( ! empty( $args['size'] ) ) {
796
			$options['size'] = $args['size'];
797
		}
798
799
		if ( 0 === (int) $post_id || empty( $options['size'] ) ) {
800
			return array();
801
		}
802
803
		$defaults = array(
804
			'size' => (int)$options['size'],
805
			'post_type' => get_post_type( $post_id ),
806
			'post_formats' => array(),
807
			'has_terms' => array(),
808
			'date_range' => array(),
809
			'exclude_post_ids' => array(),
810
		);
811
		$args = wp_parse_args( $args, $defaults );
812
		/**
813
		 * Filter the arguments used to retrieve a list of Related Posts.
814
		 *
815
		 * @module related-posts
816
		 *
817
		 * @since 2.8.0
818
		 *
819
		 * @param array $args Array of options to retrieve Related Posts.
820
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
821
		 */
822
		$args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
823
824
		$filters = $this->_get_es_filters_from_args( $post_id, $args );
825
		/**
826
		 * Filter Elasticsearch options used to calculate Related Posts.
827
		 *
828
		 * @module related-posts
829
		 *
830
		 * @since 2.8.0
831
		 *
832
		 * @param array $filters Array of Elasticsearch filters based on the post_id and args.
833
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
834
		 */
835
		$filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
836
837
		$results = $this->_get_related_posts( $post_id, $args['size'], $filters );
838
		/**
839
		 * Filter the array of related posts matched by Elasticsearch.
840
		 *
841
		 * @module related-posts
842
		 *
843
		 * @since 2.8.0
844
		 *
845
		 * @param array $results Array of related posts matched by Elasticsearch.
846
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
847
		 */
848
		return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
849
	}
850
851
	/**
852
	 * =========================
853
	 * PRIVATE UTILITY FUNCTIONS
854
	 * =========================
855
	 */
856
857
	/**
858
	 * Creates an array of Elasticsearch filters based on the post_id and args.
859
	 *
860
	 * @param int $post_id
861
	 * @param array $args
862
	 * @uses apply_filters, get_post_types, get_post_format_strings
863
	 * @return array
864
	 */
865
	protected function _get_es_filters_from_args( $post_id, array $args ) {
866
		$filters = array();
867
868
		/**
869
		 * Filter the terms used to search for Related Posts.
870
		 *
871
		 * @module related-posts
872
		 *
873
		 * @since 2.8.0
874
		 *
875
		 * @param array $args['has_terms'] Array of terms associated to the Related Posts.
876
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
877
		 */
878
		$args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
879
		if ( ! empty( $args['has_terms'] ) ) {
880
			foreach( (array)$args['has_terms'] as $term ) {
881
				if ( mb_strlen( $term->taxonomy ) ) {
882 View Code Duplication
					switch ( $term->taxonomy ) {
883
						case 'post_tag':
884
							$tax_fld = 'tag.slug';
885
							break;
886
						case 'category':
887
							$tax_fld = 'category.slug';
888
							break;
889
						default:
890
							$tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
891
							break;
892
					}
893
					$filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
894
				}
895
			}
896
		}
897
898
		/**
899
		 * Filter the Post Types where we search Related Posts.
900
		 *
901
		 * @module related-posts
902
		 *
903
		 * @since 2.8.0
904
		 *
905
		 * @param array $args['post_type'] Array of Post Types.
906
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
907
		 */
908
		$args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
909
		$valid_post_types = get_post_types();
910
		if ( is_array( $args['post_type'] ) ) {
911
			$sanitized_post_types = array();
912
			foreach ( $args['post_type'] as $pt ) {
913
				if ( in_array( $pt, $valid_post_types ) )
914
					$sanitized_post_types[] = $pt;
915
			}
916
			if ( ! empty( $sanitized_post_types ) )
917
				$filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
918
		} else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
919
			$filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
920
		}
921
922
		/**
923
		 * Filter the Post Formats where we search Related Posts.
924
		 *
925
		 * @module related-posts
926
		 *
927
		 * @since 3.3.0
928
		 *
929
		 * @param array $args['post_formats'] Array of Post Formats.
930
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
931
		 */
932
		$args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
933
		$valid_post_formats = get_post_format_strings();
934
		$sanitized_post_formats = array();
935
		foreach ( $args['post_formats'] as $pf ) {
936
			if ( array_key_exists( $pf, $valid_post_formats ) ) {
937
				$sanitized_post_formats[] = $pf;
938
			}
939
		}
940
		if ( ! empty( $sanitized_post_formats ) ) {
941
			$filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
942
		}
943
944
		/**
945
		 * Filter the date range used to search Related Posts.
946
		 *
947
		 * @module related-posts
948
		 *
949
		 * @since 2.8.0
950
		 *
951
		 * @param array $args['date_range'] Array of a month interval where we search Related Posts.
952
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
953
		 */
954
		$args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
955
		if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
956
			$args['date_range'] = array_map( 'intval', $args['date_range'] );
957
			if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
958
				$filters[] = array(
959
					'range' => array(
960
						'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
961
					)
962
				);
963
			}
964
		}
965
966
		/**
967
		 * Filter the Post IDs excluded from appearing in Related Posts.
968
		 *
969
		 * @module related-posts
970
		 *
971
		 * @since 2.9.0
972
		 *
973
		 * @param array $args['exclude_post_ids'] Array of Post IDs.
974
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
975
		 */
976
		$args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
977
		if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
978
			$excluded_post_ids = array();
979
			foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
980
				$exclude_post_id = (int)$exclude_post_id;
981
				if ( $exclude_post_id > 0 )
982
					$excluded_post_ids[] = $exclude_post_id;
983
			}
984
			$filters[] = array( 'not' => array( 'terms' => array( 'post_id' => $excluded_post_ids ) ) );
985
		}
986
987
		return $filters;
988
	}
989
990
	/**
991
	 * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
992
	 *
993
	 * @param array $date_range
994
	 * @return array
995
	 */
996
	protected function _get_coalesced_range( array $date_range ) {
997
		$now = time();
998
		$coalesce_time = $this->get_blog_id() % 86400;
999
		$current_time = $now - strtotime( 'today', $now );
1000
1001
		if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
1002
			// Move back 1 period
1003
			return array(
1004
				'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1005
				'to'   => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1006
			);
1007
		} else {
1008
			// Use current period
1009
			return array(
1010
				'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1011
				'to'   => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1012
			);
1013
		}
1014
	}
1015
1016
	/**
1017
	 * Generate and output ajax response for related posts API call.
1018
	 * NOTE: Calls exit() to end all further processing after payload has been outputed.
1019
	 *
1020
	 * @param array $excludes array of post_ids to exclude
1021
	 * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
1022
	 * @return null
1023
	 */
1024
	protected function _action_frontend_init_ajax( array $excludes ) {
1025
		define( 'DOING_AJAX', true );
1026
1027
		header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
1028
		send_nosniff_header();
1029
1030
		$options = $this->get_options();
1031
1032
		if ( isset( $_GET['jetpackrpcustomize'] ) ) {
1033
1034
			// If we're in the customizer, add dummy content.
1035
			$date_now = current_time( get_option( 'date_format' ) );
1036
			$related_posts = array(
1037
				array(
1038
					'id'       => - 1,
1039
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&h=200&crop=1',
1040
					'url_meta' => array(
1041
						'origin'   => 0,
1042
						'position' => 0
1043
					),
1044
					'title'    => esc_html__( 'Big iPhone/iPad Update Now Available', 'jetpack' ),
1045
					'date'     => $date_now,
1046
					'format'   => false,
1047
					'excerpt'  => esc_html__( 'It is that time of the year when devices are shiny again.', 'jetpack' ),
1048
					'rel'      => 'nofollow',
1049
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1050
					'img'      => array(
1051
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&h=200&crop=1',
1052
						'width'  => 350,
1053
						'height' => 200
1054
					),
1055
					'classes'  => array()
1056
				),
1057
				array(
1058
					'id'       => - 1,
1059
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&h=200&crop=1',
1060
					'url_meta' => array(
1061
						'origin'   => 0,
1062
						'position' => 0
1063
					),
1064
					'title'    => esc_html__( 'The WordPress for Android App Gets a Big Facelift', 'jetpack' ),
1065
					'date'     => $date_now,
1066
					'format'   => false,
1067
					'excerpt'  => esc_html__( 'Writing is new again in Android with the new WordPress app.', 'jetpack' ),
1068
					'rel'      => 'nofollow',
1069
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1070
					'img'      => array(
1071
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&h=200&crop=1',
1072
						'width'  => 350,
1073
						'height' => 200
1074
					),
1075
					'classes'  => array()
1076
				),
1077
				array(
1078
					'id'       => - 1,
1079
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&h=200&crop=1',
1080
					'url_meta' => array(
1081
						'origin'   => 0,
1082
						'position' => 0
1083
					),
1084
					'title'    => esc_html__( 'Upgrade Focus, VideoPress for weddings', 'jetpack' ),
1085
					'date'     => $date_now,
1086
					'format'   => false,
1087
					'excerpt'  => esc_html__( 'Weddings are in the spotlight now with VideoPress for weddings.', 'jetpack' ),
1088
					'rel'      => 'nofollow',
1089
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1090
					'img'      => array(
1091
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&h=200&crop=1',
1092
						'width'  => 350,
1093
						'height' => 200
1094
					),
1095
					'classes'  => array()
1096
				),
1097
			);
1098
1099
			for ( $total = 0; $total < $options['size'] - 3; $total++ ) {
1100
				$related_posts[] = $related_posts[ $total ];
1101
			}
1102
1103
			$current_post = get_post();
1104
1105
			// Exclude current post after filtering to make sure it's excluded and not lost during filtering.
1106
			$excluded_posts = array_merge(
1107
				/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
1108
				apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', array() ),
1109
				array( $current_post->ID )
1110
			);
1111
1112
			// Fetch posts with featured image.
1113
			$with_post_thumbnails = get_posts( array(
1114
				'posts_per_page'   => $options['size'],
1115
				'post__not_in'     => $excluded_posts,
1116
				'post_type'        => $current_post->post_type,
1117
				'meta_key'         => '_thumbnail_id',
1118
				'suppress_filters' => false,
1119
			) );
1120
1121
			// If we don't have enough, fetch posts without featured image.
1122
			if ( 0 < ( $more = $options['size'] - count( $with_post_thumbnails ) ) ) {
1123
				$no_post_thumbnails = get_posts( array(
1124
					'posts_per_page'  => $more,
1125
					'post__not_in'    => $excluded_posts,
1126
					'post_type'       => $current_post->post_type,
1127
					'meta_query' => array(
1128
						array(
1129
							'key'     => '_thumbnail_id',
1130
							'compare' => 'NOT EXISTS',
1131
						),
1132
					),
1133
					'suppress_filters' => false,
1134
				) );
1135
			} else {
1136
				$no_post_thumbnails = array();
1137
			}
1138
1139
			foreach ( array_merge( $with_post_thumbnails, $no_post_thumbnails ) as $index => $real_post ) {
1140
				$related_posts[ $index ]['id']      = $real_post->ID;
1141
				$related_posts[ $index ]['url']     = esc_url( get_permalink( $real_post ) );
1142
				$related_posts[ $index ]['title']   = $this->_to_utf8( $this->_get_title( $real_post->post_title, $real_post->post_content ) );
1143
				$related_posts[ $index ]['date']    = get_the_date( '', $real_post );
1144
				$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' );
1145
				$related_posts[ $index ]['img']     = $this->_generate_related_post_image_params( $real_post->ID );
1146
				$related_posts[ $index ]['context'] = $this->_generate_related_post_context( $real_post->ID );
1147
			}
1148
		} else {
1149
			$related_posts = $this->get_for_post_id(
1150
				get_the_ID(),
1151
				array(
1152
					'exclude_post_ids' => $excludes,
1153
				)
1154
			);
1155
		}
1156
1157
		$response = array(
1158
			'version' => self::VERSION,
1159
			'show_thumbnails' => (bool) $options['show_thumbnails'],
1160
			'show_date' => (bool) $options['show_date'],
1161
			'show_context' => (bool) $options['show_context'],
1162
			'layout' => (string) $options['layout'],
1163
			'headline' => (string) $options['headline'],
1164
			'items' => array(),
1165
		);
1166
1167
		if ( count( $related_posts ) == $options['size'] )
1168
			$response['items'] = $related_posts;
1169
1170
		echo json_encode( $response );
1171
1172
		exit();
1173
	}
1174
1175
	/**
1176
	 * Returns a UTF-8 encoded array of post information for the given post_id
1177
	 *
1178
	 * @param int $post_id
1179
	 * @param int $position
1180
	 * @param int $origin The post id that this is related to
1181
	 * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
1182
	 * @return array
1183
	 */
1184
	public function get_related_post_data_for_post( $post_id, $position, $origin ) {
1185
		$post = get_post( $post_id );
1186
1187
		return array(
1188
			'id' => $post->ID,
1189
			'url' => get_permalink( $post->ID ),
1190
			'url_meta' => array( 'origin' => $origin, 'position' => $position ),
1191
			'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),
1192
			'date' => get_the_date( '', $post->ID ),
1193
			'format' => get_post_format( $post->ID ),
1194
			'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
1195
			/**
1196
			 * Filters the rel attribute for the Related Posts' links.
1197
			 *
1198
			 * @module related-posts
1199
			 *
1200
			 * @since 3.7.0
1201
			 *
1202
			 * @param string nofollow Link rel attribute for Related Posts' link. Default is nofollow.
1203
			 * @param int $post->ID Post ID.
1204
			 */
1205
			'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', 'nofollow', $post->ID ),
1206
			/**
1207
			 * Filter the context displayed below each Related Post.
1208
			 *
1209
			 * @module related-posts
1210
			 *
1211
			 * @since 3.0.0
1212
			 *
1213
			 * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
1214
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1215
			 */
1216
			'context' => apply_filters(
1217
				'jetpack_relatedposts_filter_post_context',
1218
				$this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
1219
				$post->ID
1220
			),
1221
			'img' => $this->_generate_related_post_image_params( $post->ID ),
1222
			/**
1223
			 * Filter the post css classes added on HTML markup.
1224
			 *
1225
			 * @module related-posts
1226
			 *
1227
			 * @since 3.8.0
1228
			 *
1229
			 * @param array array() CSS classes added on post HTML markup.
1230
			 * @param string $post_id Post ID.
1231
			 */
1232
			'classes' => apply_filters(
1233
				'jetpack_relatedposts_filter_post_css_classes',
1234
				array(),
1235
				$post->ID
1236
			),
1237
		);
1238
	}
1239
1240
	/**
1241
	 * Returns either the title or a small excerpt to use as title for post.
1242
	 *
1243
	 * @param string $post_title
1244
	 * @param string $post_content
1245
	 * @uses strip_shortcodes, wp_trim_words, __
1246
	 * @return string
1247
	 */
1248
	protected function _get_title( $post_title, $post_content ) {
1249
		if ( ! empty( $post_title ) ) {
1250
			return wp_strip_all_tags( $post_title );
1251
		}
1252
1253
		$post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
1254
		if ( ! empty( $post_title ) ) {
1255
			return $post_title;
1256
		}
1257
1258
		return __( 'Untitled Post', 'jetpack' );
1259
	}
1260
1261
	/**
1262
	 * Returns a plain text post excerpt for title attribute of links.
1263
	 *
1264
	 * @param string $post_excerpt
1265
	 * @param string $post_content
1266
	 * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
1267
	 * @return string
1268
	 */
1269
	protected function _get_excerpt( $post_excerpt, $post_content ) {
1270
		if ( empty( $post_excerpt ) )
1271
			$excerpt = $post_content;
1272
		else
1273
			$excerpt = $post_excerpt;
1274
1275
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
1276
	}
1277
1278
	/**
1279
	 * Generates the thumbnail image to be used for the post. Uses the
1280
	 * image as returned by Jetpack_PostImages::get_image()
1281
	 *
1282
	 * @param int $post_id
1283
	 * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
1284
	 * @return string
1285
	 */
1286
	protected function _generate_related_post_image_params( $post_id ) {
1287
		$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...
1288
		$image_params = array(
1289
			'src' => '',
1290
			'width' => 0,
1291
			'height' => 0,
1292
		);
1293
1294
		/**
1295
		 * Filter the size of the Related Posts images.
1296
		 *
1297
		 * @module related-posts
1298
		 *
1299
		 * @since 2.8.0
1300
		 *
1301
		 * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
1302
		 */
1303
		$thumbnail_size = apply_filters(
1304
			'jetpack_relatedposts_filter_thumbnail_size',
1305
			array( 'width' => 350, 'height' => 200 )
1306
		);
1307
		if ( !is_array( $thumbnail_size ) ) {
1308
			$thumbnail_size = array(
1309
				'width' => (int)$thumbnail_size,
1310
				'height' => (int)$thumbnail_size
1311
			);
1312
		}
1313
1314
		// Try to get post image
1315
		if ( class_exists( 'Jetpack_PostImages' ) ) {
1316
			$img_url = '';
1317
			$post_image = Jetpack_PostImages::get_image(
1318
				$post_id,
1319
				$thumbnail_size
1320
			);
1321
1322
			if ( is_array($post_image) ) {
1323
				$img_url = $post_image['src'];
1324
			} elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
1325
				$media = Jetpack_Media_Summary::get( $post_id );
1326
1327
				if ( is_array($media) && !empty( $media['image'] ) ) {
1328
					$img_url = $media['image'];
1329
				}
1330
			}
1331
1332
			if ( !empty( $img_url ) ) {
1333
				$image_params['width'] = $thumbnail_size['width'];
1334
				$image_params['height'] = $thumbnail_size['height'];
1335
				$image_params['src'] = Jetpack_PostImages::fit_image_url(
1336
					$img_url,
1337
					$thumbnail_size['width'],
1338
					$thumbnail_size['height']
1339
				);
1340
			}
1341
		}
1342
1343
		return $image_params;
1344
	}
1345
1346
	/**
1347
	 * Returns the string UTF-8 encoded
1348
	 *
1349
	 * @param string $text
1350
	 * @return string
1351
	 */
1352
	protected function _to_utf8( $text ) {
1353
		if ( $this->_convert_charset ) {
1354
			return iconv( $this->_blog_charset, 'UTF-8', $text );
1355
		} else {
1356
			return $text;
1357
		}
1358
	}
1359
1360
	/**
1361
	 * =============================================
1362
	 * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
1363
	 * =============================================
1364
	 */
1365
1366
	/**
1367
	 * Workhorse method to return array of related posts matched by Elasticsearch.
1368
	 *
1369
	 * @param int $post_id
1370
	 * @param int $size
1371
	 * @param array $filters
1372
	 * @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
1373
	 * @return array
1374
	 */
1375
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1376
		$hits = $this->_filter_non_public_posts(
1377
			$this->_get_related_post_ids(
1378
				$post_id,
1379
				$size,
1380
				$filters
1381
			)
1382
		);
1383
1384
		/**
1385
		 * Filter the Related Posts matched by Elasticsearch.
1386
		 *
1387
		 * @module related-posts
1388
		 *
1389
		 * @since 2.9.0
1390
		 *
1391
		 * @param array $hits Array of Post IDs matched by Elasticsearch.
1392
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1393
		 */
1394
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1395
1396
		$related_posts = array();
1397
		foreach ( $hits as $i => $hit ) {
1398
			$related_posts[] = $this->get_related_post_data_for_post( $hit['id'], $i, $post_id );
1399
		}
1400
		return $related_posts;
1401
	}
1402
1403
	/**
1404
	 * Get array of related posts matched by Elasticsearch.
1405
	 *
1406
	 * @param int $post_id
1407
	 * @param int $size
1408
	 * @param array $filters
1409
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
1410
	 * @return array
1411
	 */
1412
	protected function _get_related_post_ids( $post_id, $size, array $filters ) {
1413
		$now_ts = time();
1414
		$cache_meta_key = '_jetpack_related_posts_cache';
1415
1416
		$body = array(
1417
			'size' => (int) $size,
1418
		);
1419
1420
		if ( !empty( $filters ) )
1421
			$body['filter'] = array( 'and' => $filters );
1422
1423
		// Build cache key
1424
		$cache_key = md5( serialize( $body ) );
1425
1426
		// Load all cached values
1427
		if ( wp_using_ext_object_cache() ) {
1428
			$transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
1429
			$cache = get_transient( $transient_name );
1430
			if ( false !== $cache ) {
1431
				return $cache;
1432
			}
1433
		} else {
1434
			$cache = get_post_meta( $post_id, $cache_meta_key, true );
1435
1436
			if ( empty( $cache ) )
1437
				$cache = array();
1438
1439
1440
			// Cache is valid! Return cached value.
1441
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
1442
				return $cache[ $cache_key ][ 'payload' ];
1443
			}
1444
		}
1445
1446
		$response = wp_remote_post(
1447
			"https://public-api.wordpress.com/rest/v1/sites/{$this->get_blog_id()}/posts/$post_id/related/",
1448
			array(
1449
				'timeout' => 10,
1450
				'user-agent' => 'jetpack_related_posts',
1451
				'sslverify' => true,
1452
				'body' => $body,
1453
			)
1454
		);
1455
1456
		// Oh no... return nothing don't cache errors.
1457
		if ( is_wp_error( $response ) ) {
1458
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
1459
				return $cache[ $cache_key ][ 'payload' ]; // return stale
1460
			else
1461
				return array();
1462
		}
1463
1464
		$results = json_decode( wp_remote_retrieve_body( $response ), true );
1465
		$related_posts = array();
1466
		if ( is_array( $results ) && !empty( $results['hits'] ) ) {
1467
			foreach( $results['hits'] as $hit ) {
1468
				$related_posts[] = array(
1469
					'id' => $hit['fields']['post_id'],
1470
				);
1471
			}
1472
		}
1473
1474
		// An empty array might indicate no related posts or that posts
1475
		// are not yet synced to WordPress.com, so we cache for only 1
1476
		// minute in this case
1477
		if ( empty( $related_posts ) ) {
1478
			$cache_ttl = 60;
1479
		} else {
1480
			$cache_ttl = 12 * HOUR_IN_SECONDS;
1481
		}
1482
1483
		// Update cache
1484
		if ( wp_using_ext_object_cache() ) {
1485
			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...
1486
		} else {
1487
			// Copy all valid cache values
1488
			$new_cache = array();
1489
			foreach ( $cache as $k => $v ) {
1490
				if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
1491
					$new_cache[ $k ] = $v;
1492
				}
1493
			}
1494
1495
			// Set new cache value
1496
			$cache_expires = $cache_ttl + $now_ts;
1497
			$new_cache[ $cache_key ] = array(
1498
				'expires' => $cache_expires,
1499
				'payload' => $related_posts,
1500
			);
1501
			update_post_meta( $post_id, $cache_meta_key, $new_cache );
1502
		}
1503
1504
		return $related_posts;
1505
	}
1506
1507
	/**
1508
	 * Filter out any hits that are not public anymore.
1509
	 *
1510
	 * @param array $related_posts
1511
	 * @uses get_post_stati, get_post_status
1512
	 * @return array
1513
	 */
1514
	protected function _filter_non_public_posts( array $related_posts ) {
1515
		$public_stati = get_post_stati( array( 'public' => true ) );
1516
1517
		$filtered = array();
1518
		foreach ( $related_posts as $hit ) {
1519
			if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
1520
				$filtered[] = $hit;
1521
			}
1522
		}
1523
		return $filtered;
1524
	}
1525
1526
	/**
1527
	 * Generates a context for the related content (second line in related post output).
1528
	 * Order of importance:
1529
	 *   - First category (Not 'Uncategorized')
1530
	 *   - First post tag
1531
	 *   - Number of comments
1532
	 *
1533
	 * @param int $post_id
1534
	 * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
1535
	 * @return string
1536
	 */
1537
	protected function _generate_related_post_context( $post_id ) {
1538
		$categories = get_the_category( $post_id );
1539
		if ( is_array( $categories ) ) {
1540
			foreach ( $categories as $category ) {
1541
				if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
1542
					$post_cat_context = sprintf(
1543
						esc_html_x( 'In “%s”', 'in {category/tag name}', 'jetpack' ),
1544
						$category->name
1545
					);
1546
					/**
1547
					 * Filter the "In Category" line displayed in the post context below each Related Post.
1548
					 *
1549
					 * @module related-posts
1550
					 *
1551
					 * @since 3.2.0
1552
					 *
1553
					 * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
1554
					 * @param array $category Array containing information about the category.
1555
					 */
1556
					return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
1557
				}
1558
			}
1559
		}
1560
1561
		$tags = get_the_terms( $post_id, 'post_tag' );
1562
		if ( is_array( $tags ) ) {
1563
			foreach ( $tags as $tag ) {
1564
				if ( '' != trim( $tag->name ) ) {
1565
					$post_tag_context = sprintf(
1566
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1567
						$tag->name
1568
					);
1569
					/**
1570
					 * Filter the "In Tag" line displayed in the post context below each Related Post.
1571
					 *
1572
					 * @module related-posts
1573
					 *
1574
					 * @since 3.2.0
1575
					 *
1576
					 * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
1577
					 * @param array $tag Array containing information about the tag.
1578
					 */
1579
					return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
1580
				}
1581
			}
1582
		}
1583
1584
		$comment_count = get_comments_number( $post_id );
1585
		if ( $comment_count > 0 ) {
1586
			return sprintf(
1587
				_n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
1588
				number_format_i18n( $comment_count )
1589
			);
1590
		}
1591
1592
		return __( 'Similar post', 'jetpack' );
1593
	}
1594
1595
	/**
1596
	 * Logs clicks for clickthrough analysis and related result tuning.
1597
	 *
1598
	 * @return null
1599
	 */
1600
	protected function _log_click( $post_id, $to_post_id, $link_position ) {
1601
1602
	}
1603
1604
	/**
1605
	 * Determines if the current post is able to use related posts.
1606
	 *
1607
	 * @uses self::get_options, is_admin, is_single, apply_filters
1608
	 * @return bool
1609
	 */
1610
	protected function _enabled_for_request() {
1611
		$enabled = is_single()
1612
			&&
1613
				! is_admin()
1614
			&&
1615
				( !$this->_allow_feature_toggle() || $this->get_option( 'enabled' ) )
1616
			&&
1617
				! Jetpack_AMP_Support::is_amp_request();
1618
1619
		/**
1620
		 * Filter the Enabled value to allow related posts to be shown on pages as well.
1621
		 *
1622
		 * @module related-posts
1623
		 *
1624
		 * @since 3.3.0
1625
		 *
1626
		 * @param bool $enabled Should Related Posts be enabled on the current page.
1627
		 */
1628
		return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
1629
	}
1630
1631
	/**
1632
	 * Adds filters and enqueues assets.
1633
	 *
1634
	 * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
1635
	 * @return null
1636
	 */
1637
	protected function _action_frontend_init_page() {
1638
		$this->_enqueue_assets( true, true );
1639
		$this->_setup_shortcode();
1640
1641
		add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
1642
	}
1643
1644
	/**
1645
	 * Enqueues assets needed to do async loading of related posts.
1646
	 *
1647
	 * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
1648
	 * @return null
1649
	 */
1650
	protected function _enqueue_assets( $script, $style ) {
1651
		$dependencies = is_customize_preview() ? array( 'customize-base' ) : array( 'jquery' );
1652
		if ( $script ) {
1653
			wp_enqueue_script(
1654
				'jetpack_related-posts',
1655
				Jetpack::get_file_url_for_environment(
1656
					'_inc/build/related-posts/related-posts.min.js',
1657
					'modules/related-posts/related-posts.js'
1658
				),
1659
				$dependencies,
1660
				self::VERSION
1661
			);
1662
			$related_posts_js_options = array(
1663
				/**
1664
				 * Filter each Related Post Heading structure.
1665
				 *
1666
				 * @since 4.0.0
1667
				 *
1668
				 * @param string $str Related Post Heading structure. Default to h4.
1669
				 */
1670
				'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
1671
			);
1672
			wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
1673
		}
1674
		if ( $style ){
1675
			wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
1676
			wp_style_add_data( 'jetpack_related-posts', 'rtl', 'replace' );
1677
		}
1678
	}
1679
1680
	/**
1681
	 * Sets up the shortcode processing.
1682
	 *
1683
	 * @uses add_filter, add_shortcode
1684
	 * @return null
1685
	 */
1686
	protected function _setup_shortcode() {
1687
		add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
1688
1689
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html' ) );
1690
	}
1691
1692
	protected function _allow_feature_toggle() {
1693
		if ( null === $this->_allow_feature_toggle ) {
1694
			/**
1695
			 * Filter the display of the Related Posts toggle in Settings > Reading.
1696
			 *
1697
			 * @module related-posts
1698
			 *
1699
			 * @since 2.8.0
1700
			 *
1701
			 * @param bool false Display a feature toggle. Default to false.
1702
			 */
1703
			$this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
1704
		}
1705
		return $this->_allow_feature_toggle;
1706
	}
1707
1708
	/**
1709
	 * ===================================================
1710
	 * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
1711
	 * ===================================================
1712
	 */
1713
1714
	/**
1715
	 * Add Related Posts to the REST API Post response.
1716
	 *
1717
	 * @since 4.4.0
1718
	 *
1719
	 * @action rest_api_init
1720
	 * @uses register_rest_field, self::rest_get_related_posts
1721
	 * @return null
1722
	 */
1723
	public function rest_register_related_posts() {
1724
		register_rest_field( 'post',
1725
			'jetpack-related-posts',
1726
			array(
1727
				'get_callback' => array( $this, 'rest_get_related_posts' ),
1728
				'update_callback' => null,
1729
				'schema'          => null,
1730
			)
1731
		);
1732
	}
1733
1734
	/**
1735
	 * Build an array of Related Posts.
1736
	 * By default returns cached results that are stored for up to 12 hours.
1737
	 *
1738
	 * @since 4.4.0
1739
	 *
1740
	 * @param array $object Details of current post.
1741
	 * @param string $field_name Name of field.
1742
	 * @param WP_REST_Request $request Current request
1743
	 *
1744
	 * @uses self::get_for_post_id
1745
	 *
1746
	 * @return array
1747
	 */
1748
	public function rest_get_related_posts( $object, $field_name, $request ) {
1749
		return $this->get_for_post_id( $object['id'], array( 'size' => 6 ) );
1750
	}
1751
}
1752
1753
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
1754
	protected $_query_name;
1755
1756
	/**
1757
	 * Allows callers of this class to tag each query with a unique name for tracking purposes.
1758
	 *
1759
	 * @param string $name
1760
	 * @return Jetpack_RelatedPosts_Raw
1761
	 */
1762
	public function set_query_name( $name ) {
1763
		$this->_query_name = (string) $name;
1764
		return $this;
1765
	}
1766
1767
	/**
1768
	 * The raw related posts class can be used by other plugins or themes
1769
	 * to get related content. This class wraps the existing RelatedPosts
1770
	 * logic thus we never want to add anything to the DOM or do anything
1771
	 * for event hooks. We will also not present any settings for this
1772
	 * class and keep it enabled as calls to this class is done
1773
	 * programmatically.
1774
	 */
1775
	public function action_admin_init() {}
1776
	public function action_frontend_init() {}
1777
	public function get_options() {
1778
		return array(
1779
			'enabled' => true,
1780
		);
1781
	}
1782
1783
	/**
1784
	 * Workhorse method to return array of related posts ids matched by Elasticsearch.
1785
	 *
1786
	 * @param int $post_id
1787
	 * @param int $size
1788
	 * @param array $filters
1789
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
1790
	 * @return array
1791
	 */
1792
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1793
		$hits = $this->_filter_non_public_posts(
1794
			$this->_get_related_post_ids(
1795
				$post_id,
1796
				$size,
1797
				$filters
1798
			)
1799
		);
1800
1801
		/** This filter is already documented in modules/related-posts/related-posts.php */
1802
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1803
1804
		return $hits;
1805
	}
1806
}
1807