Completed
Push — branch-4.5 ( 26b6a5...520ed8 )
by
unknown
326:11 queued 319:07
created

Jetpack_RelatedPosts::parse_options()   F

Complexity

Conditions 13
Paths 516

Size

Total Lines 25
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 19
nc 516
nop 1
dl 0
loc 25
rs 3.0028
c 0
b 0
f 0

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
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
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...
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
	 * Render insertion point.
147
	 *
148
	 * @since 4.2.0
149
	 *
150
	 * @return string
151
	 */
152
	public function get_headline() {
153
		$options = $this->get_options();
154
155
		if ( $options['show_headline'] ) {
156
			$headline = sprintf(
157
				'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
158
				esc_html( $options['headline'] )
159
			);
160
		} else {
161
			$headline = '';
162
		}
163
		return $headline;
164
	}
165
166
	/**
167
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
168
	 *
169
	 * @filter the_content
170
	 * @param string $content
171
	 * @returns string
172
	 */
173
	public function filter_add_target_to_dom( $content ) {
174
		if ( !$this->_found_shortcode ) {
175
			$content .= "\n" . $this->get_target_html();
176
		}
177
178
		return $content;
179
	}
180
181
	/**
182
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
183
	 *
184
	 * @filter the_content
185
	 * @param string $content
186
	 * @uses has_shortcode
187
	 * @returns string
188
	 */
189
	public function test_for_shortcode( $content ) {
190
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
191
192
		return $content;
193
	}
194
195
	/**
196
	 * Returns the HTML for the related posts section.
197
	 *
198
	 * @uses esc_html__, apply_filters
199
	 * @returns string
200
	 */
201
	public function get_target_html() {
202
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
203
		if ( Jetpack_Sync_Settings::is_syncing() ) {
204
			return '';
205
		}
206
207
		/**
208
		 * Filter the Related Posts headline.
209
		 *
210
		 * @module related-posts
211
		 *
212
		 * @since 3.0.0
213
		 *
214
		 * @param string $headline Related Posts heading.
215
		 */
216
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
217
218
		if ( $this->_previous_post_id ) {
219
			$exclude = "data-exclude='{$this->_previous_post_id}'";
220
		} else {
221
			$exclude = "";
222
		}
223
224
		return <<<EOT
225
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
226
	$headline
227
</div>
228
EOT;
229
	}
230
231
	/**
232
	 * 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.
233
	 *
234
	 * @returns string
235
	 */
236
	public function get_target_html_unsupported() {
237
		require_once JETPACK__PLUGIN_DIR . '/sync/class.jetpack-sync-settings.php';
238
		if ( Jetpack_Sync_Settings::is_syncing() ) {
239
			return '';
240
		}
241
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
242
	}
243
244
	/**
245
	 * ========================
246
	 * PUBLIC UTILITY FUNCTIONS
247
	 * ========================
248
	 */
249
250
	/**
251
	 * Gets options set for Jetpack_RelatedPosts and merge with defaults.
252
	 *
253
	 * @uses Jetpack_Options::get_option, apply_filters
254
	 * @return array
255
	 */
256
	public function get_options() {
257
		if ( null === $this->_options ) {
258
			$this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
259
			if ( ! is_array( $this->_options ) )
260
				$this->_options = array();
261
			if ( ! isset( $this->_options['enabled'] ) )
262
				$this->_options['enabled'] = true;
263
			if ( ! isset( $this->_options['show_headline'] ) )
264
				$this->_options['show_headline'] = true;
265
			if ( ! isset( $this->_options['show_thumbnails'] ) )
266
				$this->_options['show_thumbnails'] = false;
267
			if ( ! isset( $this->_options['show_date'] ) ) {
268
				$this->_options['show_date'] = true;
269
			}
270
			if ( ! isset( $this->_options['show_context'] ) ) {
271
				$this->_options['show_context'] = true;
272
			}
273
			if ( ! isset( $this->_options['layout'] ) ) {
274
				$this->_options['layout'] = 'grid';
275
			}
276
			if ( ! isset( $this->_options['headline'] ) ) {
277
				$this->_options['headline'] = esc_html__( 'Related', 'jetpack' );
278
			}
279
			if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
280
				$this->_options['size'] = 3;
281
282
			/**
283
			 * Filter Related Posts basic options.
284
			 *
285
			 * @module related-posts
286
			 *
287
			 * @since 2.8.0
288
			 *
289
			 * @param array $this->_options Array of basic Related Posts options.
290
			 */
291
			$this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
292
		}
293
294
		return $this->_options;
295
	}
296
297
	/**
298
	 * Parses input and returns normalized options array.
299
	 *
300
	 * @param array $input
301
	 * @uses self::get_options
302
	 * @return array
303
	 */
304
	public function parse_options( $input ) {
305
		$current = $this->get_options();
306
307
		if ( !is_array( $input ) )
308
			$input = array();
309
310
		if ( isset( $input['enabled'] ) && '1' == $input['enabled'] ) {
311
			$current['enabled'] = true;
312
			$current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
313
			$current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
314
			$current['show_date'] = ( isset( $input['show_date'] ) && '1' == $input['show_date'] );
315
			$current['show_context'] = ( isset( $input['show_context'] ) && '1' == $input['show_context'] );
316
			$current['layout'] = isset( $input['layout'] ) && in_array( $input['layout'], array( 'grid', 'list' ), true ) ? $input['layout'] : 'grid';
317
			$current['headline'] = isset( $input['headline'] ) ? $input['headline'] : esc_html__( 'Related', 'jetpack' );
318
		} else {
319
			$current['enabled'] = false;
320
		}
321
322
		if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
323
			$current['size'] = (int)$input['size'];
324
		else
325
			$current['size'] = null;
326
327
		return $current;
328
	}
