Completed
Push — try/mocked-data-for-related-po... ( aa8915 )
by
unknown
15:42
created

Jetpack_RelatedPosts::get_mock_results()   C

Complexity

Conditions 10
Paths 96

Size

Total Lines 166

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 96
nop 2
dl 0
loc 166
rs 6.1333
c 0
b 0
f 0

How to fix   Long Method    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
3
use Automattic\Jetpack\Assets;
4
use Automattic\Jetpack\Sync\Settings;
5
6
class Jetpack_RelatedPosts {
7
	const VERSION   = '20191011';
8
	const SHORTCODE = 'jetpack-related-posts';
9
10
	private static $instance     = null;
11
	private static $instance_raw = null;
12
13
	/**
14
	 * Creates and returns a static instance of Jetpack_RelatedPosts.
15
	 *
16
	 * @return Jetpack_RelatedPosts
17
	 */
18
	public static function init() {
19
		if ( ! self::$instance ) {
20
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init' ) ) {
21
				self::$instance = WPCOM_RelatedPosts::init();
22
			} else {
23
				self::$instance = new Jetpack_RelatedPosts();
24
			}
25
		}
26
27
		return self::$instance;
28
	}
29
30
	/**
31
	 * Creates and returns a static instance of Jetpack_RelatedPosts_Raw.
32
	 *
33
	 * @return Jetpack_RelatedPosts
34
	 */
35
	public static function init_raw() {
36
		if ( ! self::$instance_raw ) {
37
			if ( class_exists('WPCOM_RelatedPosts') && method_exists( 'WPCOM_RelatedPosts', 'init_raw' ) ) {
38
				self::$instance_raw = WPCOM_RelatedPosts::init_raw();
39
			} else {
40
				self::$instance_raw = new Jetpack_RelatedPosts_Raw();
41
			}
42
		}
43
44
		return self::$instance_raw;
45
	}
46
47
	protected $_options;
48
	protected $_allow_feature_toggle;
49
	protected $_blog_charset;
50
	protected $_convert_charset;
51
	protected $_previous_post_id;
52
	protected $_found_shortcode = false;
53
54
	/**
55
	 * Constructor for Jetpack_RelatedPosts.
56
	 *
57
	 * @uses get_option, add_action, apply_filters
58
	 *
59
	 * @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...
60
	 */
61
	public function __construct() {
62
		$this->_blog_charset = get_option( 'blog_charset' );
63
		$this->_convert_charset = ( function_exists( 'iconv' ) && ! preg_match( '/^utf\-?8$/i', $this->_blog_charset ) );
64
65
		add_action( 'admin_init', array( $this, 'action_admin_init' ) );
66
		add_action( 'wp', array( $this, 'action_frontend_init' ) );
67
68
		if ( ! class_exists( 'Jetpack_Media_Summary' ) ) {
69
			jetpack_require_lib( 'class.media-summary' );
70
		}
71
72
		// Add Related Posts to the REST API Post response.
73
		add_action( 'rest_api_init', array( $this, 'rest_register_related_posts' ) );
74
75
		if ( isset( $_GET['jetpackrpcustomize'] ) || Jetpack::is_development_mode() ) {
76
			add_filter( 'jetpack_relatedposts_pre_results', array( $this, 'mock_results' ), 10, 3 );
77
		}
78
79
		jetpack_register_block(
80
			'jetpack/related-posts',
81
			array(
82
				'render_callback' => array( $this, 'render_block' ),
83
			)
84
		);
85
	}
86
87
	protected function get_blog_id() {
88
		return Jetpack_Options::get_option( 'id' );
89
	}
90
91
	/**
92
	 * =================
93
	 * ACTIONS & FILTERS
94
	 * =================
95
	 */
96
97
	/**
98
	 * Add a checkbox field to Settings > Reading for enabling related posts.
99
	 *
100
	 * @action admin_init
101
	 * @uses add_settings_field, __, register_setting, add_action
102
	 * @return null
103
	 */
104
	public function action_admin_init() {
105
106
		// Add the setting field [jetpack_relatedposts] and place it in Settings > Reading
107
		add_settings_field( 'jetpack_relatedposts', '<span id="jetpack_relatedposts">' . __( 'Related posts', 'jetpack' ) . '</span>', array( $this, 'print_setting_html' ), 'reading' );
108
		register_setting( 'reading', 'jetpack_relatedposts', array( $this, 'parse_options' ) );
109
		add_action('admin_head', array( $this, 'print_setting_head' ) );
110
111
		if( 'options-reading.php' == $GLOBALS['pagenow'] ) {
112
			// Enqueue style for live preview on the reading settings page
113
			$this->_enqueue_assets( false, true );
114
		}
115
	}
116
117
	/**
118
	 * Load related posts assets if it's a elegiable front end page or execute search and return JSON if it's an endpoint request.
119
	 *
120
	 * @global $_GET
121
	 * @action wp
122
	 * @uses add_shortcode, get_the_ID
123
	 * @returns null
124
	 */
125
	public function action_frontend_init() {
126
		// Add a shortcode handler that outputs nothing, this gets overridden later if we can display related content
127
		add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html_unsupported' ) );
128
129
		if ( ! $this->_enabled_for_request() )
130
			return;
131
132
		if ( isset( $_GET['relatedposts'] ) ) {
133
			$excludes = $this->parse_numeric_get_arg( 'relatedposts_exclude' );
134
			$this->_action_frontend_init_ajax( $excludes );
135
		} else {
136
			if ( isset( $_GET['relatedposts_hit'], $_GET['relatedposts_origin'], $_GET['relatedposts_position'] ) ) {
137
				$this->_log_click( $_GET['relatedposts_origin'], get_the_ID(), $_GET['relatedposts_position'] );
138
				$this->_previous_post_id = (int) $_GET['relatedposts_origin'];
139
			}
140
141
			$this->_action_frontend_init_page();
142
		}
143
144
	}
145
146
	/**
147
	 * Render insertion point.
148
	 *
149
	 * @since 4.2.0
150
	 *
151
	 * @return string
152
	 */
153
	public function get_headline() {
154
		$options = $this->get_options();
155
156
		if ( $options['show_headline'] ) {
157
			$headline = sprintf(
158
				/** This filter is already documented in modules/sharedaddy/sharing-service.php */
159
				apply_filters( 'jetpack_sharing_headline_html', '<h3 class="jp-relatedposts-headline"><em>%s</em></h3>', esc_html( $options['headline'] ), 'related-posts' ),
160
				esc_html( $options['headline'] )
161
			);
162
		} else {
163
			$headline = '';
164
		}
165
		return $headline;
166
	}
167
168
	/**
169
	 * Adds a target to the post content to load related posts into if a shortcode for it did not already exist.
170
	 * Will skip adding the target if the post content contains a Related Posts block or if the 'get_the_excerpt'
171
	 * hook is in the current filter list.
172
	 *
173
	 * @filter the_content
174
	 *
175
	 * @param string $content Post content.
176
	 *
177
	 * @returns string
178
	 */
179
	public function filter_add_target_to_dom( $content ) {
180
		if ( has_block( 'jetpack/related-posts' ) ) {
181
			return $content;
182
		}
183
184
		if ( ! $this->_found_shortcode && ! doing_filter( 'get_the_excerpt' ) ) {
185
			if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
186
				$content .= "\n" . $this->get_server_rendered_html();
187
			} else {
188
				$content .= "\n" . $this->get_client_rendered_html();
189
			}
190
		}
191
192
		return $content;
193
	}
194
195
	/**
196
	 * Render static markup based on the Gutenberg block code
197
	 *
198
	 * @return string Rendered related posts HTML.
199
	 */
200
	public function get_server_rendered_html() {
201
		$rp_settings       = Jetpack_Options::get_option( 'relatedposts', array() );
202
		$block_rp_settings = array(
203
			'displayThumbnails' => $rp_settings['show_thumbnails'],
204
			'showHeadline'      => $rp_settings['show_headline'],
205
			'displayDate'       => isset( $rp_settings['show_date'] ) ? (bool) $rp_settings['show_date'] : true,
206
			'displayContext'    => isset( $rp_settings['show_context'] ) && $rp_settings['show_context'],
207
			'postLayout'        => isset( $rp_settings['layout'] ) ? $rp_settings['layout'] : 'grid',
208
			'postsToShow'       => isset( $rp_settings['size'] ) ? $rp_settings['size'] : 3,
209
			/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
210
			'headline'          => apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() ),
211
		);
212
213
		return $this->render_block( $block_rp_settings );
214
	}
215
216
	/**
217
	 * Looks for our shortcode on the unfiltered content, this has to execute early.
218
	 *
219
	 * @filter the_content
220
	 * @param string $content
221
	 * @uses has_shortcode
222
	 * @returns string
223
	 */
224
	public function test_for_shortcode( $content ) {
225
		$this->_found_shortcode = has_shortcode( $content, self::SHORTCODE );
226
227
		return $content;
228
	}
229
230
	/**
231
	 * Returns the HTML for the related posts section.
232
	 *
233
	 * @uses esc_html__, apply_filters
234
	 * @returns string
235
	 */
