Completed
Push — master-stable ( c165f1...74e410 )
by
unknown
08:22
created

modules/related-posts/jetpack-related-posts.php (2 issues)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
class Jetpack_RelatedPosts {
3
	const VERSION = '20150408';
4
	const SHORTCODE = 'jetpack-related-posts';
5
6
	/**
7
	 * Creates and returns a static instance of Jetpack_RelatedPosts.
8
	 *
9
	 * @return Jetpack_RelatedPosts
10
	 */
11 View Code Duplication
	public static function init() {
12
		static $instance = NULL;
13
14
		if ( ! $instance ) {
15
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
16
				$instance = WPCOM_RelatedPosts::init();
17
			} else {
18
				$instance = new Jetpack_RelatedPosts(
19
					get_current_blog_id(),
20
					Jetpack_Options::get_option( 'id' )
21
				);
22
			}
23
		}
24
25
		return $instance;
26
	}
27
28
	/**
29
	 * Creates and returns a static instance of Jetpack_RelatedPosts_Raw.
30
	 *
31
	 * @return Jetpack_RelatedPosts
32
	 */
33 View Code Duplication
	public static function init_raw() {
34
		static $instance = NULL;
35
36
		if ( ! $instance ) {
37
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
38
				$instance = WPCOM_RelatedPosts::init_raw();
39
			} else {
40
				$instance = new Jetpack_RelatedPosts_Raw(
41
					get_current_blog_id(),
42
					Jetpack_Options::get_option( 'id' )
43
				);
44
			}
45
		}
46
47
		return $instance;
48
	}
49
50
	protected $_blog_id_local;
51
	protected $_blog_id_wpcom;
52
	protected $_options;
53
	protected $_allow_feature_toggle;
54
	protected $_blog_charset;
55
	protected $_convert_charset;
56
	protected $_previous_post_id;
57
	protected $_found_shortcode = false;
58
59
	/**
60
	 * Constructor for Jetpack_RelatedPosts.
61
	 *
62
	 * @param int $blog_id_local
63
	 * @param int $blog_id_wpcom
64
	 * @uses get_option, add_action, apply_filters
65
	 * @return null
66
	 */
67
	public function __construct( $blog_id_local, $blog_id_wpcom ) {
68
		$this->_blog_id_local = $blog_id_local;
69
		$this->_blog_id_wpcom = $blog_id_wpcom;
70
		$this->_blog_charset = get_option( 'blog_charset' );
71
		$this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
72
73
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
74
		add_action( 'wp', array( $this, 'action_frontend_init' ) );
75
76
		if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
77
			jetpack_require_lib( 'class.media-summary' );
78
		}
79
80
		// Add Related Posts to the REST API Post response.
81
		if ( function_exists( 'register_rest_field' ) ) {
82
			add_action( 'rest_api_init',  array( $this, 'rest_register_related_posts' ) );
83
		}
84
	}
85
86
	/**
87
	 * =================
88
	 * ACTIONS & FILTERS
89
	 * =================
90
	 */
91
92
	/**
93
	 * Add a checkbox field to Settings > Reading for enabling related posts.
94
	 *
95
	 * @action admin_init
96
	 * @uses add_settings_field, __, register_setting, add_action
97
	 * @return null
98
	 */
99
	public function action_admin_init() {
100
101
		// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
102
		add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
103
		register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
104
		add_action('admin_head', array( $this, 'print_setting_head' ) );
105
106
		if( 'options-reading.php' == $GLOBALS['pagenow'] ) {
107
			// Enqueue style for live preview on the reading settings page
108
			$this->_enqueue_assets( false, true );
109
		}
110
	}
111
112
	/**
113
	 * Load related posts assets if it's a elegiable front end page or execute search and return JSON if it's an endpoint request.
114
	 *
115
	 * @global $_GET
116
	 * @action wp
117
	 * @uses add_shortcode, get_the_ID
118
	 * @returns null
119
	 */
120
	public function action_frontend_init() {
121
		// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
122
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html_unsupported' ) );
123
124
		if ( ! $this->_enabled_for_request() )
125
			return;
126
127
		if ( isset( $_GET['relatedposts'] ) ) {
128
			$excludes = array();
129
			if ( isset( $_GET['relatedposts_exclude'] ) ) {
130
				$excludes = explode( ',', $_GET['relatedposts_exclude'] );
131
			}
132
133
			$this->_action_frontend_init_ajax( $excludes );
134
		} else {
135
			if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
136
				$this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
137
				$this->_previous_post_id = (int) $_GET['relatedposts_origin'];
138
			}
139
140
			$this->_action_frontend_init_page();
141
		}
142
143
	}
144
145
	/**
146
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
147
	 *
148
	 * @filter the_content
149
	 * @param string $content
150
	 * @returns string
151
	 */
152
	public function filter_add_target_to_dom( $content ) {
153
		if ( !$this->_found_shortcode ) {
154
			$content .= "\n" . $this->get_target_html();
155
		}
156
157
		return $content;
158
	}
159
160
	/**
161
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
162
	 *
163
	 * @filter the_content
164
	 * @param string $content
165
	 * @uses has_shortcode
166
	 * @returns string
167
	 */
168
	public function test_for_shortcode( $content ) {
169
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
170
171
		return $content;
172
	}