329
330
	/**
331
	 * HTML for admin settings page.
332
	 *
333
	 * @uses self::get_options, checked, esc_html__
334
	 * @returns null
335
	 */
336
	public function print_setting_html() {
337
		$options = $this->get_options();
338
339
		$ui_settings_template = <<<EOT
340
<ul id="settings-reading-relatedposts-customize">
341
	<li>
342
		<label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
343
	</li>
344
	<li>
345
		<label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
346
	</li>
347
	<li>
348
		<label><input name="jetpack_relatedposts[show_date]" type="checkbox" value="1" %s /> %s</label>
349
	</li>
350
	<li>
351
		<label><input name="jetpack_relatedposts[show_context]" type="checkbox" value="1" %s /> %s</label>
352
	</li>
353
</ul>
354
<div id='settings-reading-relatedposts-preview'>
355
	%s
356
	<div id="jp-relatedposts" class="jp-relatedposts"></div>
357
</div>
358
EOT;
359
		$ui_settings = sprintf(
360
			$ui_settings_template,
361
			checked( $options['show_headline'], true, false ),
362
			esc_html__( 'Show a "Related" header to more clearly separate the related section from posts', 'jetpack' ),
363
			checked( $options['show_thumbnails'], true, false ),
364
			esc_html__( 'Use a large and visually striking layout', 'jetpack' ),
365
			checked( $options['show_date'], true, false ),
366
			esc_html__( 'Show entry date', 'jetpack' ),
367
			checked( $options['show_context'], true, false ),
368
			esc_html__( 'Show context (category or tag)', 'jetpack' ),
369
			esc_html__( 'Preview:', 'jetpack' )
370
		);
371
372
		if ( !$this->_allow_feature_toggle() ) {
373
			$template = <<<EOT
374
<input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
375
%s
376
EOT;
377
			printf(
378
				$template,
379
				$ui_settings
380
			);
381
		} else {
382
			$template = <<<EOT
383
<ul id="settings-reading-relatedposts">
384
	<li>
385
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
386
	</li>
387
	<li>
388
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
389
		%s
390
	</li>
391
</ul>
392
EOT;
393
			printf(
394
				$template,
395
				checked( $options['enabled'], false, false ),
396
				esc_html__( 'Hide related content after posts', 'jetpack' ),
397
				checked( $options['enabled'], true, false ),
398
				esc_html__( 'Show related content after posts', 'jetpack' ),
399
				$ui_settings
400
			);
401
		}
402
	}
403
404
	/**
405
	 * Head JS/CSS for admin settings page.
406
	 *
407
	 * @uses esc_html__
408
	 * @returns null
409
	 */
410
	public function print_setting_head() {
411
412
		// only dislay the Related Posts JavaScript on the Reading Settings Admin Page
413
		$current_screen =  get_current_screen();
414
415
		if ( is_null( $current_screen ) ) {
416
			return;
417
		}
418
419
		if( 'options-reading' != $current_screen->id )
420
			return;
421
422
		$related_headline = sprintf(
423
			'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
424
			esc_html__( 'Related', 'jetpack' )
425
		);
426
427
		$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
428
		$related_with_images = <<<EOT
429
<div class="jp-relatedposts-items jp-relatedposts-items-visual">
430
	<div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
431
		<a $href_params>
432
			<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">
433
		</a>
434
		<h4 class="jp-relatedposts-post-title">
435
			<a $href_params>Big iPhone/iPad Update Now Available</a>
436
		</h4>
437
		<p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
438
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
439
	</div>
440
	<div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
441
		<a $href_params>
442
			<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">
443
		</a>
444
		<h4 class="jp-relatedposts-post-title">
445
			<a $href_params>The WordPress for Android App Gets a Big Facelift</a>
446
		</h4>
447
		<p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
448
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
449
	</div>
450
	<div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
451
		<a $href_params>
452
			<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">
453
		</a>
454
		<h4 class="jp-relatedposts-post-title">
455
			<a $href_params>Upgrade Focus: VideoPress For Weddings</a>
456
		</h4>
457
		<p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
458
		<p class="jp-relatedposts-post-context">In "Upgrade"</p>
459
	</div>
460
</div>
461
EOT;
462
		$related_with_images = str_replace( "\n", '', $related_with_images );
463
		$related_without_images = <<<EOT
464
<div class="jp-relatedposts-items jp-relatedposts-items-minimal">
465
	<p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
466
		<span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
467
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
468
	</p>
469
	<p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
470
		<span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
471
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
472
	</p>
473
	<p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
474
		<span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
475
		<span class="jp-relatedposts-post-context">In "Upgrade"</span>
476
	</p>
477
</div>
478
EOT;
479
		$related_without_images = str_replace( "\n", '', $related_without_images );
480
481
		if ( $this->_allow_feature_toggle() ) {
482
			$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
483
		} else {
484
			$extra_css = '';
485
		}
486
487
		echo <<<EOT
488
<style type="text/css">
489
	#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
490
	#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
491
	$extra_css
492
</style>
493
<script type="text/javascript">
494
	jQuery( document ).ready( function($) {
495
		var update_ui = function() {
496
			var is_enabled = true;
497
			if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
498
				if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
499
					is_enabled = false;
500
				}
501
			}
502
			if ( is_enabled ) {
503
				$( '#settings-reading-relatedposts-customize' )
504
					.removeClass( 'disabled' )
505
					.find( 'input' )
506
					.attr( 'disabled', false );
507
				$( '#settings-reading-relatedposts-preview' )
508
					.removeClass( 'disabled' );
509
			} else {
510
				$( '#settings-reading-relatedposts-customize' )
511
					.addClass( 'disabled' )
512
					.find( 'input' )
513
					.attr( 'disabled', true );
514
				$( '#settings-reading-relatedposts-preview' )
515
					.addClass( 'disabled' );
516
			}
517
		};
518
519
		var update_preview = function() {
520
			var html = '';
521
			if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
522
				html += '$related_headline';
523
			}
524
			if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
525
				html += '$related_with_images';
526
			} else {
527
				html += '$related_without_images';
528
			}