236
	public function get_client_rendered_html() {
237
		if ( Settings::is_syncing() ) {
238
			return '';
239
		}
240
241
		/**
242
		 * Filter the Related Posts headline.
243
		 *
244
		 * @module related-posts
245
		 *
246
		 * @since 3.0.0
247
		 *
248
		 * @param string $headline Related Posts heading.
249
		 */
250
		$headline = apply_filters( 'jetpack_relatedposts_filter_headline', $this->get_headline() );
251
252
		if ( $this->_previous_post_id ) {
253
			$exclude = "data-exclude='{$this->_previous_post_id}'";
254
		} else {
255
			$exclude = "";
256
		}
257
258
		return <<<EOT
259
<div id='jp-relatedposts' class='jp-relatedposts' $exclude>
260
	$headline
261
</div>
262
EOT;
263
	}
264
265
	/**
266
	 * 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.
267
	 *
268
	 * @returns string
269
	 */
270
	public function get_client_rendered_html_unsupported() {
271
		if ( Settings::is_syncing() ) {
272
			return '';
273
		}
274
		return "\n\n<!-- Jetpack Related Posts is not supported in this context. -->\n\n";
275
	}
276
277
	/**
278
	 * ===============
279
	 * GUTENBERG BLOCK
280
	 * ===============
281
	 */
282
283
	/**
284
	 * Echoes out items for the Gutenberg block
285
	 *
286
	 * @param array $related_post The post oject.
287
	 * @param array $block_attributes The block attributes.
288
	 */
