Completed
Push — sync/roccotrip/D23362-code-154... ( 69d1f1 )
by
unknown
06:37
created

Jetpack_RelatedPosts::init_raw()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 0
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
1
<?php
2
class Jetpack_RelatedPosts {
3
	const VERSION = '20181228';
4
	const SHORTCODE = 'jetpack-related-posts';
5
	private static $instance = null;
6
	private static $instance_raw = null;
7
8
	/**
9
	 * Creates and returns a static instance of Jetpack_RelatedPosts.
10
	 *
11
	 * @return Jetpack_RelatedPosts
12
	 */
13
	public static function init() {
14
		if ( ! self::$instance ) {
15
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
16
				self::$instance = WPCOM_RelatedPosts::init();
17
			} else {
18
				self::$instance = new Jetpack_RelatedPosts();
19
			}
20
		}
21
22
		return self::$instance;
23
	}
24
25
	/**
26
	 * Creates and returns a static instance of Jetpack_RelatedPosts_Raw.
27
	 *
28
	 * @return Jetpack_RelatedPosts
29
	 */
30
	public static function init_raw() {
31
		if ( ! self::$instance_raw ) {
32
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
33
				self::$instance_raw = WPCOM_RelatedPosts::init_raw();
34
			} else {
35
				self::$instance_raw = new Jetpack_RelatedPosts_Raw();
36
			}
37
		}
38
39
		return self::$instance_raw;
40
	}
41
42
	protected $_options;
43
	protected $_allow_feature_toggle;
44
	protected $_blog_charset;
45
	protected $_convert_charset;
46
	protected $_previous_post_id;
47
	protected $_found_shortcode = false;
48
49
	/**
50
	 * Constructor for Jetpack_RelatedPosts.
51
	 *
52
	 * @uses get_option, add_action, apply_filters
53
	 * @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...
54
	 */
55
	public function __construct() {
56
		$this->_blog_charset = get_option( 'blog_charset' );
57
		$this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
58
59
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
60
		add_action( 'wp', array( $this, 'action_frontend_init' ) );
61
62
		if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
63
			jetpack_require_lib( 'class.media-summary' );
64
		}
65
66
		// Add Related Posts to the REST API Post response.
67
		if ( function_exists( 'register_rest_field' ) ) {
68
			add_action( 'rest_api_init', array( $this, 'rest_register_related_posts' ) );
69
		}
70
71
		jetpack_register_block(
72
			'related-posts',
73
			array(
74
				'render_callback' => array( $this, 'render_block' ),
75
			)
76
		);
77
	}
78
79
	protected function get_blog_id() {
80
		return Jetpack_Options::get_option( 'id' );
81
	}
82
83
	/**
84
	 * =================
85
	 * ACTIONS & FILTERS
86
	 * =================
87
	 */
88
89
	/**
90
	 * Add a checkbox field to Settings > Reading for enabling related posts.
91
	 *
92
	 * @action admin_init
93
	 * @uses add_settings_field, __, register_setting, add_action
94
	 * @return null
95
	 */
96
	public function action_admin_init() {
97
98
		// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
99
		add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
100
		register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
101
		add_action('admin_head', array( $this, 'print_setting_head' ) );
102
103
		if( 'options-reading.php' == $GLOBALS['pagenow'] ) {
104
			// Enqueue style for live preview on the reading settings page
105
			$this->_enqueue_assets( false, true );
106
		}
107
	}
108
109
	/**
110
	 * Load related posts assets if it's a elegiable front end page or execute search and return JSON if it's an endpoint request.
111
	 *
112
	 * @global $_GET
113
	 * @action wp
114
	 * @uses add_shortcode, get_the_ID
115
	 * @returns null
116
	 */
117
	public function action_frontend_init() {
118
		// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
119
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html_unsupported' ) );
120
121
		if ( ! $this->_enabled_for_request() )
122
			return;
123
124
		if ( isset( $_GET['relatedposts'] ) ) {
125
			$excludes = $this->parse_numeric_get_arg( 'relatedposts_exclude' );
126
			$this->_action_frontend_init_ajax( $excludes );
127
		} else {
128
			if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
129
				$this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
130
				$this->_previous_post_id = (int) $_GET['relatedposts_origin'];
131
			}
132
133
			$this->_action_frontend_init_page();
134
		}
135
136
	}
137
138
	/**
139
	 * Render insertion point.
140
	 *
141
	 * @since 4.2.0
142
	 *
143
	 * @return string
144
	 */
145
	public function get_headline() {
146
		$options = $this->get_options();
147
148
		if ( $options['show_headline'] ) {
149
			$headline = sprintf(
150
				/** This filter is already documented in modules/sharedaddy/sharing-service.php */
151
				apply_filters( 'jetpack_sharing_headline_html', '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>', esc_html( $options['headline'] ), 'related-posts' ),
152
				esc_html( $options['headline'] )
153
			);
154
		} else {
155
			$headline = '';
156
		}
157
		return $headline;
158
	}