529
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).html( html );
530
			if ( $( 'input[name="jetpack_relatedposts[show_date]"]:checked' ).length ) {
531
				$( '.jp-relatedposts-post-title' ).each( function() {
532
					$( this ).after( $( '<span>August 8, 2005</span>' ) );
533
				} );
534
			}
535
			if ( $( 'input[name="jetpack_relatedposts[show_context]"]:checked' ).length ) {
536
				$( '.jp-relatedposts-post-context' ).show();
537
			} else {
538
				$( '.jp-relatedposts-post-context' ).hide();
539
			}
540
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).show();
541
		};
542
543
		// Update on load
544
		update_preview();
545
		update_ui();
546
547
		// Update on change
548
		$( '#settings-reading-relatedposts-customize input' )
549
			.change( update_preview );
550
		$( '#settings-reading-relatedposts' )
551
			.find( 'input.tog' )
552
			.change( update_ui );
553
	});
554
</script>
555
EOT;
556
	}
557
558
	/**
559
	 * Gets an array of related posts that match the given post_id.
560
	 *
561
	 * @param int $post_id
562
	 * @param array $args - params to use when building ElasticSearch filters to narrow down the search domain.
563
	 * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
564
	 * @return array
565
	 */
566
	public function get_for_post_id( $post_id, array $args ) {
567
		$options = $this->get_options();
568
569
		if ( ! empty( $args['size'] ) )
570
			$options['size'] = $args['size'];
571
572
		if ( ! $options['enabled'] || 0 == (int)$post_id || empty( $options['size'] ) )
573
			return array();
574
575
		$defaults = array(
576
			'size' => (int)$options['size'],
577
			'post_type' => get_post_type( $post_id ),
578
			'post_formats' => array(),
579
			'has_terms' => array(),
580
			'date_range' => array(),
581
			'exclude_post_ids' => array(),
582
		);
583
		$args = wp_parse_args( $args, $defaults );
584
		/**
585
		 * Filter the arguments used to retrieve a list of Related Posts.
586
		 *
587
		 * @module related-posts
588
		 *
589
		 * @since 2.8.0
590
		 *
591
		 * @param array $args Array of options to retrieve Related Posts.
592
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
593
		 */
594
		$args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
595
596
		$filters = $this->_get_es_filters_from_args( $post_id, $args );
597
		/**
598
		 * Filter ElasticSearch options used to calculate Related Posts.
599
		 *
600
		 * @module related-posts
601
		 *
602
		 * @since 2.8.0
603
		 *
604
		 * @param array $filters Array of ElasticSearch filters based on the post_id and args.
605
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
606
		 */
607
		$filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
608
609
		$results = $this->_get_related_posts( $post_id, $args['size'], $filters );
610
		/**
611
		 * Filter the array of related posts matched by ElasticSearch.
612
		 *
613
		 * @module related-posts
614
		 *
615
		 * @since 2.8.0
616
		 *
617
		 * @param array $results Array of related posts matched by ElasticSearch.
618
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
619
		 */
620
		return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
621
	}
622
623
	/**
624
	 * =========================
625
	 * PRIVATE UTILITY FUNCTIONS
626
	 * =========================
627
	 */
628
629
	/**
630
	 * Creates an array of ElasticSearch filters based on the post_id and args.
631
	 *
632
	 * @param int $post_id
633
	 * @param array $args
634
	 * @uses apply_filters, get_post_types, get_post_format_strings
635
	 * @return array
636
	 */
637
	protected function _get_es_filters_from_args( $post_id, array $args ) {
638
		$filters = array();
639
640
		/**
641
		 * Filter the terms used to search for Related Posts.
642
		 *
643
		 * @module related-posts
644
		 *
645
		 * @since 2.8.0
646
		 *
647
		 * @param array $args['has_terms'] Array of terms associated to the Related Posts.
648
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
649
		 */
650
		$args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
651
		if ( ! empty( $args['has_terms'] ) ) {
652
			foreach( (array)$args['has_terms'] as $term ) {
653
				if ( mb_strlen( $term->taxonomy ) ) {
654
					switch ( $term->taxonomy ) {
655
						case 'post_tag':
656
							$tax_fld = 'tag.slug';
657
							break;
658
						case 'category':
659
							$tax_fld = 'category.slug';
660
							break;
661
						default:
662
							$tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
663
							break;
664
					}
665
					$filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
666
				}
667
			}
668
		}
669
670
		/**
671
		 * Filter the Post Types where we search Related Posts.
672
		 *
673
		 * @module related-posts
674
		 *
675
		 * @since 2.8.0
676
		 *
677
		 * @param array $args['post_type'] Array of Post Types.
678
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
679
		 */
680
		$args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
681
		$valid_post_types = get_post_types();
682
		if ( is_array( $args['post_type'] ) ) {
683
			$sanitized_post_types = array();
684
			foreach ( $args['post_type'] as $pt ) {
685
				if ( in_array( $pt, $valid_post_types ) )
686
					$sanitized_post_types[] = $pt;
687
			}
688
			if ( ! empty( $sanitized_post_types ) )
689
				$filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
690
		} else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
691
			$filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
692
		}