289
	public function render_block_item( $related_post, $block_attributes ) {
290
		$instance_id = 'related-posts-item-' . uniqid();
291
		$label_id    = $instance_id . '-label';
292
293
		$item_markup = sprintf(
294
			'<ul id="%1$s" aria-labelledby="%2$s" class="jp-related-posts-i2__post" role="menuitem">',
295
			esc_attr( $instance_id ),
296
			esc_attr( $label_id )
297
		);
298
299
		$item_markup .= sprintf(
300
			'<li class="jp-related-posts-i2__post-link"><a id="%1$s" href="%2$s" %4$s>%3$s</a></li>',
301
			esc_attr( $label_id ),
302
			esc_url( $related_post['url'] ),
303
			esc_attr( $related_post['title'] ),
304
			( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' )
305
		);
306
307
		if ( ! empty( $block_attributes['show_thumbnails'] ) && ! empty( $related_post['img']['src'] ) ) {
308
			$img_link = sprintf(
309
				'<li class="jp-related-posts-i2__post-img-link"><a href="%1$s" %2$s><img src="%3$s" width="%4$s" alt="%5$s" /></a></li>',
310
				esc_url( $related_post['url'] ),
311
				( ! empty( $related_post['rel'] ) ? 'rel="' . esc_attr( $related_post['rel'] ) . '"' : '' ),
312
				esc_url( $related_post['img']['src'] ),
313
				esc_attr( $related_post['img']['width'] ),
314
				esc_attr( $related_post['img']['alt_text'] )
315
			);
316
317
			$item_markup .= $img_link;
318
		}
319
320
		if ( $block_attributes['show_date'] ) {
321
			$date_tag = sprintf(
322
				'<li class="jp-related-posts-i2__post-date">%1$s</li>',
323
				esc_html( $related_post['date'] )
324
			);
325
326
			$item_markup .= $date_tag;
327
		}
328
329
		if ( ( $block_attributes['show_context'] ) && ! empty( $related_post['context'] ) ) {
330
			$context_tag = sprintf(
331
				'<li class="jp-related-posts-i2__post-context">%1$s</li>',
332
				esc_html( $related_post['context'] )
333
			);
334
335
			$item_markup .= $context_tag;
336
		}
337
338
		$item_markup .= '</ul>';
339
340
		return $item_markup;
341
	}
342
343
	/**
344
	 * Render a related posts row.
345
	 *
346
	 * @param array $posts The posts to render into the row.
347
	 * @param array $block_attributes Block attributes.
348
	 */
349
	public function render_block_row( $posts, $block_attributes ) {
350
		$rows_markup = '';
351
		foreach ( $posts as $post ) {
352
			$rows_markup .= $this->render_block_item( $post, $block_attributes );
353
		}
354
		return sprintf(
355
			'<div class="jp-related-posts-i2__row" data-post-count="%1$s">%2$s</div>',
356
			count( $posts ),
357
			$rows_markup
358
		);
359
	}
360
361
	/**
362
	 * Render the related posts markup.
363
	 *
364
	 * @param array $attributes Block attributes.
365
	 * @return string
366
	 */
367
	public function render_block( $attributes ) {
368
		$block_attributes = array(
369
			'headline'        => isset( $attributes['headline'] ) ? $attributes['headline'] : null,
370
			'show_thumbnails' => isset( $attributes['displayThumbnails'] ) && $attributes['displayThumbnails'],
371
			'show_date'       => isset( $attributes['displayDate'] ) ? (bool) $attributes['displayDate'] : true,
372
			'show_context'    => isset( $attributes['displayContext'] ) && $attributes['displayContext'],
373
			'layout'          => isset( $attributes['postLayout'] ) && 'list' === $attributes['postLayout'] ? $attributes['postLayout'] : 'grid',
374
			'size'            => ! empty( $attributes['postsToShow'] ) ? absint( $attributes['postsToShow'] ) : 3,
375
		);
376
377
		$excludes = $this->parse_numeric_get_arg( 'relatedposts_origin' );
378
379
		$related_posts = $this->get_for_post_id(
380
			get_the_ID(),
381
			array(
382
				'size'             => $block_attributes['size'],
383
				'exclude_post_ids' => $excludes,
384
			)
385
		);
386
387
		$display_lower_row = $block_attributes['size'] > 3;
388
389
		if ( empty( $related_posts ) ) {
390
			return '';
391
		}
392
393
		switch ( count( $related_posts ) ) {
394
			case 2:
395
			case 4:
396
			case 5:
397
				$top_row_end = 2;
398
				break;
399
400
			default:
401
				$top_row_end = 3;
402
				break;
403
		}
404
405
		$upper_row_posts = array_slice( $related_posts, 0, $top_row_end );
406
		$lower_row_posts = array_slice( $related_posts, $top_row_end );
407
408
		$rows_markup = $this->render_block_row( $upper_row_posts, $block_attributes );
409
		if ( $display_lower_row ) {
410
			$rows_markup .= $this->render_block_row( $lower_row_posts, $block_attributes );
411
		}
412
413
		/*
414
		 * Below is a hack to get the block content to render correctly.
415
		 *
416
		 * This functionality should be covered in /inc/blocks.php but due to an error,
417
		 * this has not been fixed as of this writing.
418
		 *
419
		 * Alda has submitted a patch to Core in order to have this issue fixed at
420
		 * https://core.trac.wordpress.org/ticket/45495 and
421
		 * made it into WordPress 5.2.
422
		 *
423
		 * @todo update when WP 5.2 is the minimum support version.
424
		 */
425
		$priority = has_filter( 'the_content', 'wpautop' );
426
		remove_filter( 'the_content', 'wpautop', $priority );
427
		add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
428
429
		return sprintf(
430
			'<nav class="jp-relatedposts-i2" data-layout="%1$s">%2$s%3$s</nav>',
431
			esc_attr( $block_attributes['layout'] ),
432
			$block_attributes['headline'],
433
			$rows_markup
434
		);
435
	}
436
437
	/**
438
	 * ========================
439
	 * PUBLIC UTILITY FUNCTIONS
440
	 * ========================
441
	 */
442
443
	/**
444
	 * Parse a numeric GET variable to an array of values.
445
	 *
446
	 * @since 6.9.0
447
	 *
448
	 * @uses absint
449
	 *
450
	 * @param string $arg Name of the GET variable.
451
	 * @return array $result Parsed value(s)
452
	 */
453
	public function parse_numeric_get_arg( $arg ) {
454
		$result = array();
455
456
		if ( isset( $_GET[ $arg ] ) ) {
457
			if ( is_string( $_GET[ $arg ] ) ) {
458
				$result = explode( ',', $_GET[ $arg ] );
459
			} elseif ( is_array( $_GET[ $arg ] ) ) {
460
				$result = array_values( $_GET[ $arg ] );
461
			}
462
463
			$result = array_unique( array_filter( array_map( 'absint', $result ) ) );
464
		}
465
466
		return $result;
467
	}
468
469
	/**
470
	 * Gets options set for Jetpack_RelatedPosts and merge with defaults.
471
	 *
472
	 * @uses Jetpack_Options::get_option, apply_filters
473
	 * @return array
474
	 */
475
	public function get_options() {
476
		if ( null === $this->_options ) {
477
			$this->_options = Jetpack_Options::get_option( 'relatedposts', array() );
478
			if ( ! is_array( $this->_options ) )
479
				$this->_options = array();
480
			if ( ! isset( $this->_options['enabled'] ) )
481
				$this->_options['enabled'] = true;
482
			if ( ! isset( $this->_options['show_headline'] ) )
483
				$this->_options['show_headline'] = true;
484
			if ( ! isset( $this->_options['show_thumbnails'] ) )
485
				$this->_options['show_thumbnails'] = false;
486
			if ( ! isset( $this->_options['show_date'] ) ) {
487
				$this->_options['show_date'] = true;
488
			}
489
			if ( ! isset( $this->_options['show_context'] ) ) {
490
				$this->_options['show_context'] = true;
491
			}
492
			if ( ! isset( $this->_options['layout'] ) ) {
493
				$this->_options['layout'] = 'grid';
494
			}
495
			if ( ! isset( $this->_options['headline'] ) ) {
496
				$this->_options['headline'] = esc_html__( 'Related', 'jetpack' );
497
			}
498
			if ( empty( $this->_options['size'] ) || (int)$this->_options['size'] < 1 )
499
				$this->_options['size'] = 3;
500
501
			/**
502
			 * Filter Related Posts basic options.
503
			 *
504
			 * @module related-posts
505
			 *
506
			 * @since 2.8.0
507
			 *
508
			 * @param array $this->_options Array of basic Related Posts options.
509
			 */
510
			$this->_options = apply_filters( 'jetpack_relatedposts_filter_options', $this->_options );
511
		}
512
513
		return $this->_options;
514
	}
515
516
	public function get_option( $option_name ) {
517
		$options = $this->get_options();
518
519
		if ( isset( $options[ $option_name ] ) ) {
520
			return $options[ $option_name ];
521
		}
522
523
		return false;
524
	}
525
526
	/**
527
	 * Parses input and returns normalized options array.
528
	 *
529
	 * @param array $input
530
	 * @uses self::get_options
531
	 * @return array
532
	 */
533
	public function parse_options( $input ) {
534
		$current = $this->get_options();
535
536
		if ( !is_array( $input ) )
537
			$input = array();
538
539
		if (
540
			! isset( $input['enabled'] )
541
			|| isset( $input['show_date'] )
542
			|| isset( $input['show_context'] )
543
			|| isset( $input['layout'] )
544
			|| isset( $input['headline'] )
545
			) {
546
			$input['enabled'] = '1';
547
		}
548
549
		if ( '1' == $input['enabled'] ) {
550
			$current['enabled'] = true;
551
			$current['show_headline'] = ( isset( $input['show_headline'] ) && '1' == $input['show_headline'] );
552
			$current['show_thumbnails'] = ( isset( $input['show_thumbnails'] ) && '1' == $input['show_thumbnails'] );
553
			$current['show_date'] = ( isset( $input['show_date'] ) && '1' == $input['show_date'] );
554
			$current['show_context'] = ( isset( $input['show_context'] ) && '1' == $input['show_context'] );
555
			$current['layout'] = isset( $input['layout'] ) && in_array( $input['layout'], array( 'grid', 'list' ), true ) ? $input['layout'] : 'grid';
556
			$current['headline'] = isset( $input['headline'] ) ? $input['headline'] : esc_html__( 'Related', 'jetpack' );
557
		} else {
558
			$current['enabled'] = false;
559
		}
560
561
		if ( isset( $input['size'] ) && (int)$input['size'] > 0 )
562
			$current['size'] = (int)$input['size'];
563
		else
564
			$current['size'] = null;
565
566
		return $current;
567
	}
568
569
	/**
570
	 * HTML for admin settings page.
571
	 *
572
	 * @uses self::get_options, checked, esc_html__
573
	 * @returns null
574
	 */
575
	public function print_setting_html() {
576
		$options = $this->get_options();
577
578
		$ui_settings_template = <<<EOT
579
<p class="description">%s</p>
580
<ul id="settings-reading-relatedposts-customize">
581
	<li>
582
		<label><input name="jetpack_relatedposts[show_headline]" type="checkbox" value="1" %s /> %s</label>
583
	</li>
584
	<li>
585
		<label><input name="jetpack_relatedposts[show_thumbnails]" type="checkbox" value="1" %s /> %s</label>
586
	</li>
587
	<li>
588
		<label><input name="jetpack_relatedposts[show_date]" type="checkbox" value="1" %s /> %s</label>
589
	</li>
590
	<li>
591
		<label><input name="jetpack_relatedposts[show_context]" type="checkbox" value="1" %s /> %s</label>
592
	</li>
593
</ul>
594
<div id='settings-reading-relatedposts-preview'>
595
	%s
596
	<div id="jp-relatedposts" class="jp-relatedposts"></div>
597
</div>
598
EOT;
599
		$ui_settings = sprintf(
600
			$ui_settings_template,
601
			esc_html__( 'The following settings will impact all related posts on your site, except for those you created via the block editor:', 'jetpack' ),
602
			checked( $options['show_headline'], true, false ),
603
			esc_html__( 'Highlight related content with a heading', 'jetpack' ),
604
			checked( $options['show_thumbnails'], true, false ),
605
			esc_html__( 'Show a thumbnail image where available', 'jetpack' ),
606
			checked( $options['show_date'], true, false ),
607
			esc_html__( 'Show entry date', 'jetpack' ),
608
			checked( $options['show_context'], true, false ),
609
			esc_html__( 'Show context (category or tag)', 'jetpack' ),
610
			esc_html__( 'Preview:', 'jetpack' )
611
		);
612
613
		if ( !$this->_allow_feature_toggle() ) {
614
			$template = <<<EOT
615
<input type="hidden" name="jetpack_relatedposts[enabled]" value="1" />
616
%s
617
EOT;
618
			printf(
619
				$template,
620
				$ui_settings
621
			);
622
		} else {
623
			$template = <<<EOT
624
<ul id="settings-reading-relatedposts">
625
	<li>
626
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="0" class="tog" %s /> %s</label>
627
	</li>
628
	<li>
629
		<label><input type="radio" name="jetpack_relatedposts[enabled]" value="1" class="tog" %s /> %s</label>
630
		%s
631
	</li>
632
</ul>
633
EOT;
634
			printf(
635
				$template,
636
				checked( $options['enabled'], false, false ),
637
				esc_html__( 'Hide related content after posts', 'jetpack' ),
638
				checked( $options['enabled'], true, false ),
639
				esc_html__( 'Show related content after posts', 'jetpack' ),
640
				$ui_settings
641
			);
642
		}
643
	}
644
645
	/**
646
	 * Head JS/CSS for admin settings page.
647
	 *
648
	 * @uses esc_html__
649
	 * @returns null
650
	 */
651
	public function print_setting_head() {
652
653
		// only dislay the Related Posts JavaScript on the Reading Settings Admin Page
654
		$current_screen =  get_current_screen();
655
656
		if ( is_null( $current_screen ) ) {
657
			return;
658
		}
659
660
		if( 'options-reading' != $current_screen->id )
661
			return;
662
663
		$related_headline = sprintf(
664
			'<h3 class="jp-relatedposts-headline"><em>%s</em></h3>',
665
			esc_html__( 'Related', 'jetpack' )
666
		);
667
668
		$href_params = 'class="jp-relatedposts-post-a" href="#jetpack_relatedposts" rel="nofollow" data-origin="0" data-position="0"';
669
		$related_with_images = <<<EOT
670
<div class="jp-relatedposts-items jp-relatedposts-items-visual">
671
	<div class="jp-relatedposts-post jp-relatedposts-post0 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
672
		<a $href_params>
673
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/cat-blog.png" width="350" alt="Big iPhone/iPad Update Now Available" scale="0">
674
		</a>
675
		<h4 class="jp-relatedposts-post-title">
676
			<a $href_params>Big iPhone/iPad Update Now Available</a>
677
		</h4>
678
		<p class="jp-relatedposts-post-excerpt">Big iPhone/iPad Update Now Available</p>
679
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
680
	</div>
681
	<div class="jp-relatedposts-post jp-relatedposts-post1 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
682
		<a $href_params>
683
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/devices.jpg" width="350" alt="The WordPress for Android App Gets a Big Facelift" scale="0">
684
		</a>
685
		<h4 class="jp-relatedposts-post-title">
686
			<a $href_params>The WordPress for Android App Gets a Big Facelift</a>
687
		</h4>
688
		<p class="jp-relatedposts-post-excerpt">The WordPress for Android App Gets a Big Facelift</p>
689
		<p class="jp-relatedposts-post-context">In "Mobile"</p>
690
	</div>
691
	<div class="jp-relatedposts-post jp-relatedposts-post2 jp-relatedposts-post-thumbs" data-post-id="0" data-post-format="image">
692
		<a $href_params>
693
			<img class="jp-relatedposts-post-img" src="https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg" width="350" alt="Upgrade Focus: VideoPress For Weddings" scale="0">
694
		</a>
695
		<h4 class="jp-relatedposts-post-title">
696
			<a $href_params>Upgrade Focus: VideoPress For Weddings</a>
697
		</h4>
698
		<p class="jp-relatedposts-post-excerpt">Upgrade Focus: VideoPress For Weddings</p>
699
		<p class="jp-relatedposts-post-context">In "Upgrade"</p>
700
	</div>
701
</div>
702
EOT;
703
		$related_with_images = str_replace( "\n", '', $related_with_images );
704
		$related_without_images = <<<EOT
705
<div class="jp-relatedposts-items jp-relatedposts-items-minimal">
706
	<p class="jp-relatedposts-post jp-relatedposts-post0" data-post-id="0" data-post-format="image">
707
		<span class="jp-relatedposts-post-title"><a $href_params>Big iPhone/iPad Update Now Available</a></span>
708
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
709
	</p>
710
	<p class="jp-relatedposts-post jp-relatedposts-post1" data-post-id="0" data-post-format="image">
711
		<span class="jp-relatedposts-post-title"><a $href_params>The WordPress for Android App Gets a Big Facelift</a></span>
712
		<span class="jp-relatedposts-post-context">In "Mobile"</span>
713
	</p>
714
	<p class="jp-relatedposts-post jp-relatedposts-post2" data-post-id="0" data-post-format="image">
715
		<span class="jp-relatedposts-post-title"><a $href_params>Upgrade Focus: VideoPress For Weddings</a></span>
716
		<span class="jp-relatedposts-post-context">In "Upgrade"</span>
717
	</p>
718
</div>
719
EOT;
720
		$related_without_images = str_replace( "\n", '', $related_without_images );
721
722
		if ( $this->_allow_feature_toggle() ) {
723
			$extra_css = '#settings-reading-relatedposts-customize { padding-left:2em; margin-top:.5em; }';
724
		} else {
725
			$extra_css = '';
726
		}
727
728
		echo <<<EOT
729
<style type="text/css">
730
	#settings-reading-relatedposts .disabled { opacity:.5; filter:Alpha(opacity=50); }
731
	#settings-reading-relatedposts-preview .jp-relatedposts { background:#fff; padding:.5em; width:75%; }
732
	$extra_css
733
</style>
734
<script type="text/javascript">
735
	jQuery( document ).ready( function($) {
736
		var update_ui = function() {
737
			var is_enabled = true;
738
			if ( 'radio' == $( 'input[name="jetpack_relatedposts[enabled]"]' ).attr('type') ) {
739
				if ( '0' == $( 'input[name="jetpack_relatedposts[enabled]"]:checked' ).val() ) {
740
					is_enabled = false;
741
				}
742
			}
743
			if ( is_enabled ) {
744
				$( '#settings-reading-relatedposts-customize' )
745
					.removeClass( 'disabled' )
746
					.find( 'input' )
747
					.attr( 'disabled', false );
748
				$( '#settings-reading-relatedposts-preview' )
749
					.removeClass( 'disabled' );
750
			} else {
751
				$( '#settings-reading-relatedposts-customize' )
752
					.addClass( 'disabled' )
753
					.find( 'input' )
754
					.attr( 'disabled', true );
755
				$( '#settings-reading-relatedposts-preview' )
756
					.addClass( 'disabled' );
757
			}
758
		};
759
760
		var update_preview = function() {
761
			var html = '';
762
			if ( $( 'input[name="jetpack_relatedposts[show_headline]"]:checked' ).length ) {
763
				html += '$related_headline';
764
			}
765
			if ( $( 'input[name="jetpack_relatedposts[show_thumbnails]"]:checked' ).length ) {
766
				html += '$related_with_images';
767
			} else {
768
				html += '$related_without_images';
769
			}
770
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).html( html );
771
			if ( $( 'input[name="jetpack_relatedposts[show_date]"]:checked' ).length ) {
772
				$( '.jp-relatedposts-post-title' ).each( function() {
773
					$( this ).after( $( '<span>August 8, 2005</span>' ) );
774
				} );
775
			}
776
			if ( $( 'input[name="jetpack_relatedposts[show_context]"]:checked' ).length ) {
777
				$( '.jp-relatedposts-post-context' ).show();
778
			} else {
779
				$( '.jp-relatedposts-post-context' ).hide();
780
			}
781
			$( '#settings-reading-relatedposts-preview .jp-relatedposts' ).show();
782
		};
783
784
		// Update on load
785
		update_preview();
786
		update_ui();
787
788
		// Update on change
789
		$( '#settings-reading-relatedposts-customize input' )
790
			.change( update_preview );
791
		$( '#settings-reading-relatedposts' )
792
			.find( 'input.tog' )
793
			.change( update_ui );
794
	});
795
</script>
796
EOT;
797
	}