173
174
	/**
175
	 * Returns the HTML for the related posts section.
176
	 *
177
	 * @uses esc_html__, apply_filters
178
	 * @returns string
179
	 */
180
	public function get_target_html() {
181
		if ( Jetpack_Sync_Settings::is_syncing() ) {
182
			return '';
183
		}
184
185
		$options = $this->get_options();
186
187
		if ( $options['show_headline'] ) {
188
			$headline = sprintf(
189
				'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
190
				esc_html__( 'Related', 'jetpack' )
191
			);
192
		} else {
193
			$headline = '';
194
		}
195
196
		/**
197
		 * Filter the Related Posts headline.
198
		 *
199
		 * @module related-posts
200
		 *
201
		 * @since 3.0.0
202
		 *
203
		 * @param string $headline Related Posts heading.
204
		 */
205
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $headline );
206
207
		if ( $this->_previous_post_id ) {
208
			$exclude = "data-exclude='{$this->_previous_post_id}'";
209
		} else {
210
			$exclude = "";
211
		}
212
213
		return <<<EOT
214
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
215
	$headline
216
</div>
217
EOT;
218
	}
219
220
	/**
221
	 * 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.
222
	 *
223
	 * @returns string
224
	 */
225
	public function get_target_html_unsupported() {
226
		if ( Jetpack_Sync_Settings::is_syncing() ) {
227
			return '';
228
		}
229
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
230
	}
231
232
	/**
233
	 * ========================
234
	 * PUBLIC UTILITY FUNCTIONS
235
	 * ========================
236
	 */
237
238
	/**
239
	 * Gets options set for Jetpack_RelatedPosts and merge with defaults.
240
	 *
241
	 * @uses Jetpack_Options::get_option, apply_filters
242
	 * @return array
243
	 */
244
	public function get_options() {
245
		if ( null === $this->_options ) {
246
			$this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
247
			if ( ! is_array( $this->_options ) )
248
				$this->_options = array();
249
			if ( ! isset( $this->_options['enabled'] ) )
250
				$this->_options['enabled'] = true;
251
			if ( ! isset( $this->_options['show_headline'] ) )
252
				$this->_options['show_headline'] = true;
253
			if ( ! isset( $this->_options['show_thumbnails'] ) )
254
				$this->_options['show_thumbnails'] = false;
255
			if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
256
				$this->_options['size'] = 3;
257
258
			/**
259
			 * Filter Related Posts basic options.
260
			 *
261
			 * @module related-posts
262
			 *
263
			 * @since 2.8.0
264
			 *
265
			 * @param array $this->_options Array of basic Related Posts options.
266
			 */
267
			$this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
268
		}
269
270
		return $this->_options;
271
	}
272
273
	/**
274
	 * Parses input and returnes normalized options array.
275
	 *
276
	 * @param array $input
277
	 * @uses self::get_options
278
	 * @return array
279
	 */
280
	public function parse_options( $input ) {
281
		$current = $this->get_options();
282
283
		if ( !is_array( $input ) )
284
			$input = array();
285
286
		if ( isset( $input['enabled'] ) && '1' == $input['enabled'] ) {
287
			$current['enabled'] = true;
288
			$current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
289
			$current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
290
		} else {
291
			$current['enabled'] = false;
292
		}
293
294
		if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
295
			$current['size'] = (int)$input['size'];
296
		else
297
			$current['size'] = null;
298
299
		return $current;
300
	}
301
302
	/**
303
	 * HTML for admin settings page.
304
	 *
305
	 * @uses self::get_options, checked, esc_html__
306
	 * @returns null
307
	 */
308
	public function print_setting_html() {
309
		$options = $this->get_options();
310
311
		$ui_settings_template = <<<EOT
312
<ul id="settings-reading-relatedposts-customize">
313
	<li>
314
		<label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
315
	</li>
316
	<li>
317
		<label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
318
	</li>
319
</ul>
320
<div id='settings-reading-relatedposts-preview'>
321
	%s
322
	<div id="jp-relatedposts" class="jp-relatedposts"></div>
323
</div>
324
EOT;
325
		$ui_settings = sprintf(
326
			$ui_settings_template,
327
			checked( $options['show_headline'], true, false ),
328
			esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),
329
			checked( $options['show_thumbnails'], true, false ),
330
			esc_html__( 'Use a large and visually striking layout', 'jetpack' ),
331
			esc_html__( 'Preview:', 'jetpack' )
332
		);
333
334
		if ( !$this->_allow_feature_toggle() ) {
335
			$template = <<<EOT
336
<input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
337
%s
338
EOT;
339
			printf(
340
				$template,
341
				$ui_settings
342
			);
343
		} else {
344
			$template = <<<EOT
345
<ul id="settings-reading-relatedposts">
346
	<li>
347
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
348
	</li>
349
	<li>
350
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
351
		%s
352
	</li>
353
</ul>
354
EOT;
355
			printf(
356
				$template,
357
				checked( $options['enabled'], false, false ),
358
				esc_html__( 'Hide related content after posts', 'jetpack' ),
359
				checked( $options['enabled'], true, false ),
360
				esc_html__( 'Show related content after posts', 'jetpack' ),
361
				$ui_settings
362
			);
363
		}
364
	}
365
366
	/**
367
	 * Head JS/CSS for admin settings page.
368
	 *
369
	 * @uses esc_html__
370
	 * @returns null
371
	 */