693
694
		/**
695
		 * Filter the Post Formats where we search Related Posts.
696
		 *
697
		 * @module related-posts
698
		 *
699
		 * @since 3.3.0
700
		 *
701
		 * @param array $args['post_formats'] Array of Post Formats.
702
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
703
		 */
704
		$args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
705
		$valid_post_formats = get_post_format_strings();
706
		$sanitized_post_formats = array();
707
		foreach ( $args['post_formats'] as $pf ) {
708
			if ( array_key_exists( $pf, $valid_post_formats ) ) {
709
				$sanitized_post_formats[] = $pf;
710
			}
711
		}
712
		if ( ! empty( $sanitized_post_formats ) ) {
713
			$filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
714
		}
715
716
		/**
717
		 * Filter the date range used to search Related Posts.
718
		 *
719
		 * @module related-posts
720
		 *
721
		 * @since 2.8.0
722
		 *
723
		 * @param array $args['date_range'] Array of a month interval where we search Related Posts.
724
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
725
		 */
726
		$args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
727
		if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
728
			$args['date_range'] = array_map( 'intval', $args['date_range'] );
729
			if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
730
				$filters[] = array(
731
					'range' => array(
732
						'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
733
					)
734
				);
735
			}
736
		}
737
738
		/**
739
		 * Filter the Post IDs excluded from appearing in Related Posts.
740
		 *
741
		 * @module related-posts
742
		 *
743
		 * @since 2.9.0
744
		 *
745
		 * @param array $args['exclude_post_ids'] Array of Post IDs.
746
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
747
		 */
748
		$args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
749
		if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
750
			foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
751
				$exclude_post_id = (int)$exclude_post_id;
752
753
				if ( $exclude_post_id > 0 )
754
					$filters[] = array( 'not' => array( 'term' => array( 'post_id' => $exclude_post_id ) ) );
755
			}
756
		}
757
758
		return $filters;
759
	}
760
761
	/**
762
	 * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
763
	 *
764
	 * @param array $date_range
765
	 * @return array
766
	 */
767
	protected function _get_coalesced_range( array $date_range ) {
768
		$now = time();
769
		$coalesce_time = $this->_blog_id_wpcom % 86400;
770
		$current_time = $now - strtotime( 'today', $now );
771
772
		if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
773
			// Move back 1 period
774
			return array(
775
				'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
776
				'to'   => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
777
			);
778
		} else {
779
			// Use current period
780
			return array(
781
				'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
782
				'to'   => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
783
			);
784
		}
785
	}
786
787
	/**
788
	 * Generate and output ajax response for related posts API call.
789
	 * NOTE: Calls exit() to end all further processing after payload has been outputed.
790
	 *
791
	 * @param array $excludes array of post_ids to exclude
792
	 * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
793
	 * @return null
794
	 */
795
	protected function _action_frontend_init_ajax( array $excludes ) {
796
		define( 'DOING_AJAX', true );
797
798
		header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
799
		send_nosniff_header();
800
801
		$options = $this->get_options();
802
803
		if ( isset( $_GET['jetpackrpcustomize'] ) ) {
804
805
			// If we're in the customizer, add dummy content.
806
			$date_now = current_time( get_option( 'date_format' ) );
807
			$related_posts = array(
808
				array(
809
					'id'       => - 1,
810
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&h=200&crop=1',
811
					'url_meta' => array(
812
						'origin'   => 0,
813
						'position' => 0
814
					),
815
					'title'    => esc_html__( 'Big iPhone/iPad Update Now Available', 'jetpack' ),
816
					'date'     => $date_now,
817
					'format'   => false,
818
					'excerpt'  => esc_html__( 'It is that time of the year when devices are shiny again.', 'jetpack' ),
819
					'rel'      => 'nofollow',
820
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
821
					'img'      => array(
822
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/1-wpios-ipad-3-1-viewsite.png?w=350&h=200&crop=1',
823
						'width'  => 350,
824
						'height' => 200
825
					),
826
					'classes'  => array()
827
				),
828
				array(
829
					'id'       => - 1,
830
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&h=200&crop=1',
831
					'url_meta' => array(
832
						'origin'   => 0,
833
						'position' => 0
834
					),
835
					'title'    => esc_html__( 'The WordPress for Android App Gets a Big Facelift', 'jetpack' ),
836
					'date'     => $date_now,
837
					'format'   => false,
838
					'excerpt'  => esc_html__( 'Writing is new again in Android with the new WordPress app.' ),
839
					'rel'      => 'nofollow',
840
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
841
					'img'      => array(
842
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/wordpress-com-news-wordpress-for-android-ui-update2.jpg?w=350&h=200&crop=1',
843
						'width'  => 350,
844
						'height' => 200
845
					),
846
					'classes'  => array()
847
				),
848
				array(
849
					'id'       => - 1,
850
					'url'      => 'https://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&h=200&crop=1',
851
					'url_meta' => array(
852
						'origin'   => 0,
853
						'position' => 0
854
					),
855
					'title'    => esc_html__( 'Upgrade Focus, VideoPress for weddings', 'jetpack' ),
856
					'date'     => $date_now,
857
					'format'   => false,
858
					'excerpt'  => esc_html__( 'Weddings are in the spotlight now with VideoPress for weddings.' ),
859
					'rel'      => 'nofollow',
860
					'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
861
					'img'      => array(
862
						'src'    => 'https://jetpackme.files.wordpress.com/2014/08/videopresswedding.jpg?w=350&h=200&crop=1',
863
						'width'  => 350,
864
						'height' => 200
865
					),
866
					'classes'  => array()
867
				),
868
			);
869
			$options['size'] = 3;
870
		} else {
871
			$related_posts = $this->get_for_post_id(
872
				get_the_ID(),
873
				array(
874
					'exclude_post_ids' => $excludes,
875
				)
876
			);
877
		}
878
879
		$response = array(
880
			'version' => self::VERSION,
881
			'show_thumbnails' => (bool) $options['show_thumbnails'],
882
			'show_date' => (bool) $options['show_date'],
883
			'show_context' => (bool) $options['show_context'],
884
			'layout' => (string) $options['layout'],
885
			'headline' => (string) $options['headline'],
886
			'items' => array(),
887
		);
888
889
		if ( count( $related_posts ) == $options['size'] )
890
			$response['items'] = $related_posts;
891
892
		echo json_encode( $response );
893
894
		exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method _action_frontend_init_ajax() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
895
	}