798
799
	/**
800
	 * Gets an array of related posts that match the given post_id.
801
	 *
802
	 * @param int   $post_id Post which we want to find related posts for.
803
	 * @param array $args - params to use when building Elasticsearch filters to narrow down the search domain.
804
	 * @uses self::get_options, get_post_type, wp_parse_args, apply_filters
805
	 * @return array
806
	 */
807
	public function get_for_post_id( $post_id, array $args ) {
808
		$options = $this->get_options();
809
810
		if ( ! empty( $args['size'] ) ) {
811
			$options['size'] = $args['size'];
812
		}
813
814
		if (
815
			! $options['enabled']
816
			|| 0 === (int) $post_id
817
			|| empty( $options['size'] )
818
		) {
819
			return array();
820
		}
821
822
		$defaults = array(
823
			'size'             => (int) $options['size'],
824
			'post_type'        => get_post_type( $post_id ),
825
			'post_formats'     => array(),
826
			'has_terms'        => array(),
827
			'date_range'       => array(),
828
			'exclude_post_ids' => array(),
829
		);
830
		$args     = wp_parse_args( $args, $defaults );
831
		/**
832
		 * Filter the arguments used to retrieve a list of Related Posts.
833
		 *
834
		 * @module related-posts
835
		 *
836
		 * @since 2.8.0
837
		 *
838
		 * @param array $args Array of options to retrieve Related Posts.
839
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
840
		 */
841
		$args = apply_filters( 'jetpack_relatedposts_filter_args', $args, $post_id );
842
843
		$results = apply_filters( 'jetpack_relatedposts_pre_results', null, $post_id, $args );
844
845
		if ( null === $results ) {
846
			$filters = $this->_get_es_filters_from_args( $post_id, $args );
847
			/**
848
			 * Filter Elasticsearch options used to calculate Related Posts.
849
			 *
850
			 * @module related-posts
851
			 *
852
			 * @since 2.8.0
853
			 *
854
			 * @param array $filters Array of Elasticsearch filters based on the post_id and args.
855
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
856
			 */
857
			$filters = apply_filters( 'jetpack_relatedposts_filter_filters', $filters, $post_id );
858
859
			$results = $this->_get_related_posts( $post_id, $args['size'], $filters );
860
		}
861
862
		/**
863
		 * Filter the array of related posts matched by Elasticsearch.
864
		 *
865
		 * @module related-posts
866
		 *
867
		 * @since 2.8.0
868
		 *
869
		 * @param array $results Array of related posts matched by Elasticsearch.
870
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
871
		 */
872
		return apply_filters( 'jetpack_relatedposts_returned_results', $results, $post_id );
873
	}
874
875
	/**
876
	 * =========================
877
	 * PRIVATE UTILITY FUNCTIONS
878
	 * =========================
879
	 */
880
881
	/**
882
	 * Creates an array of Elasticsearch filters based on the post_id and args.
883
	 *
884
	 * @param int $post_id
885
	 * @param array $args
886
	 * @uses apply_filters, get_post_types, get_post_format_strings
887
	 * @return array
888
	 */