372
	public function print_setting_head() {
373
374
		// only dislay the Related Posts JavaScript on the Reading Settings Admin Page
375
		$current_screen =  get_current_screen();
376
377
		if ( is_null( $current_screen ) ) {
378
			return;
379
		}
380
381
		if( 'options-reading' != $current_screen->id )
382
			return;
383
384
		$related_headline = sprintf(
385
			'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
386
			esc_html__( 'Related', 'jetpack' )
387
		);
388
389
		$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
390
		$related_with_images = <<<EOT
391
<div class="jp-relatedposts-items jp-relatedposts-items-visual">
392
	<div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
393
		<a $href_params>
394
			<img class="jp-relatedposts-post-img" src="http://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">
395
		</a>
396
		<h4 class="jp-relatedposts-post-title">
397
			<a $href_params>Big iPhone/iPad Update Now Available</a>
398
		</h4>
399
		<p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
400
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
401
	</div>
402
	<div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
403
		<a $href_params>
404
			<img class="jp-relatedposts-post-img" src="http://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">
405
		</a>
406
		<h4 class="jp-relatedposts-post-title">
407
			<a $href_params>The WordPress for Android App Gets a Big Facelift</a>
408
		</h4>
409
		<p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
410
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
411
	</div>
412
	<div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
413
		<a $href_params>
414
			<img class="jp-relatedposts-post-img" src="http://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">
415
		</a>
416
		<h4 class="jp-relatedposts-post-title">
417
			<a $href_params>Upgrade Focus: VideoPress For Weddings</a>
418
		</h4>
419
		<p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
420
		<p class="jp-relatedposts-post-context">In "Upgrade"</p>
421
	</div>
422
</div>
423
EOT;
424
		$related_with_images = str_replace( "\n", '', $related_with_images );
425
		$related_without_images = <<<EOT
426
<div class="jp-relatedposts-items jp-relatedposts-items-minimal">
427
	<p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
428
		<span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
429
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
430
	</p>
431
	<p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
432
		<span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
433
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
434
	</p>
435
	<p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
436
		<span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
437
		<span class="jp-relatedposts-post-context">In "Upgrade"</span>
438
	</p>
439
</div>
440
EOT;
441
		$related_without_images = str_replace( "\n", '', $related_without_images );
442
443
		if ( $this->_allow_feature_toggle() ) {
444
			$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
445
		} else {
446
			$extra_css = '';
447
		}
448
449
		echo <<<EOT
450
<style type="text/css">
451
	#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
452
	#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
453
	$extra_css
454
</style>
455
<script type="text/javascript">
456
	jQuery( document ).ready( function($) {
457
		var update_ui = function() {
458
			var is_enabled = true;
459
			if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
460
				if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
461
					is_enabled = false;
462
				}
463
			}
464
			if ( is_enabled ) {
465
				$( '#settings-reading-relatedposts-customize' )
466
					.removeClass( 'disabled' )
467
					.find( 'input' )
468
					.attr( 'disabled', false );
469
				$( '#settings-reading-relatedposts-preview' )
470
					.removeClass( 'disabled' );
471
			} else {
472
				$( '#settings-reading-relatedposts-customize' )
473
					.addClass( 'disabled' )
474
					.find( 'input' )
475
					.attr( 'disabled', true );
476
				$( '#settings-reading-relatedposts-preview' )
477
					.addClass( 'disabled' );
478
			}
479
		};
480
481
		var update_preview = function() {
482
			var html = '';
483
			if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
484
				html += '$related_headline';
485
			}
486
			if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
487
				html += '$related_with_images';
488
			} else {
489
				html += '$related_without_images';
490
			}
491
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' )
492
				.html( html )
493
				.show();
494
		};
495
496
		// Update on load
497
		update_preview();
498
		update_ui();
499
500
		// Update on change
501
		$( '#settings-reading-relatedposts-customize input' )
502
			.change( update_preview );
503
		$( '#settings-reading-relatedposts' )
504
			.find( 'input.tog' )
505
			.change( update_ui );
506
	});
507
</script>
508
EOT;
509
	}
510
511
	/**
512
	 * Gets an array of related posts that match the given post_id.
513
	 *
514
	 * @param int $post_id
515
	 * @param array $args - params to use when building ElasticSearch filters to narrow down the search domain.
516
	 * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
517
	 * @return array
518
	 */