896
897
	/**
898
	 * Returns a UTF-8 encoded array of post information for the given post_id
899
	 *
900
	 * @param int $post_id
901
	 * @param int $position
902
	 * @param int $origin The post id that this is related to
903
	 * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
904
	 * @return array
905
	 */
906
	protected function _get_related_post_data_for_post( $post_id, $position, $origin ) {
907
		$post = get_post( $post_id );
908
909
		return array(
910
			'id' => $post->ID,
911
			'url' => get_permalink( $post->ID ),
912
			'url_meta' => array( 'origin' => $origin, 'position' => $position ),
913
			'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),
914
			'date' => get_the_date( '', $post->ID ),
915
			'format' => get_post_format( $post->ID ),
916
			'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
917
			/**
918
			 * Filters the rel attribute for the Related Posts' links.
919
			 *
920
			 * @module related-posts
921
			 *
922
			 * @since 3.7.0
923
			 *
924
			 * @param string nofollow Link rel attribute for Related Posts' link. Default is nofollow.
925
			 * @param int $post->ID Post ID.
926
			 */
927
			'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', 'nofollow', $post->ID ),
928
			/**
929
			 * Filter the context displayed below each Related Post.
930
			 *
931
			 * @module related-posts
932
			 *
933
			 * @since 3.0.0
934
			 *
935
			 * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
936
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
937
			 */
938
			'context' => apply_filters(
939
				'jetpack_relatedposts_filter_post_context',
940
				$this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
941
				$post->ID
942
			),
943
			'img' => $this->_generate_related_post_image_params( $post->ID ),
944
			/**
945
			 * Filter the post css classes added on HTML markup.
946
			 *
947
			 * @module related-posts
948
			 *
949
			 * @since 3.8.0
950
			 *
951
			 * @param array array() CSS classes added on post HTML markup.
952
			 * @param string $post_id Post ID.
953
			 */
954
			'classes' => apply_filters(
955
				'jetpack_relatedposts_filter_post_css_classes',
956
				array(),
957
				$post->ID
958
			),
959
		);
960
	}
961
962
	/**
963
	 * Returns either the title or a small excerpt to use as title for post.
964
	 *
965
	 * @param string $post_title
966
	 * @param string $post_content
967
	 * @uses strip_shortcodes, wp_trim_words, __
968
	 * @return string
969
	 */
970
	protected function _get_title( $post_title, $post_content ) {
971
		if ( ! empty( $post_title ) ) {
972
			return wp_strip_all_tags( $post_title );
973
		}
974
975
		$post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
976
		if ( ! empty( $post_title ) ) {
977
			return $post_title;
978
		}
979
980
		return __( 'Untitled Post', 'jetpack' );
981
	}
982
983
	/**
984
	 * Returns a plain text post excerpt for title attribute of links.
985
	 *
986
	 * @param string $post_excerpt
987
	 * @param string $post_content
988
	 * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
989
	 * @return string
990
	 */
991
	protected function _get_excerpt( $post_excerpt, $post_content ) {
992
		if ( empty( $post_excerpt ) )
993
			$excerpt = $post_content;
994
		else
995
			$excerpt = $post_excerpt;
996
997
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
998
	}
999
1000
	/**
1001
	 * Generates the thumbnail image to be used for the post. Uses the
1002
	 * image as returned by Jetpack_PostImages::get_image()
1003
	 *
1004
	 * @param int $post_id
1005
	 * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
1006
	 * @return string
1007
	 */
