Completed
Push — add/related-posts-gutenberg-bl... ( 2bd773 )
by Marin
10:42
created

Jetpack_RelatedPosts   F

Complexity

Total Complexity 173

Size/Duplication

Total Lines 1575
Duplicated Lines 5.71 %

Coupling/Cohesion

Components 2
Dependencies 7

Importance

Changes 0
Metric Value
dl 90
loc 1575
rs 0.8
c 0
b 0
f 0
wmc 173
lcom 2
cbo 7

37 Methods

Rating   Name   Duplication   Size   Complexity  
A init() 14 14 4
A init_raw() 14 14 4
A __construct() 0 19 4
A action_admin_init() 0 12 2
B action_frontend_init() 0 30 7
A get_headline() 0 14 2
A filter_add_target_to_dom() 0 7 2
A test_for_shortcode() 0 5 1
A get_target_html() 0 29 3
A get_target_html_unsupported() 0 7 2
F get_options() 0 40 12
A get_option() 0 9 2
F parse_options() 0 35 17
B print_setting_html() 0 67 2
B print_setting_head() 0 147 4
B get_for_post_id() 0 58 6
F _get_es_filters_from_args() 11 124 23
A _get_coalesced_range() 0 19 3
B _action_frontend_init_ajax() 0 150 6
B _get_related_post_data_for_post() 0 55 1
A _get_title() 0 12 3
A _get_excerpt() 0 8 2
B _generate_related_post_image_params() 9 63 9
A _to_utf8() 0 7 2
A _get_related_posts() 0 27 2
D _get_related_post_ids() 0 94 19
A _filter_non_public_posts() 0 11 3
B _generate_related_post_context() 42 57 9
A _log_click() 0 3 1
A _enabled_for_request() 0 20 5
A _action_frontend_init_page() 0 6 1
A _enqueue_assets() 0 29 4
A enqueue_block_editor_assets() 0 21 1
A _setup_shortcode() 0 5 1
A _allow_feature_toggle() 0 15 2
A rest_register_related_posts() 0 10 1
A rest_get_related_posts() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_RelatedPosts often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_RelatedPosts, and based on these observations, apply Extract Interface, too.

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