519
	public function get_for_post_id( $post_id, array $args ) {
520
		$options = $this->get_options();
521
522
		if ( ! empty( $args['size'] ) )
523
			$options['size'] = $args['size'];
524
525
		if ( ! $options['enabled'] || 0 == (int)$post_id || empty( $options['size'] ) )
526
			return array();
527
528
		$defaults = array(
529
			'size' => (int)$options['size'],
530
			'post_type' => get_post_type( $post_id ),
531
			'post_formats' => array(),
532
			'has_terms' => array(),
533
			'date_range' => array(),
534
			'exclude_post_ids' => array(),
535
		);
536
		$args = wp_parse_args( $args, $defaults );
537
		/**
538
		 * Filter the arguments used to retrieve a list of Related Posts.
539
		 *
540
		 * @module related-posts
541
		 *
542
		 * @since 2.8.0
543
		 *
544
		 * @param array $args Array of options to retrieve Related Posts.
545
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
546
		 */
547
		$args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
548
549
		$filters = $this->_get_es_filters_from_args( $post_id, $args );
550
		/**
551
		 * Filter ElasticSearch options used to calculate Related Posts.
552
		 *
553
		 * @module related-posts
554
		 *
555
		 * @since 2.8.0
556
		 *
557
		 * @param array $filters Array of ElasticSearch filters based on the post_id and args.
558
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
559
		 */
560
		$filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
561
562
		$results = $this->_get_related_posts( $post_id, $args['size'], $filters );
563
		/**
564
		 * Filter the array of related posts matched by ElasticSearch.
565
		 *
566
		 * @module related-posts
567
		 *
568
		 * @since 2.8.0
569
		 *
570
		 * @param array $results Array of related posts matched by ElasticSearch.
571
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
572
		 */
573
		return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
574
	}
575
576
	/**
577
	 * =========================
578
	 * PRIVATE UTILITY FUNCTIONS
579
	 * =========================
580
	 */
581
582
	/**
583
	 * Creates an array of ElasticSearch filters based on the post_id and args.
584
	 *
585
	 * @param int $post_id
586
	 * @param array $args
587
	 * @uses apply_filters, get_post_types, get_post_format_strings
588
	 * @return array
589
	 */
590
	protected function _get_es_filters_from_args( $post_id, array $args ) {
591
		$filters = array();
592
593
		/**
594
		 * Filter the terms used to search for Related Posts.
595
		 *
596
		 * @module related-posts
597
		 *
598
		 * @since 2.8.0
599
		 *
600
		 * @param array $args['has_terms'] Array of terms associated to the Related Posts.
601
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
602
		 */
603
		$args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
604
		if ( ! empty( $args['has_terms'] ) ) {
605
			foreach( (array)$args['has_terms'] as $term ) {
606
				if ( mb_strlen( $term->taxonomy ) ) {
607
					switch ( $term->taxonomy ) {
608
						case 'post_tag':
609
							$tax_fld = 'tag.slug';
610
							break;
611
						case 'category':
612
							$tax_fld = 'category.slug';
613
							break;
614
						default:
615
							$tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
616
							break;
617
					}
618
					$filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
619
				}
620
			}
621
		}
622
623
		/**
624
		 * Filter the Post Types where we search Related Posts.
625
		 *
626
		 * @module related-posts
627
		 *
628
		 * @since 2.8.0
629
		 *
630
		 * @param array $args['post_type'] Array of Post Types.
631
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
632
		 */
633
		$args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
634
		$valid_post_types = get_post_types();
635
		if ( is_array( $args['post_type'] ) ) {
636
			$sanitized_post_types = array();
637
			foreach ( $args['post_type'] as $pt ) {
638
				if ( in_array( $pt, $valid_post_types ) )
639
					$sanitized_post_types[] = $pt;
640
			}
641
			if ( ! empty( $sanitized_post_types ) )
642
				$filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
643
		} else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
644
			$filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
645
		}
646
647
		/**
648
		 * Filter the Post Formats where we search Related Posts.
649
		 *
650
		 * @module related-posts
651
		 *
652
		 * @since 3.3.0
653
		 *
654
		 * @param array $args['post_formats'] Array of Post Formats.
655
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
656
		 */
657
		$args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
658
		$valid_post_formats = get_post_format_strings();
659
		$sanitized_post_formats = array();
660
		foreach ( $args['post_formats'] as $pf ) {
661
			if ( array_key_exists( $pf, $valid_post_formats ) ) {
662
				$sanitized_post_formats[] = $pf;
663
			}
664
		}
665
		if ( ! empty( $sanitized_post_formats ) ) {
666
			$filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
667
		}
668
669
		/**
670
		 * Filter the date range used to search Related Posts.
671
		 *
672
		 * @module related-posts
673
		 *
674
		 * @since 2.8.0
675
		 *
676
		 * @param array $args['date_range'] Array of a month interval where we search Related Posts.
677
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
678
		 */
679
		$args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
680
		if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
681
			$args['date_range'] = array_map( 'intval', $args['date_range'] );
682
			if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
683
				$filters[] = array(
684
					'range' => array(
685
						'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
686
					)
687
				);
688
			}
689
		}
690
691
		/**
692
		 * Filter the Post IDs excluded from appearing in Related Posts.
693
		 *
694
		 * @module related-posts
695
		 *
696
		 * @since 2.9.0
697
		 *
698
		 * @param array $args['exclude_post_ids'] Array of Post IDs.
699
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
700
		 */
701
		$args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
702
		if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
703
			foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
704
				$exclude_post_id = (int)$exclude_post_id;
705
706
				if ( $exclude_post_id > 0 )
707
					$filters[] = array( 'not' => array( 'term' => array( 'post_id' => $exclude_post_id ) ) );
708
			}
709
		}
710
711
		return $filters;
712
	}
713
714
	/**
715
	 * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
716
	 *
717
	 * @param array $date_range
718
	 * @return array
719
	 */