1008
	protected function _generate_related_post_image_params( $post_id ) {
1009
		$options = $this->get_options();
1010
		$image_params = array(
1011
			'src' => '',
1012
			'width' => 0,
1013
			'height' => 0,
1014
		);
1015
1016
		if ( ! $options['show_thumbnails'] ) {
1017
			return $image_params;
1018
		}
1019
1020
		/**
1021
		 * Filter the size of the Related Posts images.
1022
		 *
1023
		 * @module related-posts
1024
		 *
1025
		 * @since 2.8.0
1026
		 *
1027
		 * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
1028
		 */
1029
		$thumbnail_size = apply_filters(
1030
			'jetpack_relatedposts_filter_thumbnail_size',
1031
			array( 'width' => 350, 'height' => 200 )
1032
		);
1033
		if ( !is_array( $thumbnail_size ) ) {
1034
			$thumbnail_size = array(
1035
				'width' => (int)$thumbnail_size,
1036
				'height' => (int)$thumbnail_size
1037
			);
1038
		}
1039
1040
		// Try to get post image
1041
		if ( class_exists( 'Jetpack_PostImages' ) ) {
1042
			$img_url = '';
1043
			$post_image = Jetpack_PostImages::get_image(
1044
				$post_id,
1045
				$thumbnail_size
1046
			);
1047
1048
			if ( is_array($post_image) ) {
1049
				$img_url = $post_image['src'];
1050
			} elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
1051
				$media = Jetpack_Media_Summary::get( $post_id );
1052
1053
				if ( is_array($media) && !empty( $media['image'] ) ) {
1054
					$img_url = $media['image'];
1055
				}
1056
			}
1057
1058
			if ( !empty( $img_url ) ) {
1059
				$image_params['width'] = $thumbnail_size['width'];
1060
				$image_params['height'] = $thumbnail_size['height'];
1061
				$image_params['src'] = Jetpack_PostImages::fit_image_url(
1062
					$img_url,
1063
					$thumbnail_size['width'],
1064
					$thumbnail_size['height']
1065
				);
1066
			}
1067
		}
1068
1069
		return $image_params;
1070
	}
1071
1072
	/**
1073
	 * Returns the string UTF-8 encoded
1074
	 *
1075
	 * @param string $text
1076
	 * @return string
1077
	 */
1078
	protected function _to_utf8( $text ) {
1079
		if ( $this->_convert_charset ) {
1080
			return iconv( $this->_blog_charset, 'UTF-8', $text );
1081
		} else {
1082
			return $text;
1083
		}
1084
	}
1085
1086
	/**
1087
	 * =============================================
1088
	 * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
1089
	 * =============================================
1090
	 */
1091
1092
	/**
1093
	 * Workhorse method to return array of related posts matched by ElasticSearch.
1094
	 *
1095
	 * @param int $post_id
1096
	 * @param int $size
1097
	 * @param array $filters
1098
	 * @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
1099
	 * @return array
1100
	 */
1101
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1102
		$hits = $this->_filter_non_public_posts(
1103
			$this->_get_related_post_ids(
1104
				$post_id,
1105
				$size,
1106
				$filters
1107
			)
1108
		);
1109
1110
		/**
1111
		 * Filter the Related Posts matched by ElasticSearch.
1112
		 *
1113
		 * @module related-posts
1114
		 *
1115
		 * @since 2.9.0
1116
		 *
1117
		 * @param array $hits Array of Post IDs matched by ElasticSearch.
1118
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1119
		 */
1120
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1121
1122
		$related_posts = array();
1123
		foreach ( $hits as $i => $hit ) {
1124
			$related_posts[] = $this->_get_related_post_data_for_post( $hit['id'], $i, $post_id );
1125
		}
1126
		return $related_posts;
1127
	}
1128
1129
	/**
1130
	 * Get array of related posts matched by ElasticSearch.
1131
	 *
1132
	 * @param int $post_id
1133
	 * @param int $size
1134
	 * @param array $filters
1135
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
1136
	 * @return array
1137
	 */
1138
	protected function _get_related_post_ids( $post_id, $size, array $filters ) {
1139
		$now_ts = time();
1140
		$cache_meta_key = '_jetpack_related_posts_cache';
1141
1142
		$body = array(
1143
			'size' => (int) $size,
1144
		);
1145
1146
		if ( !empty( $filters ) )
1147
			$body['filter'] = array( 'and' => $filters );
1148
1149
		// Build cache key
1150
		$cache_key = md5( serialize( $body ) );
1151
1152
		// Load all cached values
1153
		if ( wp_using_ext_object_cache() ) {
1154
			$transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
1155
			$cache = get_transient( $transient_name );
1156
			if ( false !== $cache ) {
1157
				return $cache;
1158
			}
1159
		} else {
1160
			$cache = get_post_meta( $post_id, $cache_meta_key, true );
1161
1162
			if ( empty( $cache ) )
1163
				$cache = array();
1164
1165
1166
			// Cache is valid! Return cached value.
1167
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
1168
				return $cache[ $cache_key ][ 'payload' ];
1169
			}
1170
		}
1171
1172
		$response = wp_remote_post(
1173
			"https://public-api.wordpress.com/rest/v1/sites/{$this->_blog_id_wpcom}/posts/$post_id/related/",
1174
			array(
1175
				'timeout' => 10,
1176
				'user-agent' => 'jetpack_related_posts',
1177
				'sslverify' => true,
1178
				'body' => $body,
1179
			)
1180
		);
1181
1182
		// Oh no... return nothing don't cache errors.
1183
		if ( is_wp_error( $response ) ) {
1184
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
1185
				return $cache[ $cache_key ][ 'payload' ]; // return stale
1186
			else
1187
				return array();
1188
		}
1189
1190
		$results = json_decode( wp_remote_retrieve_body( $response ), true );
1191
		$related_posts = array();
1192
		if ( is_array( $results ) && !empty( $results['hits'] ) ) {
1193
			foreach( $results['hits'] as $hit ) {
1194
				$related_posts[] = array(
1195
					'id' => $hit['fields']['post_id'],
1196
				);
1197
			}
1198
		}