889
	protected function _get_es_filters_from_args( $post_id, array $args ) {
890
		$filters = array();
891
892
		/**
893
		 * Filter the terms used to search for Related Posts.
894
		 * Only used in building the Elasticsearch filters.
895
		 * Use `jetpack_relatedposts_filter_args` for filtering $args more generally.
896
		 *
897
		 * @module related-posts
898
		 *
899
		 * @since 2.8.0
900
		 *
901
		 * @param array $args['has_terms'] Array of terms associated to the Related Posts.
902
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
903
		 */
904
		$args['has_terms'] = apply_filters( 'jetpack_relatedposts_filter_has_terms', $args['has_terms'], $post_id );
905
		if ( ! empty( $args['has_terms'] ) ) {
906
			foreach( (array)$args['has_terms'] as $term ) {
907
				if ( mb_strlen( $term->taxonomy ) ) {
908 View Code Duplication
					switch ( $term->taxonomy ) {
909
						case 'post_tag':
910
							$tax_fld = 'tag.slug';
911
							break;
912
						case 'category':
913
							$tax_fld = 'category.slug';
914
							break;
915
						default:
916
							$tax_fld = 'taxonomy.' . $term->taxonomy . '.slug';
917
							break;
918
					}
919
					$filters[] = array( 'term' => array( $tax_fld => $term->slug ) );
920
				}
921
			}
922
		}
923
924
		/**
925
		 * Filter the Post Types where we search Related Posts.
926
		 * Only used in building the Elasticsearch filters.
927
		 * Use `jetpack_relatedposts_filter_args` for filtering $args more generally.
928
		 *
929
		 * @module related-posts
930
		 *
931
		 * @since 2.8.0
932
		 *
933
		 * @param array $args['post_type'] Array of Post Types.
934
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
935
		 */
936
		$args['post_type'] = apply_filters( 'jetpack_relatedposts_filter_post_type', $args['post_type'], $post_id );
937
		$valid_post_types = get_post_types();
938
		if ( is_array( $args['post_type'] ) ) {
939
			$sanitized_post_types = array();
940
			foreach ( $args['post_type'] as $pt ) {
941
				if ( in_array( $pt, $valid_post_types ) )
942
					$sanitized_post_types[] = $pt;
943
			}
944
			if ( ! empty( $sanitized_post_types ) )
945
				$filters[] = array( 'terms' => array( 'post_type' => $sanitized_post_types ) );
946
		} else if ( in_array( $args['post_type'], $valid_post_types ) && 'all' != $args['post_type'] ) {
947
			$filters[] = array( 'term' => array( 'post_type' => $args['post_type'] ) );
948
		}
949
950
		/**
951
		 * Filter the Post Formats where we search Related Posts.
952
		 * Only used in building the Elasticsearch filters.
953
		 * Use `jetpack_relatedposts_filter_args` for filtering $args more generally.
954
		 *
955
		 * @module related-posts
956
		 *
957
		 * @since 3.3.0
958
		 *
959
		 * @param array $args['post_formats'] Array of Post Formats.
960
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
961
		 */
962
		$args['post_formats'] = apply_filters( 'jetpack_relatedposts_filter_post_formats', $args['post_formats'], $post_id );
963
		$valid_post_formats = get_post_format_strings();
964
		$sanitized_post_formats = array();
965
		foreach ( $args['post_formats'] as $pf ) {
966
			if ( array_key_exists( $pf, $valid_post_formats ) ) {
967
				$sanitized_post_formats[] = $pf;
968
			}
969
		}
970
		if ( ! empty( $sanitized_post_formats ) ) {
971
			$filters[] = array( 'terms' => array( 'post_format' => $sanitized_post_formats ) );
972
		}
973
974
		/**
975
		 * Filter the date range used to search Related Posts.
976
		 * Only used in building the Elasticsearch filters.
977
		 * Use `jetpack_relatedposts_filter_args` for filtering $args more generally.
978
		 *
979
		 * @module related-posts
980
		 *
981
		 * @since 2.8.0
982
		 *
983
		 * @param array $args['date_range'] Array of a month interval where we search Related Posts.
984
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
985
		 */
986
		$args['date_range'] = apply_filters( 'jetpack_relatedposts_filter_date_range', $args['date_range'], $post_id );
987
		if ( is_array( $args['date_range'] ) && ! empty( $args['date_range'] ) ) {
988
			$args['date_range'] = array_map( 'intval', $args['date_range'] );
989
			if ( !empty( $args['date_range']['from'] ) && !empty( $args['date_range']['to'] ) ) {
990
				$filters[] = array(
991
					'range' => array(
992
						'date_gmt' => $this->_get_coalesced_range( $args['date_range'] ),
993
					)
994
				);
995
			}
996
		}
997
998
		/**
999
		 * Filter the Post IDs excluded from appearing in Related Posts.
1000
		 * Only used in building the Elasticsearch filters.
1001
		 * Use `jetpack_relatedposts_filter_args` for filtering $args more generally.
1002
		 *
1003
		 * @module related-posts
1004
		 *
1005
		 * @since 2.9.0
1006
		 *
1007
		 * @param array $args['exclude_post_ids'] Array of Post IDs.
1008
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1009
		 */
1010
		$args['exclude_post_ids'] = apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'], $post_id );
1011
		if ( !empty( $args['exclude_post_ids'] ) && is_array( $args['exclude_post_ids'] ) ) {
1012
			$excluded_post_ids = array();
1013
			foreach ( $args['exclude_post_ids'] as $exclude_post_id) {
1014
				$exclude_post_id = (int)$exclude_post_id;
1015
				if ( $exclude_post_id > 0 )
1016
					$excluded_post_ids[] = $exclude_post_id;
1017
			}
1018
			$filters[] = array( 'not' => array( 'terms' => array( 'post_id' => $excluded_post_ids ) ) );
1019
		}
1020
1021
		return $filters;
1022
	}
1023
1024
	/**
1025
	 * Takes a range and coalesces it into a month interval bracketed by a time as determined by the blog_id to enhance caching.
1026
	 *
1027
	 * @param array $date_range
1028
	 * @return array
1029
	 */