720
	protected function _get_coalesced_range( array $date_range ) {
721
		$now = time();
722
		$coalesce_time = $this->_blog_id_wpcom % 86400;
723
		$current_time = $now - strtotime( 'today', $now );
724
725
		if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
726
			// Move back 1 period
727
			return array(
728
				'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
729
				'to'   => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
730
			);
731
		} else {
732
			// Use current period
733
			return array(
734
				'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
735
				'to'   => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
736
			);
737
		}
738
	}
739
740
	/**
741
	 * Generate and output ajax response for related posts API call.
742
	 * NOTE: Calls exit() to end all further processing after payload has been outputed.
743
	 *
744
	 * @param array $excludes array of post_ids to exclude
745
	 * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
746
	 * @return null
747
	 */
748
	protected function _action_frontend_init_ajax( array $excludes ) {
749
		define( 'DOING_AJAX', true );
750
751
		header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
752
		send_nosniff_header();
753
754
		$related_posts = $this->get_for_post_id(
755
			get_the_ID(),
756
			array(
757
				'exclude_post_ids' => $excludes,
758
			)
759
		);
760
761
		$options = $this->get_options();
762
763
		$response = array(
764
			'version' => self::VERSION,
765
			'show_thumbnails' => (bool) $options['show_thumbnails'],
766
			'items' => array(),
767
		);
768
769
		if ( count( $related_posts ) == $options['size'] )
770
			$response['items'] = $related_posts;
771
772
		echo json_encode( $response );
773
774
		exit();
775
	}
776
777
	/**
778
	 * Returns a UTF-8 encoded array of post information for the given post_id
779
	 *
780
	 * @param int $post_id
781
	 * @param int $position
782
	 * @param int $origin The post id that this is related to
783
	 * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
784
	 * @return array
785
	 */
786
	protected function _get_related_post_data_for_post( $post_id, $position, $origin ) {
787
		$post = get_post( $post_id );
788
789
		return array(
790
			'id' => $post->ID,
791
			'url' => get_permalink( $post->ID ),
792
			'url_meta' => array( 'origin' => $origin, 'position' => $position ),
793
			'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),
794
			'date' => get_the_date( '', $post->ID ),
795
			'format' => get_post_format( $post->ID ),
796
			'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
797
			/**
798
			 * Filters the rel attribute for the Related Posts' links.
799
			 *
800
			 * @module related-posts
801
			 *
802
			 * @since 3.7.0
803
			 *
804
			 * @param string nofollow Link rel attribute for Related Posts' link. Default is nofollow.
805
			 * @param int $post->ID Post ID.
806
			 */
807
			'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', 'nofollow', $post->ID ),
808
			/**
809
			 * Filter the context displayed below each Related Post.
810
			 *
811
			 * @module related-posts
812
			 *
813
			 * @since 3.0.0
814
			 *
815
			 * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
816
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
817
			 */
818
			'context' => apply_filters(
819
				'jetpack_relatedposts_filter_post_context',
820
				$this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
821
				$post->ID
822
			),
823
			'img' => $this->_generate_related_post_image_params( $post->ID ),
824
			/**
825
			 * Filter the post css classes added on HTML markup.
826
			 *
827
			 * @module related-posts
828
			 *
829
			 * @since 3.8.0
830
			 *
831
			 * @param array array() CSS classes added on post HTML markup.
832
			 * @param string $post_id Post ID.
833
			 */
834
			'classes' => apply_filters(
835
				'jetpack_relatedposts_filter_post_css_classes',
836
				array(),
837
				$post->ID
838
			),
839
		);
840
	}
841
842
	/**
843
	 * Returns either the title or a small excerpt to use as title for post.
844
	 *
845
	 * @param string $post_title
846
	 * @param string $post_content
847
	 * @uses strip_shortcodes, wp_trim_words, __
848
	 * @return string
849
	 */
850
	protected function _get_title( $post_title, $post_content ) {
851
		if ( ! empty( $post_title ) ) {
852
			return wp_strip_all_tags( $post_title );
853
		}
854
855
		$post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
856
		if ( ! empty( $post_title ) ) {
857
			return $post_title;
858
		}
859
860
		return __( 'Untitled Post', 'jetpack' );
861
	}
862
863
	/**
864
	 * Returns a plain text post excerpt for title attribute of links.
865
	 *
866
	 * @param string $post_excerpt
867
	 * @param string $post_content
868
	 * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
869
	 * @return string
870
	 */
871
	protected function _get_excerpt( $post_excerpt, $post_content ) {
872
		if ( empty( $post_excerpt ) )
873
			$excerpt = $post_content;
874
		else
875
			$excerpt = $post_excerpt;
876
877
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
878
	}
879
880
	/**
881
	 * Generates the thumbnail image to be used for the post. Uses the
882
	 * image as returned by Jetpack_PostImages::get_image()
883
	 *
884
	 * @param int $post_id
885
	 * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
886
	 * @return string
887
	 */