159
160
	/**
161
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
162
	 * Will skip adding the target if the post content contains a Related Posts block.
163
	 *
164
	 * @filter the_content
165
	 * @param string $content
166
	 * @returns string
167
	 */
168
	public function filter_add_target_to_dom( $content ) {
169
		if ( function_exists( 'has_block' ) && has_block( 'jetpack/related-posts', $content ) ) {
170
			return $content;
171
		}
172
173
		if ( ! $this->_found_shortcode ) {
174
			$content .= "\n" . $this->get_target_html();
175
		}
176
177
		return $content;
178
	}
179
180
	/**
181
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
182
	 *
183
	 * @filter the_content
184
	 * @param string $content
185
	 * @uses has_shortcode
186
	 * @returns string
187
	 */
188
	public function test_for_shortcode( $content ) {
189
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
190
191
		return $content;
192
	}
193
194
	/**
195
	 * Returns the HTML for the related posts section.
196
	 *
197
	 * @uses esc_html__, apply_filters
198
	 * @returns string
199
	 */
200
	public function get_target_html() {
201
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
202
		if ( Jetpack_Sync_Settings::is_syncing() ) {
203
			return '';
204
		}
205
206
		/**
207
		 * Filter the Related Posts headline.
208
		 *
209
		 * @module related-posts
210
		 *
211
		 * @since 3.0.0
212
		 *
213
		 * @param string $headline Related Posts heading.
214
		 */
215
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
216
217
		if ( $this->_previous_post_id ) {
218
			$exclude = "data-exclude='{$this->_previous_post_id}'";
219
		} else {
220
			$exclude = "";
221
		}
222
223
		return <<<EOT
224
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
225
	$headline
226
</div>
227
EOT;
228
	}
229
230
	/**
231
	 * 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.
232
	 *
233
	 * @returns string
234
	 */
235
	public function get_target_html_unsupported() {
236
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
237
		if ( Jetpack_Sync_Settings::is_syncing() ) {
238
			return '';
239
		}
240
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
241
	}
242
243
	/**
244
	 * ===============
245
	 * GUTENBERG BLOCK
246
	 * ===============
247
	 */
248
249
	/**
250
 	 * Echoes out items for the Gutenberg block
251
 	 *
252
 	 * @param array $related_post The post oject
253
 	 * @param array $block attributes The block attributes
0 ignored issues
show
Bug introduced by
There is no parameter named $block. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
254
 	 */
255
 	public function render_block_item( $related_post, $block_attributes ) {
256
 		?>
257
 		<div
258
 			data-post-id="<?php echo esc_attr( $related_post['id'] ); ?>"
259
 			data-post-format="<?php echo esc_attr( ! empty( $related_post['format'] ) ? $related_post['format'] : 'false' ); ?>"
260
 			class="jp-related-posts-i2__post"
261
 		>
262
 			<h3 class="jp-related-posts-i2__post-heading">
263
 				<a
264
 					href="<?php echo esc_url( $related_post['url'] ); ?>"
265
 					title="<?php echo esc_html( $related_post['title'] ); ?>"
266
 					rel="<?php echo esc_attr( $related_post['rel'] ); ?>"
267
 					data-origin="<?php echo esc_attr( $related_post['url_meta']['origin'] ); ?>"
268
 					data-position="<?php echo esc_attr( $related_post['url_meta']['position'] ); ?>"
269
 					class="jp-related-posts-i2__post-link"
270
 				>
271
 					<?php echo esc_html( $related_post['title'] ); ?>
272
 				</a>
273
 			</h3>
274
 			<?php
275
 			if (
276
 				! empty( $block_attributes['show_thumbnails'] ) &&
277
 				! empty( $related_post['img']['src'] )
278
 			) :
279
 			?>
280
 			<a
281
 				href="<?php echo esc_url( $related_post['url'] ); ?>"
282
 				title="<?php echo esc_html( $related_post['title'] ); ?>"
283
 				rel="<?php echo esc_attr( $related_post['rel'] ); ?>"
284
 				data-origin="<?php echo esc_attr( $related_post['url_meta']['origin'] ); ?>"
285
 				data-position="<?php echo esc_attr( $related_post['url_meta']['position'] ); ?>"
286
 				class="jp-related-posts-i2__post-img-link"
287
 			>
288
 				<img class="jp-related-posts-i2__post-img"
289
 					src="<?php echo esc_url( $related_post['img']['src'] ); ?>"
290
 					width="<?php echo esc_attr( $related_post['img']['width'] ); ?>"
291
 					alt="<?php echo esc_html( $related_post['title'] ); ?>"
292
 				/>
293
 			</a>
294
 			<? endif; ?>
0 ignored issues
show
Security Best Practice introduced by
It is not recommend to use PHP's short opening tag <?, better use <?php, or <?= in case of outputting.

Short opening tags are disabled in PHP’s default configuration. In such a case, all content of this file is output verbatim to the browser without being parsed, or executed.

As a precaution to avoid these problems better use the long opening tag <?php.

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