1030
	protected function _get_coalesced_range( array $date_range ) {
1031
		$now = time();
1032
		$coalesce_time = $this->get_blog_id() % 86400;
1033
		$current_time = $now - strtotime( 'today', $now );
1034
1035
		if ( $current_time < $coalesce_time && '01' == date( 'd', $now ) ) {
1036
			// Move back 1 period
1037
			return array(
1038
				'from' => date( 'Y-m-01', strtotime( '-1 month', $date_range['from'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1039
				'to'   => date( 'Y-m-01', $date_range['to'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1040
			);
1041
		} else {
1042
			// Use current period
1043
			return array(
1044
				'from' => date( 'Y-m-01', $date_range['from'] ) . ' ' . date( 'H:i:s', $coalesce_time ),
1045
				'to'   => date( 'Y-m-01', strtotime( '+1 month', $date_range['to'] ) ) . ' ' . date( 'H:i:s', $coalesce_time ),
1046
			);
1047
		}
1048
	}
1049
1050
	public function mock_results( $related_posts, $post_id, $args ) {
1051
		if ( null !== $related_posts ) {
1052
			return $related_posts;
1053
		}
1054
1055
		return $this->get_mock_results( $post_id, $args );
1056
	}
1057
1058
	public function get_mock_results( $post_id, $args ) {
1059
		$date_now = current_time( get_option( 'date_format' ) );
1060
1061
		// Dummy content
1062
		$related_posts = array(
1063
			array(
1064
				'id'       => - 1,
1065
				'url'      => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
1066
				'url_meta' => array(
1067
					'origin'   => 0,
1068
					'position' => 0
1069
				),
1070
				'title'    => esc_html__( 'Big iPhone/iPad Update Now Available', 'jetpack' ),
1071
				'date'     => $date_now,
1072
				'format'   => false,
1073
				'excerpt'  => esc_html__( 'It is that time of the year when devices are shiny again.', 'jetpack' ),
1074
				'rel'      => 'nofollow',
1075
				'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1076
				'img'      => array(
1077
					'src'    => 'https://jetpackme.files.wordpress.com/2019/03/cat-blog.png',
1078
					'width'  => 350,
1079
					'height' => 200
1080
				),
1081
				'classes'  => array()
1082
			),
1083
			array(
1084
				'id'       => - 1,
1085
				'url'      => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
1086
				'url_meta' => array(
1087
					'origin'   => 0,
1088
					'position' => 0
1089
				),
1090
				'title'    => esc_html__( 'The WordPress for Android App Gets a Big Facelift', 'jetpack' ),
1091
				'date'     => $date_now,
1092
				'format'   => false,
1093
				'excerpt'  => esc_html__( 'Writing is new again in Android with the new WordPress app.', 'jetpack' ),
1094
				'rel'      => 'nofollow',
1095
				'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1096
				'img'      => array(
1097
					'src'    => 'https://jetpackme.files.wordpress.com/2019/03/devices.jpg',
1098
					'width'  => 350,
1099
					'height' => 200
1100
				),
1101
				'classes'  => array()
1102
			),
1103
			array(
1104
				'id'       => - 1,
1105
				'url'      => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
1106
				'url_meta' => array(
1107
					'origin'   => 0,
1108
					'position' => 0
1109
				),
1110
				'title'    => esc_html__( 'Upgrade Focus, VideoPress for weddings', 'jetpack' ),
1111
				'date'     => $date_now,
1112
				'format'   => false,
1113
				'excerpt'  => esc_html__( 'Weddings are in the spotlight now with VideoPress for weddings.', 'jetpack' ),
1114
				'rel'      => 'nofollow',
1115
				'context'  => esc_html__( 'In "Mobile"', 'jetpack' ),
1116
				'img'      => array(
1117
					'src'    => 'https://jetpackme.files.wordpress.com/2019/03/mobile-wedding.jpg',
1118
					'width'  => 350,
1119
					'height' => 200
1120
				),
1121
				'classes'  => array()
1122
			),
1123
		);
1124
1125
		// Pad to required length
1126
		if ( 3 < $args['size'] ) {
1127
			$related_posts = call_user_func_array( 'array_merge', array_fill( 0, (int) ceil( $args['size'] / 3 ), $related_posts ) );
1128
		}
1129
		$related_posts = array_slice( $related_posts, 0, $args['size'] );
1130
1131
		// Exclude current post after filtering to make sure it's excluded and not lost during filtering.
1132
		$excluded_posts = array_merge(
1133
			/** This filter is already documented in modules/related-posts/jetpack-related-posts.php */
1134
			apply_filters( 'jetpack_relatedposts_filter_exclude_post_ids', $args['exclude_post_ids'] ),
1135
			array( get_the_ID() )
1136
		);
1137
1138
		$tax_query = array( 'relation' => 'AND' );
1139
		if ( $args['has_terms'] ) {
1140
			$term_query = array( 'relation' => 'OR' );
1141
			foreach ( $args['has_terms'] as $term ) {
1142
				$term_query[] = array(
1143
					'taxonomy' => $term->taxonomy,
1144
					'terms'    => $term->term_id,
1145
				);
1146
			}
1147
1148
			$tax_query[] = $term_query;
1149
		}
1150
1151
		if ( $args['post_formats'] ) {
1152
			$post_formats = array_intersect( $args['post_formats'], get_post_format_slugs() );
1153
1154
			if ( $post_formats ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $post_formats of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1155
				foreach ( $post_formats as &$post_format ) {
1156
					$post_format = "post-format-{$post_format}";
1157
				}
1158
1159
				$post_formats_query = array(
1160
					'relation' => 'OR',
1161
					'taxonomy' => 'post_format',
1162
					'field'    => 'slug',
1163
					'terms'    => $post_formats,
1164
				);
1165
1166
				$tax_query[] = $post_formats_query;
1167
			}
1168
		}
1169
1170
		if ( $args['date_range'] ) {
1171
			$date_query = array(
1172
				'column' => 'post_date_gmt',
1173
				'after'  => $args['date_range']['from'],
1174
				'before' => $args['date_range']['to'],
1175
			);
1176
		} else {
1177
			$date_query = array();
1178
		}
1179
1180
		// Fetch posts with featured image.
1181
		$with_post_thumbnails = get_posts( array(
1182
			'posts_per_page'   => $args['size'],
1183
			'post__not_in'     => $excluded_posts,
1184
			'post_type'        => $args['post_type'],
1185
			'meta_key'         => '_thumbnail_id',
1186
			'tax_query'        => $tax_query,
1187
			'date_query'       => $date_query,
1188
			'suppress_filters' => false,
1189
		) );
1190
1191
		// If we don't have enough, fetch posts without featured image.
1192
		if ( 0 < ( $more = $args['size'] - count( $with_post_thumbnails ) ) ) {
1193
			$no_post_thumbnails = get_posts( array(
1194
				'posts_per_page'  => $more,
1195
				'post__not_in'    => $excluded_posts,
1196
				'post_type'       => $args['post_type'],
1197
				'meta_query' => array(
1198
					array(
1199
						'key'     => '_thumbnail_id',
1200
						'compare' => 'NOT EXISTS',
1201
					),
1202
				),
1203
				'tax_query'        => $tax_query,
1204
				'date_query'       => $date_query,
1205
				'suppress_filters' => false,
1206
			) );
1207
		} else {
1208
			$no_post_thumbnails = array();
1209
		}
1210
1211
		foreach ( array_merge( $with_post_thumbnails, $no_post_thumbnails ) as $index => $real_post ) {
1212
			$related_posts[ $index ]['id']      = $real_post->ID;
1213
			$related_posts[ $index ]['url']     = esc_url( get_permalink( $real_post ) );
1214
			$related_posts[ $index ]['title']   = $this->_to_utf8( $this->_get_title( $real_post->post_title, $real_post->post_content ) );
1215
			$related_posts[ $index ]['date']    = get_the_date( '', $real_post );
1216
			$related_posts[ $index ]['format']  = get_post_format( $real_post );
1217
			$related_posts[ $index ]['excerpt'] = html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $real_post->post_excerpt, $real_post->post_content ) ), ENT_QUOTES, 'UTF-8' );
1218
			$related_posts[ $index ]['img']     = $this->_generate_related_post_image_params( $real_post->ID );
1219
			$related_posts[ $index ]['context'] = $this->_generate_related_post_context( $real_post->ID );
1220
		}
1221
1222
		return $related_posts;
1223
	}
1224
1225
	/**
1226
	 * Generate and output ajax response for related posts API call.
1227
	 * NOTE: Calls exit() to end all further processing after payload has been outputed.
1228
	 *
1229
	 * @param array $excludes array of post_ids to exclude
1230
	 * @uses send_nosniff_header, self::get_for_post_id, get_the_ID
1231
	 * @return null
1232
	 */
1233
	protected function _action_frontend_init_ajax( array $excludes ) {
1234
		define( 'DOING_AJAX', true );
1235
1236
		header( 'Content-type: application/json; charset=utf-8' ); // JSON can only be UTF-8
1237
		send_nosniff_header();
1238
1239
		$options = $this->get_options();
1240
1241
		$related_posts = $this->get_for_post_id(
1242
			get_the_ID(),
1243
			array(
1244
				'exclude_post_ids' => $excludes,
1245
			)
1246
		);
1247
1248
		$response = array(
1249
			'version' => self::VERSION,
1250
			'show_thumbnails' => (bool) $options['show_thumbnails'],
1251
			'show_date' => (bool) $options['show_date'],
1252
			'show_context' => (bool) $options['show_context'],
1253
			'layout' => (string) $options['layout'],
1254
			'headline' => (string) $options['headline'],
1255
			'items' => array(),
1256
		);
1257
1258
		if ( count( $related_posts ) == $options['size'] )
1259
			$response['items'] = $related_posts;
1260
1261
		echo json_encode( $response );
1262
1263
		exit();
1264
	}
1265
1266
	/**
1267
	 * Returns a UTF-8 encoded array of post information for the given post_id
1268
	 *
1269
	 * @param int $post_id
1270
	 * @param int $position
1271
	 * @param int $origin The post id that this is related to
1272
	 * @uses get_post, get_permalink, remove_query_arg, get_post_format, apply_filters
1273
	 * @return array
1274
	 */
1275
	public function get_related_post_data_for_post( $post_id, $position, $origin ) {
1276
		$post = get_post( $post_id );
1277
1278
		return array(
1279
			'id' => $post->ID,
1280
			'url' => get_permalink( $post->ID ),
1281
			'url_meta' => array( 'origin' => $origin, 'position' => $position ),
1282
			'title' => $this->_to_utf8( $this->_get_title( $post->post_title, $post->post_content ) ),
1283
			'date' => get_the_date( '', $post->ID ),
1284
			'format' => get_post_format( $post->ID ),
1285
			'excerpt' => html_entity_decode( $this->_to_utf8( $this->_get_excerpt( $post->post_excerpt, $post->post_content ) ), ENT_QUOTES, 'UTF-8' ),
1286
			/**
1287
			 * Filters the rel attribute for the Related Posts' links.
1288
			 *
1289
			 * @module related-posts
1290
			 *
1291
			 * @since 3.7.0
1292
			 * @since 7.9.0 - Change Default value to empty.
1293
			 *
1294
			 * @param string $link_rel Link rel attribute for Related Posts' link. Default is empty.
1295
			 * @param int    $post->ID Post ID.
1296
			 */
1297
			'rel' => apply_filters( 'jetpack_relatedposts_filter_post_link_rel', '', $post->ID ),
1298
			/**
1299
			 * Filter the context displayed below each Related Post.
1300
			 *
1301
			 * @module related-posts
1302
			 *
1303
			 * @since 3.0.0
1304
			 *
1305
			 * @param string $this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ) Context displayed below each related post.
1306
			 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1307
			 */
1308
			'context' => apply_filters(
1309
				'jetpack_relatedposts_filter_post_context',
1310
				$this->_to_utf8( $this->_generate_related_post_context( $post->ID ) ),
1311
				$post->ID
1312
			),
1313
			'img' => $this->_generate_related_post_image_params( $post->ID ),
1314
			/**
1315
			 * Filter the post css classes added on HTML markup.
1316
			 *
1317
			 * @module related-posts
1318
			 *
1319
			 * @since 3.8.0
1320
			 *
1321
			 * @param array array() CSS classes added on post HTML markup.
1322
			 * @param string $post_id Post ID.
1323
			 */
1324
			'classes' => apply_filters(
1325
				'jetpack_relatedposts_filter_post_css_classes',
1326
				array(),
1327
				$post->ID
1328
			),
1329
		);
1330
	}
1331
1332
	/**
1333
	 * Returns either the title or a small excerpt to use as title for post.
1334
	 *
1335
	 * @param string $post_title
1336
	 * @param string $post_content
1337
	 * @uses strip_shortcodes, wp_trim_words, __
1338
	 * @return string
1339
	 */
1340
	protected function _get_title( $post_title, $post_content ) {
1341
		if ( ! empty( $post_title ) ) {
1342
			return wp_strip_all_tags( $post_title );
1343
		}
1344
1345
		$post_title = wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post_content ) ), 5, '…' );
1346
		if ( ! empty( $post_title ) ) {
1347
			return $post_title;
1348
		}
1349
1350
		return __( 'Untitled Post', 'jetpack' );
1351
	}
1352
1353
	/**
1354
	 * Returns a plain text post excerpt for title attribute of links.
1355
	 *
1356
	 * @param string $post_excerpt
1357
	 * @param string $post_content
1358
	 * @uses strip_shortcodes, wp_strip_all_tags, wp_trim_words
1359
	 * @return string
1360
	 */
1361
	protected function _get_excerpt( $post_excerpt, $post_content ) {
1362
		if ( empty( $post_excerpt ) )
1363
			$excerpt = $post_content;
1364
		else
1365
			$excerpt = $post_excerpt;
1366
1367
		return wp_trim_words( wp_strip_all_tags( strip_shortcodes( $excerpt ) ), 50, '…' );
1368
	}
1369
1370
	/**
1371
	 * Generates the thumbnail image to be used for the post. Uses the
1372
	 * image as returned by Jetpack_PostImages::get_image()
1373
	 *
1374
	 * @param int $post_id
1375
	 * @uses self::get_options, apply_filters, Jetpack_PostImages::get_image, Jetpack_PostImages::fit_image_url
1376
	 * @return string
1377
	 */