888
	protected function _generate_related_post_image_params( $post_id ) {
889
		$options = $this->get_options();
890
		$image_params = array(
891
			'src' => '',
892
			'width' => 0,
893
			'height' => 0,
894
		);
895
896
		if ( ! $options['show_thumbnails'] ) {
897
			return $image_params;
898
		}
899
900
		/**
901
		 * Filter the size of the Related Posts images.
902
		 *
903
		 * @module related-posts
904
		 *
905
		 * @since 2.8.0
906
		 *
907
		 * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
908
		 */
909
		$thumbnail_size = apply_filters(
910
			'jetpack_relatedposts_filter_thumbnail_size',
911
			array( 'width' => 350, 'height' => 200 )
912
		);
913
		if ( !is_array( $thumbnail_size ) ) {
914
			$thumbnail_size = array(
915
				'width' => (int)$thumbnail_size,
916
				'height' => (int)$thumbnail_size
917
			);
918
		}
919
920
		// Try to get post image
921
		if ( class_exists( 'Jetpack_PostImages' ) ) {
922
			$img_url = '';
923
			$post_image = Jetpack_PostImages::get_image(
924
				$post_id,
925
				$thumbnail_size
926
			);
927
928
			if ( is_array($post_image) ) {
929
				$img_url = $post_image['src'];
930
			} elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
931
				$media = Jetpack_Media_Summary::get( $post_id );
932
933
				if ( is_array($media) && !empty( $media['image'] ) ) {
934
					$img_url = $media['image'];
935
				}
936
			}
937
938
			if ( !empty( $img_url ) ) {
939
				$image_params['width'] = $thumbnail_size['width'];
940
				$image_params['height'] = $thumbnail_size['height'];
941
				$image_params['src'] = Jetpack_PostImages::fit_image_url(
942
					$img_url,
943
					$thumbnail_size['width'],
944
					$thumbnail_size['height']
945
				);
946
			}
947
		}
948
949
		return $image_params;
950
	}
951
952
	/**
953
	 * Returns the string UTF-8 encoded
954
	 *
955
	 * @param string $text
956
	 * @return string
957
	 */
958
	protected function _to_utf8( $text ) {
959
		if ( $this->_convert_charset ) {
960
			return iconv( $this->_blog_charset, 'UTF-8', $text );
961
		} else {
962
			return $text;
963
		}
964
	}
965
966
	/**
967
	 * =============================================
968
	 * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
969
	 * =============================================
970
	 */
971
972
	/**
973
	 * Workhorse method to return array of related posts matched by ElasticSearch.
974
	 *
975
	 * @param int $post_id
976
	 * @param int $size
977
	 * @param array $filters
978
	 * @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
979
	 * @return array
980
	 */
981
	protected function _get_related_posts( $post_id, $size, array $filters ) {
982
		$hits = $this->_filter_non_public_posts(
983
			$this->_get_related_post_ids(
984
				$post_id,
985
				$size,
986
				$filters
987
			)
988
		);
989
990
		/**
991
		 * Filter the Related Posts matched by ElasticSearch.
992
		 *
993
		 * @module related-posts
994
		 *
995
		 * @since 2.9.0
996
		 *
997
		 * @param array $hits Array of Post IDs matched by ElasticSearch.
998
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
999
		 */
1000
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1001
1002
		$related_posts = array();
1003
		foreach ( $hits as $i => $hit ) {
1004
			$related_posts[] = $this->_get_related_post_data_for_post( $hit['id'], $i, $post_id );
1005
		}
1006
		return $related_posts;
1007
	}
1008
1009
	/**
1010
	 * Get array of related posts matched by ElasticSearch.
1011
	 *
1012
	 * @param int $post_id
1013
	 * @param int $size
1014
	 * @param array $filters
1015
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
1016
	 * @return array
1017
	 */
1018
	protected function _get_related_post_ids( $post_id, $size, array $filters ) {
1019
		$now_ts = time();
1020
		$cache_meta_key = '_jetpack_related_posts_cache';
1021
1022
		$body = array(
1023
			'size' => (int) $size,
1024
		);
1025
1026
		if ( !empty( $filters ) )
1027
			$body['filter'] = array( 'and' => $filters );
1028
1029
		// Build cache key
1030
		$cache_key = md5( serialize( $body ) );
1031
1032
		// Load all cached values
1033
		if ( wp_using_ext_object_cache() ) {
1034
			$transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
1035
			$cache = get_transient( $transient_name );
1036
			if ( false !== $cache ) {
1037
				return $cache;
1038
			}
1039
		} else {
1040
			$cache = get_post_meta( $post_id, $cache_meta_key, true );
1041
1042
			if ( empty( $cache ) )
1043
				$cache = array();
1044
1045
1046
			// Cache is valid! Return cached value.
1047
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
1048
				return $cache[ $cache_key ][ 'payload' ];
1049
			}
1050
		}
1051
1052
		$response = wp_remote_post(
1053
			"https://public-api.wordpress.com/rest/v1/sites/{$this->_blog_id_wpcom}/posts/$post_id/related/",
1054
			array(
1055
				'timeout' => 10,
1056
				'user-agent' => 'jetpack_related_posts',
1057
				'sslverify' => true,
1058
				'body' => $body,
1059
			)
1060
		);
1061
1062
		// Oh no... return nothing don't cache errors.
1063
		if ( is_wp_error( $response ) ) {
1064
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
1065
				return $cache[ $cache_key ][ 'payload' ]; // return stale
1066
			else
1067
				return array();
1068
		}
1069
1070
		$results = json_decode( wp_remote_retrieve_body( $response ), true );
1071
		$related_posts = array();
1072
		if ( is_array( $results ) && !empty( $results['hits'] ) ) {
1073
			foreach( $results['hits'] as $hit ) {
1074
				$related_posts[] = array(
1075
					'id' => $hit['fields']['post_id'],
1076
				);
1077
			}
1078
		}