1199
1200
		// An empty array might indicate no related posts or that posts
1201
		// are not yet synced to WordPress.com, so we cache for only 1
1202
		// minute in this case
1203
		if ( empty( $related_posts ) ) {
1204
			$cache_ttl = 60;
1205
		} else {
1206
			$cache_ttl = 12 * HOUR_IN_SECONDS;
1207
		}
1208
1209
		// Update cache
1210
		if ( wp_using_ext_object_cache() ) {
1211
			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...
1212
		} else {
1213
			// Copy all valid cache values
1214
			$new_cache = array();
1215
			foreach ( $cache as $k => $v ) {
1216
				if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
1217
					$new_cache[ $k ] = $v;
1218
				}
1219
			}
1220
1221
			// Set new cache value
1222
			$cache_expires = $cache_ttl + $now_ts;
1223
			$new_cache[ $cache_key ] = array(
1224
				'expires' => $cache_expires,
1225
				'payload' => $related_posts,
1226
			);
1227
			update_post_meta( $post_id, $cache_meta_key, $new_cache );
1228
		}
1229
1230
		return $related_posts;
1231
	}
1232
1233
	/**
1234
	 * Filter out any hits that are not public anymore.
1235
	 *
1236
	 * @param array $related_posts
1237
	 * @uses get_post_stati, get_post_status
1238
	 * @return array
1239
	 */
1240
	protected function _filter_non_public_posts( array $related_posts ) {
1241
		$public_stati = get_post_stati( array( 'public' => true ) );
1242
1243
		$filtered = array();
1244
		foreach ( $related_posts as $hit ) {
1245
			if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
1246
				$filtered[] = $hit;
1247
			}
1248
		}
1249
		return $filtered;
1250
	}
1251
1252
	/**
1253
	 * Generates a context for the related content (second line in related post output).
1254
	 * Order of importance:
1255
	 *   - First category (Not 'Uncategorized')
1256
	 *   - First post tag
1257
	 *   - Number of comments
1258
	 *
1259
	 * @param int $post_id
1260
	 * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
1261
	 * @return string
1262
	 */
1263
	protected function _generate_related_post_context( $post_id ) {
1264
		$categories = get_the_category( $post_id );
1265 View Code Duplication
		if ( is_array( $categories ) ) {
1266
			foreach ( $categories as $category ) {
1267
				if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
1268
					$post_cat_context = sprintf(
1269
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1270
						$category->name
1271
					);
1272
					/**
1273
					 * Filter the "In Category" line displayed in the post context below each Related Post.
1274
					 *
1275
					 * @module related-posts
1276
					 *
1277
					 * @since 3.2.0
1278
					 *
1279
					 * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
1280
					 * @param array $category Array containing information about the category.
1281
					 */
1282
					return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
1283
				}
1284
			}
1285
		}
1286
1287
		$tags = get_the_terms( $post_id, 'post_tag' );
1288 View Code Duplication
		if ( is_array( $tags ) ) {
1289
			foreach ( $tags as $tag ) {
1290
				if ( '' != trim( $tag->name ) ) {
1291
					$post_tag_context = sprintf(
1292
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1293
						$tag->name
1294
					);
1295
					/**
1296
					 * Filter the "In Tag" line displayed in the post context below each Related Post.
1297
					 *
1298
					 * @module related-posts
1299
					 *
1300
					 * @since 3.2.0
1301
					 *
1302
					 * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
1303
					 * @param array $tag Array containing information about the tag.
1304
					 */
1305
					return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
1306
				}
1307
			}
1308
		}
1309
1310
		$comment_count = get_comments_number( $post_id );
1311
		if ( $comment_count > 0 ) {
1312
			return sprintf(
1313
				_n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
1314
				number_format_i18n( $comment_count )
1315
			);
1316
		}
1317
1318
		return __( 'Similar post', 'jetpack' );
1319
	}
1320
1321
	/**
1322
	 * Logs clicks for clickthrough analysis and related result tuning.
1323
	 *
1324
	 * @return null
1325
	 */
1326
	protected function _log_click( $post_id, $to_post_id, $link_position ) {
0 ignored issues
show
Unused Code introduced by
The parameter $post_id 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...
Unused Code introduced by
The parameter $to_post_id 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...
Unused Code introduced by
The parameter $link_position 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...
1327
1328
	}
1329
1330
	/**
1331
	 * Determines if the current post is able to use related posts.
1332
	 *
1333
	 * @uses self::get_options, is_admin, is_single, apply_filters
1334
	 * @return bool
1335
	 */
1336
	protected function _enabled_for_request() {
1337
		// Default to enabled
1338
		$enabled = true;
1339
1340
		// Must have feature enabled
1341
		$options = $this->get_options();
1342
		if ( ! $options['enabled'] ) {
1343
			$enabled = false;
1344
		}
1345
1346
		// Only run for frontend pages
1347
		if ( is_admin() ) {
1348
			$enabled = false;
1349
		}
1350
1351
		// Only run for standalone posts
1352
		if ( ! is_single() ) {
1353
			$enabled = false;
1354
		}
1355
1356
		/**
1357
		 * Filter the Enabled value to allow related posts to be shown on pages as well.
1358
		 *
1359
		 * @module related-posts
1360
		 *
1361
		 * @since 3.3.0
1362
		 *
1363
		 * @param bool $enabled Should Related Posts be enabled on the current page.
1364
		 */
1365
		return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
1366
	}