1378
	protected function _generate_related_post_image_params( $post_id ) {
1379
		$options = $this->get_options();
0 ignored issues
show
Unused Code introduced by
$options is not used, you could remove the assignment.

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

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

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

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

Loading history...
1380
		$image_params = array(
1381
			'alt_text' => '',
1382
			'src'      => '',
1383
			'width'    => 0,
1384
			'height'   => 0,
1385
		);
1386
1387
		/**
1388
		 * Filter the size of the Related Posts images.
1389
		 *
1390
		 * @module related-posts
1391
		 *
1392
		 * @since 2.8.0
1393
		 *
1394
		 * @param array array( 'width' => 350, 'height' => 200 ) Size of the images displayed below each Related Post.
1395
		 */
1396
		$thumbnail_size = apply_filters(
1397
			'jetpack_relatedposts_filter_thumbnail_size',
1398
			array( 'width' => 350, 'height' => 200 )
1399
		);
1400
		if ( !is_array( $thumbnail_size ) ) {
1401
			$thumbnail_size = array(
1402
				'width' => (int)$thumbnail_size,
1403
				'height' => (int)$thumbnail_size
1404
			);
1405
		}
1406
1407
		// Try to get post image
1408
		if ( class_exists( 'Jetpack_PostImages' ) ) {
1409
			$img_url    = '';
1410
			$post_image = Jetpack_PostImages::get_image(
1411
				$post_id,
1412
				$thumbnail_size
1413
			);
1414
1415
			if ( is_array($post_image) ) {
1416
				$img_url = $post_image['src'];
1417
			} elseif ( class_exists( 'Jetpack_Media_Summary' ) ) {
1418
				$media = Jetpack_Media_Summary::get( $post_id );
1419
1420
				if ( is_array($media) && !empty( $media['image'] ) ) {
1421
					$img_url = $media['image'];
1422
				}
1423
			}
1424
1425
			if ( ! empty( $img_url ) ) {
1426
				if ( ! empty( $post_image['alt_text'] ) ) {
1427
					$image_params['alt_text'] = $post_image['alt_text'];
1428
				} else {
1429
					$image_params['alt_text'] = '';
1430
				}
1431
				$image_params['width']  = $thumbnail_size['width'];
1432
				$image_params['height'] = $thumbnail_size['height'];
1433
				$image_params['src']    = Jetpack_PostImages::fit_image_url(
1434
					$img_url,
1435
					$thumbnail_size['width'],
1436
					$thumbnail_size['height']
1437
				);
1438
			}
1439
		}
1440
1441
		return $image_params;
1442
	}
1443
1444
	/**
1445
	 * Returns the string UTF-8 encoded
1446
	 *
1447
	 * @param string $text
1448
	 * @return string
1449
	 */
1450
	protected function _to_utf8( $text ) {
1451
		if ( $this->_convert_charset ) {
1452
			return iconv( $this->_blog_charset, 'UTF-8', $text );
1453
		} else {
1454
			return $text;
1455
		}
1456
	}
1457
1458
	/**
1459
	 * =============================================
1460
	 * PROTECTED UTILITY FUNCTIONS EXTENDED BY WPCOM
1461
	 * =============================================
1462
	 */
1463
1464
	/**
1465
	 * Workhorse method to return array of related posts matched by Elasticsearch.
1466
	 *
1467
	 * @param int $post_id
1468
	 * @param int $size
1469
	 * @param array $filters
1470
	 * @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
1471
	 * @return array
1472
	 */
1473
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1474
		$hits = $this->_filter_non_public_posts(
1475
			$this->_get_related_post_ids(
1476
				$post_id,
1477
				$size,
1478
				$filters
1479
			)
1480
		);
1481
1482
		/**
1483
		 * Filter the Related Posts matched by Elasticsearch.
1484
		 *
1485
		 * @module related-posts
1486
		 *
1487
		 * @since 2.9.0
1488
		 *
1489
		 * @param array $hits Array of Post IDs matched by Elasticsearch.
1490
		 * @param string $post_id Post ID of the post for which we are retrieving Related Posts.
1491
		 */
1492
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1493
1494
		$related_posts = array();
1495
		foreach ( $hits as $i => $hit ) {
1496
			$related_posts[] = $this->get_related_post_data_for_post( $hit['id'], $i, $post_id );
1497
		}
1498
		return $related_posts;
1499
	}
1500
1501
	/**
1502
	 * Get array of related posts matched by Elasticsearch.
1503
	 *
1504
	 * @param int $post_id
1505
	 * @param int $size
1506
	 * @param array $filters
1507
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body, get_post_meta, update_post_meta
1508
	 * @return array
1509
	 */
1510
	protected function _get_related_post_ids( $post_id, $size, array $filters ) {
1511
		$now_ts = time();
1512
		$cache_meta_key = '_jetpack_related_posts_cache';
1513
1514
		$body = array(
1515
			'size' => (int) $size,
1516
		);
1517
1518
		if ( !empty( $filters ) )
1519
			$body['filter'] = array( 'and' => $filters );
1520
1521
		// Build cache key
1522
		$cache_key = md5( serialize( $body ) );
1523
1524
		// Load all cached values
1525
		if ( wp_using_ext_object_cache() ) {
1526
			$transient_name = "{$cache_meta_key}_{$cache_key}_{$post_id}";
1527
			$cache = get_transient( $transient_name );
1528
			if ( false !== $cache ) {
1529
				return $cache;
1530
			}
1531
		} else {
1532
			$cache = get_post_meta( $post_id, $cache_meta_key, true );
1533
1534
			if ( empty( $cache ) )
1535
				$cache = array();
1536
1537
1538
			// Cache is valid! Return cached value.
1539
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) && $cache[ $cache_key ][ 'expires' ] > $now_ts ) {
1540
				return $cache[ $cache_key ][ 'payload' ];
1541
			}
1542
		}
1543
1544
		$response = wp_remote_post(
1545
			"https://public-api.wordpress.com/rest/v1/sites/{$this->get_blog_id()}/posts/$post_id/related/",
1546
			array(
1547
				'timeout' => 10,
1548
				'user-agent' => 'jetpack_related_posts',
1549
				'sslverify' => true,
1550
				'body' => $body,
1551
			)
1552
		);
1553
1554
		// Oh no... return nothing don't cache errors.
1555
		if ( is_wp_error( $response ) ) {
1556
			if ( isset( $cache[ $cache_key ] ) && is_array( $cache[ $cache_key ] ) )
1557
				return $cache[ $cache_key ][ 'payload' ]; // return stale
1558
			else
1559
				return array();
1560
		}
1561
1562
		$results = json_decode( wp_remote_retrieve_body( $response ), true );
1563
		$related_posts = array();
1564
		if ( is_array( $results ) && !empty( $results['hits'] ) ) {
1565
			foreach( $results['hits'] as $hit ) {
1566
				$related_posts[] = array(
1567
					'id' => $hit['fields']['post_id'],
1568
				);
1569
			}
1570
		}
1571
1572
		// An empty array might indicate no related posts or that posts
1573
		// are not yet synced to WordPress.com, so we cache for only 1
1574
		// minute in this case
1575
		if ( empty( $related_posts ) ) {
1576
			$cache_ttl = 60;
1577
		} else {
1578
			$cache_ttl = 12 * HOUR_IN_SECONDS;
1579
		}
1580
1581
		// Update cache
1582
		if ( wp_using_ext_object_cache() ) {
1583
			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...
1584
		} else {
1585
			// Copy all valid cache values
1586
			$new_cache = array();
1587
			foreach ( $cache as $k => $v ) {
1588
				if ( is_array( $v ) && $v[ 'expires' ] > $now_ts ) {
1589
					$new_cache[ $k ] = $v;
1590
				}
1591
			}
1592
1593
			// Set new cache value
1594
			$cache_expires = $cache_ttl + $now_ts;
1595
			$new_cache[ $cache_key ] = array(
1596
				'expires' => $cache_expires,
1597
				'payload' => $related_posts,
1598
			);
1599
			update_post_meta( $post_id, $cache_meta_key, $new_cache );
1600
		}
1601
1602
		return $related_posts;
1603
	}
1604
1605
	/**
1606
	 * Filter out any hits that are not public anymore.
1607
	 *
1608
	 * @param array $related_posts
1609
	 * @uses get_post_stati, get_post_status
1610
	 * @return array
1611
	 */
1612
	protected function _filter_non_public_posts( array $related_posts ) {
1613
		$public_stati = get_post_stati( array( 'public' => true ) );
1614
1615
		$filtered = array();
1616
		foreach ( $related_posts as $hit ) {
1617
			if ( in_array( get_post_status( $hit['id'] ), $public_stati ) ) {
1618
				$filtered[] = $hit;
1619
			}
1620
		}
1621
		return $filtered;
1622
	}
1623
1624
	/**
1625
	 * Generates a context for the related content (second line in related post output).
1626
	 * Order of importance:
1627
	 *   - First category (Not 'Uncategorized')
1628
	 *   - First post tag
1629
	 *   - Number of comments
1630
	 *
1631
	 * @param int $post_id
1632
	 * @uses get_the_category, get_the_terms, get_comments_number, number_format_i18n, __, _n
1633
	 * @return string
1634
	 */