1079
1080
		// An empty array might indicate no related posts or that posts
1081
		// are not yet synced to WordPress.com, so we cache for only 1
1082
		// minute in this case
1083
		if ( empty( $related_posts ) ) {
1084
			$cache_ttl = 60;
1085
		} else {
1086
			$cache_ttl = 12 * HOUR_IN_SECONDS;
1087
		}
1088
1089
		// Update cache
1090
		if ( wp_using_ext_object_cache() ) {
1091
			set_transient( $transient_name, $related_posts, $cache_ttl );
1092
		} else {
1093
			// Copy all valid cache values
1094
			$new_cache = array();
1095
			foreach ( $cache as $k => $v ) {
1096
				if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
1097
					$new_cache[ $k ] = $v;
1098
				}
1099
			}
1100
1101
			// Set new cache value
1102
			$cache_expires = $cache_ttl + $now_ts;
1103
			$new_cache[ $cache_key ] = array(
1104
				'expires' => $cache_expires,
1105
				'payload' => $related_posts,
1106
			);
1107
			update_post_meta( $post_id, $cache_meta_key, $new_cache );
1108
		}
1109
1110
		return $related_posts;
1111
	}
1112
1113
	/**
1114
	 * Filter out any hits that are not public anymore.
1115
	 *
1116
	 * @param array $related_posts
1117
	 * @uses get_post_stati, get_post_status
1118
	 * @return array
1119
	 */
1120
	protected function _filter_non_public_posts( array $related_posts ) {
1121
		$public_stati = get_post_stati( array( 'public' => true ) );
1122
1123
		$filtered = array();
1124
		foreach ( $related_posts as $hit ) {
1125
			if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
1126
				$filtered[] = $hit;
1127
			}
1128
		}
1129
		return $filtered;
1130
	}
1131
1132
	/**
1133
	 * Generates a context for the related content (second line in related post output).
1134
	 * Order of importance:
1135
	 *   - First category (Not 'Uncategorized')
1136
	 *   - First post tag
1137
	 *   - Number of comments
1138
	 *
1139
	 * @param int $post_id
1140
	 * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
1141
	 * @return string
1142
	 */
1143
	protected function _generate_related_post_context( $post_id ) {
1144
		$categories = get_the_category( $post_id );
1145 View Code Duplication
		if ( is_array( $categories ) ) {
1146
			foreach ( $categories as $category ) {
1147
				if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
1148
					$post_cat_context = sprintf(
1149
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1150
						$category->name
1151
					);
1152
					/**
1153
					 * Filter the "In Category" line displayed in the post context below each Related Post.
1154
					 *
1155
					 * @module related-posts
1156
					 *
1157
					 * @since 3.2.0
1158
					 *
1159
					 * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
1160
					 * @param array $category Array containing information about the category.
1161
					 */
1162
					return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
1163
				}
1164
			}
1165
		}
1166
1167
		$tags = get_the_terms( $post_id, 'post_tag' );
1168 View Code Duplication
		if ( is_array( $tags ) ) {
1169
			foreach ( $tags as $tag ) {
1170
				if ( '' != trim( $tag->name ) ) {
1171
					$post_tag_context = sprintf(
1172
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1173
						$tag->name
1174
					);
1175
					/**
1176
					 * Filter the "In Tag" line displayed in the post context below each Related Post.
1177
					 *
1178
					 * @module related-posts
1179
					 *
1180
					 * @since 3.2.0
1181
					 *
1182
					 * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
1183
					 * @param array $tag Array containing information about the tag.
1184
					 */
1185
					return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
1186
				}
1187
			}
1188
		}
1189
1190
		$comment_count = get_comments_number( $post_id );
1191
		if ( $comment_count > 0 ) {
1192
			return sprintf(
1193
				_n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
1194
				number_format_i18n( $comment_count )
1195
			);
1196
		}
1197
1198
		return __( 'Similar post', 'jetpack' );
1199
	}
1200
1201
	/**
1202
	 * Logs clicks for clickthrough analysis and related result tuning.
1203
	 *
1204
	 * @return null
1205
	 */
1206
	protected function _log_click( $post_id, $to_post_id, $link_position ) {
1207
1208
	}
1209
1210
	/**
1211
	 * Determines if the current post is able to use related posts.
1212
	 *
1213
	 * @uses self::get_options, is_admin, is_single, apply_filters
1214
	 * @return bool
1215
	 */
1216
	protected function _enabled_for_request() {
1217
		// Default to enabled
1218
		$enabled = true;
1219
1220
		// Must have feature enabled
1221
		$options = $this->get_options();
1222
		if ( ! $options['enabled'] ) {
1223
			$enabled = false;
1224
		}
1225
1226
		// Only run for frontend pages
1227
		if ( is_admin() ) {
1228
			$enabled = false;
1229
		}
1230
1231
		// Only run for standalone posts
1232
		if ( ! is_single() ) {
1233
			$enabled = false;
1234
		}
1235
1236
		/**
1237
		 * Filter the Enabled value to allow related posts to be shown on pages as well.
1238
		 *
1239
		 * @module related-posts
1240
		 *
1241
		 * @since 3.3.0
1242
		 *
1243
		 * @param bool $enabled Should Related Posts be enabled on the current page.
1244
		 */
1245
		return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
1246
	}