1367
1368
	/**
1369
	 * Adds filters and enqueues assets.
1370
	 *
1371
	 * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
1372
	 * @return null
1373
	 */
1374
	protected function _action_frontend_init_page() {
1375
		$this->_enqueue_assets( true, true );
1376
		$this->_setup_shortcode();
1377
1378
		add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
1379
	}
1380
1381
	/**
1382
	 * Enqueues assets needed to do async loading of related posts.
1383
	 *
1384
	 * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
1385
	 * @return null
1386
	 */
1387
	protected function _enqueue_assets( $script, $style ) {
1388
		$dependencies = is_customize_preview() ? array( 'customize-base' ) : array( 'jquery' );
1389
		if ( $script ) {
1390
			wp_enqueue_script( 'jetpack_related-posts', plugins_url( 'related-posts.js', __FILE__ ), $dependencies, self::VERSION );
1391
			$related_posts_js_options = array(
1392
				/**
1393
				 * Filter each Related Post Heading structure.
1394
				 *
1395
				 * @since 4.0.0
1396
				 *
1397
				 * @param string $str Related Post Heading structure. Default to h4.
1398
				 */
1399
				'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
1400
			);
1401
			wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
1402
		}
1403
		if ( $style ){
1404
			if( is_rtl() ) {
1405
				wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'rtl/related-posts-rtl.css', __FILE__ ), array(), self::VERSION );
1406
			} else {
1407
				wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
1408
			}
1409
		}
1410
	}
1411
1412
	/**
1413
	 * Sets up the shortcode processing.
1414
	 *
1415
	 * @uses add_filter, add_shortcode
1416
	 * @return null
1417
	 */
1418
	protected function _setup_shortcode() {
1419
		add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
1420
1421
		add_shortcode( self::SHORTCODE, array( $this, 'get_target_html' ) );
1422
	}
1423
1424
	protected function _allow_feature_toggle() {
1425
		if ( null === $this->_allow_feature_toggle ) {
1426
			/**
1427
			 * Filter the display of the Related Posts toggle in Settings > Reading.
1428
			 *
1429
			 * @module related-posts
1430
			 *
1431
			 * @since 2.8.0
1432
			 *
1433
			 * @param bool false Display a feature toggle. Default to false.
1434
			 */
1435
			$this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
1436
		}
1437
		return $this->_allow_feature_toggle;
1438
	}
1439
1440
	/**
1441
	 * ===================================================
1442
	 * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
1443
	 * ===================================================
1444
	 */
1445
1446
	/**
1447
	 * Add Related Posts to the REST API Post response.
1448
	 *
1449
	 * @since 4.4.0
1450
	 *
1451
	 * @action rest_api_init
1452
	 * @uses register_rest_field, self::rest_get_related_posts
1453
	 * @return null
1454
	 */
1455
	public function rest_register_related_posts() {
1456
		register_rest_field( 'post',
1457
			'jetpack-related-posts',
1458
			array(
1459
				'get_callback' => array( $this, 'rest_get_related_posts' ),
1460
				'update_callback' => null,
1461
				'schema'          => null,
1462
			)
1463
		);
1464
	}
1465
1466
	/**
1467
	 * Build an array of Related Posts.
1468
	 *
1469
	 * @since 4.4.0
1470
	 *
1471
	 * @param array $object Details of current post.
1472
	 * @param string $field_name Name of field.
1473
	 * @param WP_REST_Request $request Current request
1474
	 *
1475
	 * @uses self::get_for_post_id
1476
	 *
1477
	 * @return array
1478
	 */
1479
	public function rest_get_related_posts( $object, $field_name, $request ) {
0 ignored issues
show
Unused Code introduced by
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...
Unused Code introduced by
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...
1480
		return $this->get_for_post_id( $object['id'], array() );
1481
	}
1482
}
1483
1484
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
1485
	protected $_query_name;
1486
1487
	/**
1488
	 * Allows callers of this class to tag each query with a unique name for tracking purposes.
1489
	 *
1490
	 * @param string $name
1491
	 * @return Jetpack_RelatedPosts_Raw
1492
	 */
1493
	public function set_query_name( $name ) {
1494
		$this->_query_name = (string) $name;
1495
		return $this;
1496
	}
1497
1498
	/**
1499
	 * The raw related posts class can be used by other plugins or themes
1500
	 * to get related content. This class wraps the existing RelatedPosts
1501
	 * logic thus we never want to add anything to the DOM or do anything
1502
	 * for event hooks. We will also not present any settings for this
1503
	 * class and keep it enabled as calls to this class is done
1504
	 * programmatically.
1505
	 */
1506
	public function action_admin_init() {}
1507
	public function action_frontend_init() {}
1508
	public function get_options() {
1509
		return array(
1510
			'enabled' => true,
1511
		);
1512
	}
1513
1514
	/**
1515
	 * Workhorse method to return array of related posts ids matched by ElasticSearch.
1516
	 *
1517
	 * @param int $post_id
1518
	 * @param int $size
1519
	 * @param array $filters
1520
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
1521
	 * @return array
1522
	 */
1523
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1524
		$hits = $this->_filter_non_public_posts(
1525
			$this->_get_related_post_ids(
1526
				$post_id,
1527
				$size,
1528
				$filters
1529
			)
1530
		);
1531
1532
		/** This filter is already documented in modules/related-posts/related-posts.php */
1533
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1534
1535
		return $hits;
1536
	}
1537
}
1538