1635
	protected function _generate_related_post_context( $post_id ) {
1636
		$categories = get_the_category( $post_id );
1637 View Code Duplication
		if ( is_array( $categories ) ) {
1638
			foreach ( $categories as $category ) {
1639
				if ( 'uncategorized' != $category->slug && '' != trim( $category->name ) ) {
1640
					$post_cat_context = sprintf(
1641
						esc_html_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1642
						$category->name
1643
					);
1644
					/**
1645
					 * Filter the "In Category" line displayed in the post context below each Related Post.
1646
					 *
1647
					 * @module related-posts
1648
					 *
1649
					 * @since 3.2.0
1650
					 *
1651
					 * @param string $post_cat_context "In Category" line displayed in the post context below each Related Post.
1652
					 * @param array $category Array containing information about the category.
1653
					 */
1654
					return apply_filters( 'jetpack_relatedposts_post_category_context', $post_cat_context, $category );
1655
				}
1656
			}
1657
		}
1658
1659
		$tags = get_the_terms( $post_id, 'post_tag' );
1660 View Code Duplication
		if ( is_array( $tags ) ) {
1661
			foreach ( $tags as $tag ) {
1662
				if ( '' != trim( $tag->name ) ) {
1663
					$post_tag_context = sprintf(
1664
						_x( 'In "%s"', 'in {category/tag name}', 'jetpack' ),
1665
						$tag->name
1666
					);
1667
					/**
1668
					 * Filter the "In Tag" line displayed in the post context below each Related Post.
1669
					 *
1670
					 * @module related-posts
1671
					 *
1672
					 * @since 3.2.0
1673
					 *
1674
					 * @param string $post_tag_context "In Tag" line displayed in the post context below each Related Post.
1675
					 * @param array $tag Array containing information about the tag.
1676
					 */
1677
					return apply_filters( 'jetpack_relatedposts_post_tag_context', $post_tag_context, $tag );
1678
				}
1679
			}
1680
		}
1681
1682
		$comment_count = get_comments_number( $post_id );
1683
		if ( $comment_count > 0 ) {
1684
			return sprintf(
1685
				_n( 'With 1 comment', 'With %s comments', $comment_count, 'jetpack' ),
1686
				number_format_i18n( $comment_count )
1687
			);
1688
		}
1689
1690
		return __( 'Similar post', 'jetpack' );
1691
	}
1692
1693
	/**
1694
	 * Logs clicks for clickthrough analysis and related result tuning.
1695
	 *
1696
	 * @return null
1697
	 */
1698
	protected function _log_click( $post_id, $to_post_id, $link_position ) {
1699
1700
	}
1701
1702
	/**
1703
	 * Determines if the current post is able to use related posts.
1704
	 *
1705
	 * @uses self::get_options, is_admin, is_single, apply_filters
1706
	 * @return bool
1707
	 */
1708
	protected function _enabled_for_request() {
1709
		$enabled = is_single()
1710
			&& ! is_attachment()
1711
			&& ! is_admin()
1712
			&& ( ! $this->_allow_feature_toggle() || $this->get_option( 'enabled' ) );
1713
1714
		/**
1715
		 * Filter the Enabled value to allow related posts to be shown on pages as well.
1716
		 *
1717
		 * @module related-posts
1718
		 *
1719
		 * @since 3.3.0
1720
		 *
1721
		 * @param bool $enabled Should Related Posts be enabled on the current page.
1722
		 */
1723
		return apply_filters( 'jetpack_relatedposts_filter_enabled_for_request', $enabled );
1724
	}
1725
1726
	/**
1727
	 * Adds filters and enqueues assets.
1728
	 *
1729
	 * @uses self::_enqueue_assets, self::_setup_shortcode, add_filter
1730
	 * @return null
1731
	 */
1732
	protected function _action_frontend_init_page() {
1733
1734
		$enqueue_script = ! ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() );
1735
		$this->_enqueue_assets( $enqueue_script, true );
1736
		$this->_setup_shortcode();
1737
1738
		add_filter( 'the_content', array( $this, 'filter_add_target_to_dom' ), 40 );
1739
	}
1740
1741
	/**
1742
	 * Enqueues assets needed to do async loading of related posts.
1743
	 *
1744
	 * @uses wp_enqueue_script, wp_enqueue_style, plugins_url
1745
	 * @return null
1746
	 */
1747
	protected function _enqueue_assets( $script, $style ) {
1748
		$dependencies = is_customize_preview() ? array( 'customize-base' ) : array( 'jquery' );
1749
		if ( $script ) {
1750
			wp_enqueue_script(
1751
				'jetpack_related-posts',
1752
				Assets::get_file_url_for_environment(
1753
					'_inc/build/related-posts/related-posts.min.js',
1754
					'modules/related-posts/related-posts.js'
1755
				),
1756
				$dependencies,
1757
				self::VERSION
1758
			);
1759
			$related_posts_js_options = array(
1760
				/**
1761
				 * Filter each Related Post Heading structure.
1762
				 *
1763
				 * @since 4.0.0
1764
				 *
1765
				 * @param string $str Related Post Heading structure. Default to h4.
1766
				 */
1767
				'post_heading' => apply_filters( 'jetpack_relatedposts_filter_post_heading', esc_attr( 'h4' ) ),
1768
			);
1769
			wp_localize_script( 'jetpack_related-posts', 'related_posts_js_options', $related_posts_js_options );
1770
		}
1771
		if ( $style ){
1772
			wp_enqueue_style( 'jetpack_related-posts', plugins_url( 'related-posts.css', __FILE__ ), array(), self::VERSION );
1773
			wp_style_add_data( 'jetpack_related-posts', 'rtl', 'replace' );
1774
			add_action( 'amp_post_template_css', array( $this, 'render_amp_reader_mode_css' ) );
1775
		}
1776
	}
1777
1778
	public function render_amp_reader_mode_css() {
1779
		echo file_get_contents( plugin_dir_path( __FILE__ ) . 'related-posts.css' );
1780
	}
1781
1782
	/**
1783
	 * Sets up the shortcode processing.
1784
	 *
1785
	 * @uses add_filter, add_shortcode
1786
	 * @return null
1787
	 */
1788
	protected function _setup_shortcode() {
1789
		add_filter( 'the_content', array( $this, 'test_for_shortcode' ), 0 );
1790
1791
		add_shortcode( self::SHORTCODE, array( $this, 'get_client_rendered_html' ) );
1792
	}
1793
1794
	protected function _allow_feature_toggle() {
1795
		if ( null === $this->_allow_feature_toggle ) {
1796
			/**
1797
			 * Filter the display of the Related Posts toggle in Settings > Reading.
1798
			 *
1799
			 * @module related-posts
1800
			 *
1801
			 * @since 2.8.0
1802
			 *
1803
			 * @param bool false Display a feature toggle. Default to false.
1804
			 */
1805
			$this->_allow_feature_toggle = apply_filters( 'jetpack_relatedposts_filter_allow_feature_toggle', false );
1806
		}
1807
		return $this->_allow_feature_toggle;
1808
	}
1809
1810
	/**
1811
	 * ===================================================
1812
	 * FUNCTIONS EXPOSING RELATED POSTS IN THE WP REST API
1813
	 * ===================================================
1814
	 */
1815
1816
	/**
1817
	 * Add Related Posts to the REST API Post response.
1818
	 *
1819
	 * @since 4.4.0
1820
	 *
1821
	 * @action rest_api_init
1822
	 * @uses register_rest_field, self::rest_get_related_posts
1823
	 * @return null
1824
	 */
1825
	public function rest_register_related_posts() {
1826
		register_rest_field( 'post',
1827
			'jetpack-related-posts',
1828
			array(
1829
				'get_callback' => array( $this, 'rest_get_related_posts' ),
1830
				'update_callback' => null,
1831
				'schema'          => null,
1832
			)
1833
		);
1834
	}
1835
1836
	/**
1837
	 * Build an array of Related Posts.
1838
	 * By default returns cached results that are stored for up to 12 hours.
1839
	 *
1840
	 * @since 4.4.0
1841
	 *
1842
	 * @param array $object Details of current post.
1843
	 * @param string $field_name Name of field.
1844
	 * @param WP_REST_Request $request Current request
1845
	 *
1846
	 * @uses self::get_for_post_id
1847
	 *
1848
	 * @return array
1849
	 */
1850
	public function rest_get_related_posts( $object, $field_name, $request ) {
1851
		return $this->get_for_post_id( $object['id'], array( 'size' => 6 ) );
1852
	}
1853
}
1854
1855
class Jetpack_RelatedPosts_Raw extends Jetpack_RelatedPosts {
1856
	protected $_query_name;
1857
1858
	/**
1859
	 * Allows callers of this class to tag each query with a unique name for tracking purposes.
1860
	 *
1861
	 * @param string $name
1862
	 * @return Jetpack_RelatedPosts_Raw
1863
	 */
1864
	public function set_query_name( $name ) {
1865
		$this->_query_name = (string) $name;
1866
		return $this;
1867
	}
1868
1869
	/**
1870
	 * The raw related posts class can be used by other plugins or themes
1871
	 * to get related content. This class wraps the existing RelatedPosts
1872
	 * logic thus we never want to add anything to the DOM or do anything
1873
	 * for event hooks. We will also not present any settings for this
1874
	 * class and keep it enabled as calls to this class is done
1875
	 * programmatically.
1876
	 */
1877
	public function action_admin_init() {}
1878
	public function action_frontend_init() {}
1879
	public function get_options() {
1880
		return array(
1881
			'enabled' => true,
1882
		);
1883
	}
1884
1885
	/**
1886
	 * Workhorse method to return array of related posts ids matched by Elasticsearch.
1887
	 *
1888
	 * @param int $post_id
1889
	 * @param int $size
1890
	 * @param array $filters
1891
	 * @uses wp_remote_post, is_wp_error, wp_remote_retrieve_body
1892
	 * @return array
1893
	 */
1894
	protected function _get_related_posts( $post_id, $size, array $filters ) {
1895
		$hits = $this->_filter_non_public_posts(
1896
			$this->_get_related_post_ids(
1897
				$post_id,
1898
				$size,
1899
				$filters
1900
			)
1901
		);
1902
1903
		/** This filter is already documented in modules/related-posts/related-posts.php */
1904
		$hits = apply_filters( 'jetpack_relatedposts_filter_hits', $hits, $post_id );
1905
1906
		return $hits;
1907
	}
1908
}
1909