1247
1248
	/**
1249
	 * Adds filters and enqueues assets.
1250
	 *
1251
	 * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
1252
	 * @return null
1253
	 */
1254
	protected function _action_frontend_init_page() {
1255
		$this->_enqueue_assets( true, true );
1256
		$this->_setup_shortcode();
1257
1258
		add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
1259
	}
1260
1261
	/**
1262
	 * Enqueues assets needed to do async loading of related posts.
1263
	 *
1264
	 * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
1265
	 * @return null
1266
	 */
1267
	protected function _enqueue_assets( $script, $style ) {
1268
		if ( $script ) {
1269
			wp_enqueue_script( 'jetpack_related-posts', plugins_url( 'related-posts.js', __FILE__ ), array( 'jquery' ), self::VERSION );
1270
			$related_posts_js_options = array(
1271
				/**
1272
				 * Filter each Related Post Heading structure.
1273
				 *
1274
				 * @since 4.0.0
1275
				 *
1276
				 * @param string $str Related Post Heading structure. Default to h4.
1277
				 */
1278
				'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
1279
			);
1280
			wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
1281
		}
1282
		if ( $style ){
1283
			if( is_rtl() ) {
1284
				wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'rtl/related-posts-rtl.css', __FILE__ ), array(), self::VERSION );
1285
			} else {
1286
				wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
1287
			}
1288
		}
1289
	}
1290
1291
	/**
1292
	 * Sets up the shortcode processing.
1293
	 *
1294
	 * @uses add_filter, add_shortcode
1295
	 * @return null
1296
	 */
1297
	protected function _setup_shortcode() {
1298
		add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
1299
1300
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html' ) );
1301
	}
1302
1303
	protected function _allow_feature_toggle() {
1304
		if ( null === $this->_allow_feature_toggle ) {
1305
			/**
1306
			 * Filter the display of the Related Posts toggle in Settings > Reading.
1307
			 *
1308
			 * @module related-posts
1309
			 *
1310
			 * @since 2.8.0
1311
			 *
1312
			 * @param bool false Display a feature toggle. Default to false.
1313
			 */
1314
			$this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
1315
		}
1316
		return $this->_allow_feature_toggle;
1317
	}
1318
1319
	/**
1320
	 * ===================================================
1321
	 * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
1322
	 * ===================================================
1323
	 */
1324
1325
	/**
1326
	 * Add Related Posts to the REST API Post response.
1327
	 *
1328
	 * @since 4.4.0
1329
	 *
1330
	 * @action rest_api_init
1331
	 * @uses register_rest_field, self::rest_get_related_posts
1332
	 * @return null
1333
	 */
1334
	public function rest_register_related_posts() {
1335
		register_rest_field( 'post',
1336
			'jetpack-related-posts',
1337
			array(
1338
				'get_callback' => array( $this, 'rest_get_related_posts' ),
1339
				'update_callback' => null,
1340
				'schema'          => null,
1341
			)
1342
		);
1343
	}
1344
1345
	/**
1346
	 * Build an array of Related Posts.
1347
	 *
1348
	 * @since 4.4.0
1349
	 *
1350
	 * @param array $object Details of current post.
1351
	 * @param string $field_name Name of field.
1352
	 * @param WP_REST_Request $request Current request
1353
	 *
1354
	 * @uses self::get_for_post_id
1355
	 *
1356
	 * @return array
1357
	 */
1358
	public function rest_get_related_posts( $object, $field_name, $request ) {
0 ignored issues
show
The parameter $field_name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1359
		return $this->get_for_post_id( $object['id'], array() );
1360
	}
1361
}
1362
1363
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
1364
	protected $_query_name;
1365
1366
	/**
1367
	 * Allows callers of this class to tag each query with a unique name for tracking purposes.
1368
	 *
1369
	 * @param string $name
1370
	 * @return Jetpack_RelatedPosts_Raw
1371
	 */
1372
	public function set_query_name( $name ) {
1373
		$this->_query_name = (string) $name;
1374
		return $this;
1375
	}
1376
1377
	/**
1378
	 * The raw related posts class can be used by other plugins or themes
1379
	 * to get related content. This class wraps the existing RelatedPosts
1380
	 * logic thus we never want to add anything to the DOM or do anything
1381
	 * for event hooks. We will also not present any settings for this
1382
	 * class and keep it enabled as calls to this class is done
1383
	 * programmatically.
1384
	 */
1385
	public function action_admin_init() {}
1386
	public function action_frontend_init() {}
1387
	public function get_options() {
1388
		return array(
1389
			'enabled' => true,
1390
		);
1391
	}
1392
1393
	/**
1394
	 * Workhorse method to return array of related posts ids matched by ElasticSearch.
1395
	 *
1396
	 * @param int $post_id
1397
	 * @param int $size
1398
	 * @param array $filters
1399
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
1400
	 * @return array
1401
	 */
1402
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1403
		$hits = $this->_filter_non_public_posts(
1404
			$this->_get_related_post_ids(
1405
				$post_id,
1406
				$size,
1407
				$filters
1408
			)
1409
		);
1410
1411
		/** This filter is already documented in modules/related-posts/related-posts.php */
1412
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1413
1414
		return $hits;
1415
	}
1416
}
1417