Completed
Push — update/pre_connection_jitms_pl... ( 9bf136...a14e59 )
by
unknown
214:58 queued 207:06
created

The_Neverending_Home_Page::amp_output_buffer()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
use Automattic\Jetpack\Assets;
4
use Automattic\Jetpack\Redirect;
5
6
/*
7
Plugin Name: The Neverending Home Page.
8
Plugin URI: https://automattic.com/
9
Description: Adds infinite scrolling support to the front-end blog post view for themes, pulling the next set of posts automatically into view when the reader approaches the bottom of the page.
10
Version: 1.1
11
Author: Automattic
12
Author URI: https://automattic.com/
13
License: GNU General Public License v2 or later
14
License URI: https://www.gnu.org/licenses/gpl-2.0.html
15
*/
16
17
/**
18
 * Class: The_Neverending_Home_Page relies on add_theme_support, expects specific
19
 * styling from each theme; including fixed footer.
20
 */
21
class The_Neverending_Home_Page {
22
	/**
23
	* Maximum allowed number of posts per page in $_REQUEST.
24
	*/
25
	const MAX_ALLOWED_POSTS_PER_PAGE_ΙΝ_REQUEST = 5000;
26
27
	/**
28
	 * Register actions and filters, plus parse IS settings
29
	 *
30
	 * @uses add_action, add_filter, self::get_settings
31
	 * @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...
32
	 */
33
	function __construct() {
34
		add_action( 'pre_get_posts', array( $this, 'posts_per_page_query' ) );
35
		add_action( 'admin_init', array( $this, 'settings_api_init' ) );
36
		add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
37
		add_action( 'customize_preview_init', array( $this, 'init_customizer_assets' ) );
38
		add_action( 'template_redirect', array( $this, 'ajax_response' ) );
39
		add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) );
40
		add_filter( 'infinite_scroll_query_args', array( $this, 'inject_query_args' ) );
41
		add_filter( 'infinite_scroll_allowed_vars', array( $this, 'allowed_query_vars' ) );
42
		add_action( 'the_post', array( $this, 'preserve_more_tag' ) );
43
		add_action( 'wp_footer', array( $this, 'footer' ) );
44
		add_filter( 'infinite_scroll_additional_scripts', array( $this, 'add_mejs_config' ) );
45
46
		// Plugin compatibility
47
		add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
48
49
		// AMP compatibility
50
		// needs to happen after parse_query so that Jetpack_AMP_Support::is_amp_request() is ready.
51
		add_action( 'wp', array( $this, 'amp_load_hooks' ) );
52
53
		// Parse IS settings from theme
54
		self::get_settings();
55
	}
56
57
	/**
58
	 * Initialize our static variables
59
	 */
60
	static $the_time = null;
61
	static $settings = null; // Don't access directly, instead use self::get_settings().
62
63
	static $option_name_enabled = 'infinite_scroll';
64
65
	/**
66
	 * Parse IS settings provided by theme
67
	 *
68
	 * @uses get_theme_support, infinite_scroll_has_footer_widgets, sanitize_title, add_action, get_option, wp_parse_args, is_active_sidebar
69
	 * @return object
70
	 */
71
	static function get_settings() {
72
		if ( is_null( self::$settings ) ) {
73
			$css_pattern = '#[^A-Z\d\-_]#i';
74
75
			$settings = $defaults = array(
76
				'type'            => 'scroll', // scroll | click
77
				'requested_type'  => 'scroll', // store the original type for use when logic overrides it
78
				'footer_widgets'  => false, // true | false | sidebar_id | array of sidebar_ids -- last two are checked with is_active_sidebar
79
				'container'       => 'content', // container html id
80
				'wrapper'         => true, // true | false | html class
81
				'render'          => false, // optional function, otherwise the `content` template part will be used
82
				'footer'          => true, // boolean to enable or disable the infinite footer | string to provide an html id to derive footer width from
83
				'footer_callback' => false, // function to be called to render the IS footer, in place of the default
84
				'posts_per_page'  => false, // int | false to set based on IS type
85
				'click_handle'    => true, // boolean to enable or disable rendering the click handler div. If type is click and this is false, page must include its own trigger with the HTML ID `infinite-handle`.
86
			);
87
88
			// Validate settings passed through add_theme_support()
89
			$_settings = get_theme_support( 'infinite-scroll' );
90
91
			if ( is_array( $_settings ) ) {
92
				// Preferred implementation, where theme provides an array of options
93
				if ( isset( $_settings[0] ) && is_array( $_settings[0] ) ) {
94
					foreach ( $_settings[0] as $key => $value ) {
95
						switch ( $key ) {
96
							case 'type' :
97
								if ( in_array( $value, array( 'scroll', 'click' ) ) )
98
									$settings[ $key ] = $settings['requested_type'] = $value;
99
100
								break;
101
102
							case 'footer_widgets' :
103
								if ( is_string( $value ) )
104
									$settings[ $key ] = sanitize_title( $value );
105
								elseif ( is_array( $value ) )
106
									$settings[ $key ] = array_map( 'sanitize_title', $value );
107
								elseif ( is_bool( $value ) )
108
									$settings[ $key ] = $value;
109
110
								break;
111
112
							case 'container' :
113 View Code Duplication
							case 'wrapper' :
114
								if ( 'wrapper' == $key && is_bool( $value ) ) {
115
									$settings[ $key ] = $value;
116
								} else {
117
									$value = preg_replace( $css_pattern, '', $value );
118
119
									if ( ! empty( $value ) )
120
										$settings[ $key ] = $value;
121
								}
122
123
								break;
124
125
							case 'render' :
126
								if ( false !== $value && is_callable( $value ) ) {
127
									$settings[ $key ] = $value;
128
								}
129
130
								break;
131
132 View Code Duplication
							case 'footer' :
133
								if ( is_bool( $value ) ) {
134
									$settings[ $key ] = $value;
135
								} elseif ( is_string( $value ) ) {
136
									$value = preg_replace( $css_pattern, '', $value );
137
138
									if ( ! empty( $value ) )
139
										$settings[ $key ] = $value;
140
								}
141
142
								break;
143
144
							case 'footer_callback' :
145
								if ( is_callable( $value ) )
146
									$settings[ $key ] = $value;
147
								else
148
									$settings[ $key ] = false;
149
150
								break;
151
152
							case 'posts_per_page' :
153
								if ( is_numeric( $value ) )
154
									$settings[ $key ] = (int) $value;
155
156
								break;
157
158
							case 'click_handle' :
159
								if ( is_bool( $value ) ) {
160
									$settings[ $key ] = $value;
161
								}
162
163
								break;
164
165
							default:
166
								break;
167
						}
168
					}
169
				} elseif ( is_string( $_settings[0] ) ) {
170
					// Checks below are for backwards compatibility
171
172
					// Container to append new posts to
173
					$settings['container'] = preg_replace( $css_pattern, '', $_settings[0] );
174
175
					// Wrap IS elements?
176
					if ( isset( $_settings[1] ) )
177
						$settings['wrapper'] = (bool) $_settings[1];
178
				}
179
			}
180
181
			// Always ensure all values are present in the final array
182
			$settings = wp_parse_args( $settings, $defaults );
0 ignored issues
show
Documentation introduced by
$defaults is of type array<string,string|bool...ick_handle":"boolean"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
183
184
			// Check if a legacy `infinite_scroll_has_footer_widgets()` function is defined and override the footer_widgets parameter's value.
185
			// Otherwise, if a widget area ID or array of IDs was provided in the footer_widgets parameter, check if any contains any widgets.
186
			// It is safe to use `is_active_sidebar()` before the sidebar is registered as this function doesn't check for a sidebar's existence when determining if it contains any widgets.
187
			if ( function_exists( 'infinite_scroll_has_footer_widgets' ) ) {
188
				$settings['footer_widgets'] = (bool) infinite_scroll_has_footer_widgets();
189
			} elseif ( is_array( $settings['footer_widgets'] ) ) {
190
				$sidebar_ids = $settings['footer_widgets'];
191
				$settings['footer_widgets'] = false;
192
193
				foreach ( $sidebar_ids as $sidebar_id ) {
194
					if ( is_active_sidebar( $sidebar_id ) ) {
195
						$settings['footer_widgets'] = true;
196
						break;
197
					}
198
				}
199
200
				unset( $sidebar_ids );
201
				unset( $sidebar_id );
202
			} elseif ( is_string( $settings['footer_widgets'] ) ) {
203
				$settings['footer_widgets'] = (bool) is_active_sidebar( $settings['footer_widgets'] );
204
			}
205
206
			/**
207
			 * Filter Infinite Scroll's `footer_widgets` parameter.
208
			 *
209
			 * @module infinite-scroll
210
			 *
211
			 * @since 2.0.0
212
			 *
213
			 * @param bool $settings['footer_widgets'] Does the current theme have Footer Widgets.
214
			 */
215
			$settings['footer_widgets'] = apply_filters( 'infinite_scroll_has_footer_widgets', $settings['footer_widgets'] );
216
217
			// Finally, after all of the sidebar checks and filtering, ensure that a boolean value is present, otherwise set to default of `false`.
218
			if ( ! is_bool( $settings['footer_widgets'] ) )
219
				$settings['footer_widgets'] = false;
220
221
			// Ensure that IS is enabled and no footer widgets exist if the IS type isn't already "click".
222
			if ( 'click' != $settings['type'] ) {
223
				// Check the setting status
224
				$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
225
226
				// Footer content or Reading option check
227
				if ( $settings['footer_widgets'] || $disabled )
228
					$settings['type'] = 'click';
229
			}
230
231
			// Force display of the click handler and attendant bits when the type isn't `click`
232
			if ( 'click' !== $settings['type'] ) {
233
				$settings['click_handle'] = true;
234
			}
235
236
			// Store final settings in a class static to avoid reparsing
237
			/**
238
			 * Filter the array of Infinite Scroll settings.
239
			 *
240
			 * @module infinite-scroll
241
			 *
242
			 * @since 2.0.0
243
			 *
244
			 * @param array $settings Array of Infinite Scroll settings.
245
			 */
246
			self::$settings = apply_filters( 'infinite_scroll_settings', $settings );
247
		}
248
249
		/** This filter is already documented in modules/infinite-scroll/infinity.php */
250
		return (object) apply_filters( 'infinite_scroll_settings', self::$settings );
251
	}
252
253
	/**
254
	 * Number of posts per page.
255
	 *
256
	 * @uses self::wp_query, self::get_settings, apply_filters
257
	 * @return int
258
	 */
259
	static function posts_per_page() {
260
		$posts_per_page             = self::get_settings()->posts_per_page ? self::get_settings()->posts_per_page : self::wp_query()->get( 'posts_per_page' );
261
		$posts_per_page_core_option = get_option( 'posts_per_page' );
262
263
		// If Infinite Scroll is set to click, and if the site owner changed posts_per_page, let's use that.
264
		if (
265
			'click' === self::get_settings()->type
266
				&& ( '10' !== $posts_per_page_core_option )
267
		) {
268
			$posts_per_page = $posts_per_page_core_option;
269
		}
270
271
		// Take JS query into consideration here.
272
		$posts_per_page_in_request = isset( $_REQUEST['query_args']['posts_per_page'] ) ? (int) $_REQUEST['query_args']['posts_per_page'] : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
273
		if ( $posts_per_page_in_request > 0 &&
274
			self::MAX_ALLOWED_POSTS_PER_PAGE_ΙΝ_REQUEST >= $posts_per_page_in_request
275
		) {
276
			$posts_per_page = $posts_per_page_in_request;
277
		}
278
279
		/**
280
		 * Filter the number of posts per page.
281
		 *
282
		 * @module infinite-scroll
283
		 *
284
		 * @since 6.0.0
285
		 *
286
		 * @param int $posts_per_page The number of posts to display per page.
287
		 */
288
		return (int) apply_filters( 'infinite_scroll_posts_per_page', $posts_per_page );
289
	}
290
291
	/**
292
	 * Retrieve the query used with Infinite Scroll
293
	 *
294
	 * @global $wp_the_query
295
	 * @uses apply_filters
296
	 * @return object
297
	 */
298
	static function wp_query() {
299
		global $wp_the_query;
300
		/**
301
		 * Filter the Infinite Scroll query object.
302
		 *
303
		 * @module infinite-scroll
304
		 *
305
		 * @since 2.2.1
306
		 *
307
		 * @param WP_Query $wp_the_query WP Query.
308
		 */
309
		return apply_filters( 'infinite_scroll_query_object', $wp_the_query );
310
	}
311
312
	/**
313
	 * Has infinite scroll been triggered?
314
	 */
315
	static function got_infinity() {
316
		/**
317
		 * Filter the parameter used to check if Infinite Scroll has been triggered.
318
		 *
319
		 * @module infinite-scroll
320
		 *
321
		 * @since 3.9.0
322
		 *
323
		 * @param bool isset( $_GET[ 'infinity' ] ) Return true if the "infinity" parameter is set.
324
		 */
325
		return apply_filters( 'infinite_scroll_got_infinity', isset( $_GET[ 'infinity' ] ) );
326
	}
327
328
	/**
329
	 * Is this guaranteed to be the last batch of posts?
330
	 */
331
	static function is_last_batch() {
332
		/**
333
		 * Override whether or not this is the last batch for a request
334
		 *
335
		 * @module infinite-scroll
336
		 *
337
		 * @since 4.8.0
338
		 *
339
		 * @param bool|null null                 Bool if value should be overridden, null to determine from query
340
		 * @param object    self::wp_query()     WP_Query object for current request
341
		 * @param object    self::get_settings() Infinite Scroll settings
342
		 */
343
		$override = apply_filters( 'infinite_scroll_is_last_batch', null, self::wp_query(), self::get_settings() );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with self::wp_query().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
344
		if ( is_bool( $override ) ) {
345
			return $override;
346
		}
347
348
		$entries = (int) self::wp_query()->found_posts;
349
		$posts_per_page = self::posts_per_page();
350
351
		// This is to cope with an issue in certain themes or setups where posts are returned but found_posts is 0.
352
		if ( 0 == $entries ) {
353
			return (bool) ( count( self::wp_query()->posts ) < $posts_per_page );
354
		}
355
		$paged = max( 1, self::wp_query()->get( 'paged' ) );
356
357
		// Are there enough posts for more than the first page?
358
		if ( $entries <= $posts_per_page ) {
359
			return true;
360
		}
361
362
		// Calculate entries left after a certain number of pages
363
		if ( $paged && $paged > 1 ) {
364
			$entries -= $posts_per_page * $paged;
365
		}
366
367
		// Are there some entries left to display?
368
		return $entries <= 0;
369
	}
370
371
	/**
372
	 * The more tag will be ignored by default if the blog page isn't our homepage.
373
	 * Let's force the $more global to false.
374
	 */
375
	function preserve_more_tag( $array ) {
376
		global $more;
377
378
		if ( self::got_infinity() )
379
			$more = 0; //0 = show content up to the more tag. Add more link.
380
381
		return $array;
382
	}
383
384
	/**
385
	 * Add a checkbox field to Settings > Reading
386
	 * for enabling infinite scroll.
387
	 *
388
	 * Only show if the current theme supports infinity.
389
	 *
390
	 * @uses current_theme_supports, add_settings_field, __, register_setting
391
	 * @action admin_init
392
	 * @return null
393
	 */
394
	function settings_api_init() {
395
		if ( ! current_theme_supports( 'infinite-scroll' ) )
396
			return;
397
398
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
399
			// This setting is no longer configurable in wp-admin on WordPress.com -- leave a pointer
400
			add_settings_field( self::$option_name_enabled,
401
				'<span id="infinite-scroll-options">' . esc_html__( 'Infinite Scroll Behavior', 'jetpack' ) . '</span>',
402
				array( $this, 'infinite_setting_html_calypso_placeholder' ),
403
				'reading'
404
			);
405
			return;
406
		}
407
408
		// Add the setting field [infinite_scroll] and place it in Settings > Reading
409
		add_settings_field( self::$option_name_enabled, '<span id="infinite-scroll-options">' . esc_html__( 'Infinite Scroll Behavior', 'jetpack' ) . '</span>', array( $this, 'infinite_setting_html' ), 'reading' );
410
		register_setting( 'reading', self::$option_name_enabled, 'esc_attr' );
411
	}
412
413
	function infinite_setting_html_calypso_placeholder() {
414
		$details     = get_blog_details();
415
		$writing_url = Redirect::get_url( 'calypso-settings-writing', array( 'site' => $details->domain ) );
416
		echo '<span>' . sprintf(
417
			/* translators: Variables are the enclosing link to the settings page */
418
			esc_html__( 'This option has moved. You can now manage it %1$shere%2$s.', 'jetpack' ),
419
			'<a href="' . esc_url( $writing_url ) . '">',
420
			'</a>'
421
		) . '</span>';
422
	}
423
424
	/**
425
	 * HTML code to display a checkbox true/false option
426
	 * for the infinite_scroll setting.
427
	 */
428
	function infinite_setting_html() {
429
		$notice = '<em>' . __( 'We&rsquo;ve changed this option to a click-to-scroll version for you since you have footer widgets in Appearance &rarr; Widgets, or your theme uses click-to-scroll as the default behavior.', 'jetpack' ) . '</em>';
430
431
		// If the blog has footer widgets, show a notice instead of the checkbox
432
		if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) {
433
			echo '<label>' . $notice . '</label>';
434
		} else {
435
			echo '<label><input name="infinite_scroll" type="checkbox" value="1" ' . checked( 1, '' !== get_option( self::$option_name_enabled ), false ) . ' /> ' . esc_html__( 'Check to load posts as you scroll. Uncheck to show clickable button to load posts', 'jetpack' ) . '</label>';
436
			echo '<p class="description">' . esc_html( sprintf( _n( 'Shows %s post on each load.', 'Shows %s posts on each load.', self::posts_per_page(), 'jetpack' ), number_format_i18n( self::posts_per_page() ) ) ) . '</p>';
437
		}
438
	}
439
440
	/**
441
	 * Does the legwork to determine whether the feature is enabled.
442
	 *
443
	 * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action
444
	 * @action template_redirect
445
	 * @return null
446
	 */
447
	function action_template_redirect() {
448
		// Check that we support infinite scroll, and are on the home page.
449
		if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
450
			return;
451
452
		$id = self::get_settings()->container;
453
454
		// Check that we have an id.
455
		if ( empty( $id ) )
456
			return;
457
458
		// AMP infinite scroll functionality will start on amp_load_hooks().
459
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
460
			return;
461
		}
462
463
		// Add our scripts.
464
		wp_register_script(
465
			'the-neverending-homepage',
466
			Assets::get_file_url_for_environment(
467
				'_inc/build/infinite-scroll/infinity.min.js',
468
				'modules/infinite-scroll/infinity.js'
469
			),
470
			array(),
471
			JETPACK__VERSION . '-is5.0.0', // Added for ability to cachebust on WP.com.
472
			true
473
		);
474
475
		// Add our default styles.
476
		wp_register_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20140422' );
477
478
		// Make sure there are enough posts for IS
479
		if ( self::is_last_batch() ) {
480
			return;
481
		}
482
483
		// Add our scripts.
484
		wp_enqueue_script( 'the-neverending-homepage' );
485
486
		// Add our default styles.
487
		wp_enqueue_style( 'the-neverending-homepage' );
488
489
		add_action( 'wp_footer', array( $this, 'action_wp_footer_settings' ), 2 );
490
491
		add_action( 'wp_footer', array( $this, 'action_wp_footer' ), 21 ); // Core prints footer scripts at priority 20, so we just need to be one later than that
492
493
		add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ), 10, 3 );
494
	}
495
496
	/**
497
	 * Initialize the Customizer logic separately from the main JS.
498
	 *
499
	 * @since 8.4.0
500
	 */
501
	public function init_customizer_assets() {
502
		// Add our scripts.
503
		wp_register_script(
504
			'the-neverending-homepage-customizer',
505
			Assets::get_file_url_for_environment(
506
				'_inc/build/infinite-scroll/infinity-customizer.min.js',
507
				'modules/infinite-scroll/infinity-customizer.js'
508
			),
509
			array( 'customize-base' ),
510
			JETPACK__VERSION . '-is5.0.0', // Added for ability to cachebust on WP.com.
511
			true
512
		);
513
514
		wp_enqueue_script( 'the-neverending-homepage-customizer' );
515
	}
516
517
	/**
518
	 * Returns classes to be added to <body>. If it's enabled, 'infinite-scroll'. If set to continuous scroll, adds 'neverending' too.
519
	 *
520
	 * @since 4.7.0 No longer added as a 'body_class' filter but passed to JS environment and added using JS.
521
	 *
522
	 * @return string
523
	 */
524
	function body_class() {
525
		$classes = '';
526
		// Do not add infinity-scroll class if disabled through the Reading page
527
		$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
528
		if ( ! $disabled || 'click' == self::get_settings()->type ) {
529
			$classes = 'infinite-scroll';
530
531
			if ( 'scroll' == self::get_settings()->type )
532
				$classes .= ' neverending';
533
		}
534
535
		return $classes;
536
	}
537
538
	/**
539
	 * In case IS is activated on search page, we have to exclude initially loaded posts which match the keyword by title, not the content as they are displayed before content-matching ones
540
	 *
541
	 * @uses self::wp_query
542
	 * @uses self::get_last_post_date
543
	 * @uses self::has_only_title_matching_posts
544
	 * @return array
545
	 */
546
	function get_excluded_posts() {
547
548
		$excluded_posts = array();
549
		//loop through posts returned by wp_query call
550
		foreach( self::wp_query()->get_posts() as $post ) {
551
552
			$orderby = isset( self::wp_query()->query_vars['orderby'] ) ? self::wp_query()->query_vars['orderby'] : '';
553
			$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
554
			if ( 'modified' === $orderby || false === $post_date ) {
555
				$post_date = $post->post_modified;
556
			}
557
558
			//in case all posts initially displayed match the keyword by title we add em all to excluded posts array
559
			//else, we add only posts which are older than last_post_date param as newer are natually excluded by last_post_date condition in the SQL query
560
			if ( self::has_only_title_matching_posts() || $post_date <= self::get_last_post_date() ) {
561
				array_push( $excluded_posts, $post->ID );
562
			}
563
		}
564
		return $excluded_posts;
565
	}
566
567
	/**
568
	 * In case IS is active on search, we have to exclude posts matched by title rather than by post_content in order to prevent dupes on next pages
569
	 *
570
	 * @uses self::wp_query
571
	 * @uses self::get_excluded_posts
572
	 * @return array
573
	 */
574
	function get_query_vars() {
575
576
		$query_vars = self::wp_query()->query_vars;
577
		//applies to search page only
578
		if ( true === self::wp_query()->is_search() ) {
579
			//set post__not_in array in query_vars in case it does not exists
580
			if ( false === isset( $query_vars['post__not_in'] ) ) {
581
				$query_vars['post__not_in'] = array();
582
			}
583
			//get excluded posts
584
			$excluded = self::get_excluded_posts();
585
			//merge them with other post__not_in posts (eg.: sticky posts)
586
			$query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $excluded );
587
		}
588
		return $query_vars;
589
	}
590
591
	/**
592
	 * This function checks whether all posts returned by initial wp_query match the keyword by title
593
	 * The code used in this function is borrowed from WP_Query class where it is used to construct like conditions for keywords
594
	 *
595
	 * @uses self::wp_query
596
	 * @return bool
597
	 */
598
	function has_only_title_matching_posts() {
599
600
		//apply following logic for search page results only
601
		if ( false === self::wp_query()->is_search() ) {
602
			return false;
603
		}
604
605
		//grab the last posts in the stack as if the last one is title-matching the rest is title-matching as well
606
		$post = end( self::wp_query()->posts );
607
608
		//code inspired by WP_Query class
609
		if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', self::wp_query()->get( 's' ), $matches ) ) {
610
			$search_terms = self::wp_query()->query_vars['search_terms'];
611
			// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
612
			if ( empty( $search_terms ) || count( $search_terms ) > 9 ) {
613
				$search_terms = array( self::wp_query()->get( 's' ) );
614
			}
615
		} else {
616
			$search_terms = array( self::wp_query()->get( 's' ) );
617
		}
618
619
		//actual testing. As search query combines multiple keywords with AND, it's enough to check if any of the keywords is present in the title
620
		$term = current( $search_terms );
621
		if ( ! empty( $term ) && false !== strpos( $post->post_title, $term ) ) {
622
			return true;
623
		}
624
625
		return false;
626
	}
627
628
	/**
629
	 * Grab the timestamp for the initial query's last post.
630
	 *
631
	 * This takes into account the query's 'orderby' parameter and returns
632
	 * false if the posts are not ordered by date.
633
	 *
634
	 * @uses self::got_infinity
635
	 * @uses self::has_only_title_matching_posts
636
	 * @uses self::wp_query
637
	 * @return string 'Y-m-d H:i:s' or false
638
	 */
639
	function get_last_post_date() {
640
		if ( self::got_infinity() )
641
			return;
642
643
		if ( ! self::wp_query()->have_posts() ) {
644
			return null;
645
		}
646
647
		//In case there are only title-matching posts in the initial WP_Query result, we don't want to use the last_post_date param yet
648
		if ( true === self::has_only_title_matching_posts() ) {
649
			return false;
650
		}
651
652
		$post = end( self::wp_query()->posts );
653
		$orderby = isset( self::wp_query()->query_vars['orderby'] ) ?
654
			self::wp_query()->query_vars['orderby'] : '';
655
		$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
656 View Code Duplication
		switch ( $orderby ) {
657
			case 'modified':
658
				return $post->post_modified;
659
			case 'date':
660
			case '':
661
				return $post_date;
662
			default:
663
				return false;
664
		}
665
	}
666
667
	/**
668
	 * Returns the appropriate `wp_posts` table field for a given query's
669
	 * 'orderby' parameter, if applicable.
670
	 *
671
	 * @param optional object $query
672
	 * @uses self::wp_query
673
	 * @return string or false
674
	 */
675
	function get_query_sort_field( $query = null ) {
676
		if ( empty( $query ) )
677
			$query = self::wp_query();
678
679
		$orderby = isset( $query->query_vars['orderby'] ) ? $query->query_vars['orderby'] : '';
680
681 View Code Duplication
		switch ( $orderby ) {
682
			case 'modified':
683
				return 'post_modified';
684
			case 'date':
685
			case '':
686
				return 'post_date';
687
			default:
688
				return false;
689
		}
690
	}
691
692
	/**
693
	 * Create a where clause that will make sure post queries return posts
694
	 * in the correct order, without duplicates, if a new post is added
695
	 * and we're sorting by post date.
696
	 *
697
	 * @global $wpdb
698
	 * @param string $where
699
	 * @param object $query
700
	 * @uses apply_filters
701
	 * @filter posts_where
702
	 * @return string
703
	 */
704
	function query_time_filter( $where, $query ) {
705
		if ( self::got_infinity() ) {
706
			global $wpdb;
707
708
			$sort_field = self::get_query_sort_field( $query );
709
710
			if ( 'post_date' !== $sort_field || 'DESC' !== $_REQUEST['query_args']['order'] ) {
711
				return $where;
712
			}
713
714
			$query_before = sanitize_text_field( wp_unslash( $_REQUEST['query_before'] ) );
715
716
			if ( empty( $query_before ) ) {
717
				return $where;
718
			}
719
720
			// Construct the date query using our timestamp
721
			$clause = $wpdb->prepare( " AND {$wpdb->posts}.post_date <= %s", $query_before );
722
723
			/**
724
			 * Filter Infinite Scroll's SQL date query making sure post queries
725
			 * will always return results prior to (descending sort)
726
			 * or before (ascending sort) the last post date.
727
			 *
728
			 * @module infinite-scroll
729
			 *
730
			 * @param string $clause SQL Date query.
731
			 * @param object $query Query.
732
			 * @param string $operator @deprecated Query operator.
733
			 * @param string $last_post_date @deprecated Last Post Date timestamp.
734
			 */
735
			$operator       = 'ASC' === $_REQUEST['query_args']['order'] ? '>' : '<';
736
			$last_post_date = sanitize_text_field( wp_unslash( $_REQUEST['last_post_date'] ) );
737
			$where         .= apply_filters( 'infinite_scroll_posts_where', $clause, $query, $operator, $last_post_date );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $query.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
738
		}
739
740
		return $where;
741
	}
742
743
	/**
744
	 * Let's overwrite the default post_per_page setting to always display a fixed amount.
745
	 *
746
	 * @param object $query
747
	 * @uses is_admin, self::archive_supports_infinity, self::get_settings
748
	 * @return null
749
	 */
750
	function posts_per_page_query( $query ) {
751
		if ( ! is_admin() && self::archive_supports_infinity() && $query->is_main_query() )
752
			$query->set( 'posts_per_page', self::posts_per_page() );
753
	}
754
755
	/**
756
	 * Check if the IS output should be wrapped in a div.
757
	 * Setting value can be a boolean or a string specifying the class applied to the div.
758
	 *
759
	 * @uses self::get_settings
760
	 * @return bool
761
	 */
762
	function has_wrapper() {
763
		return (bool) self::get_settings()->wrapper;
764
	}
765
766
	/**
767
	 * Returns the Ajax url
768
	 *
769
	 * @global $wp
770
	 * @uses home_url, add_query_arg, apply_filters
771
	 * @return string
772
	 */
773
	function ajax_url() {
774
		$base_url = set_url_scheme( home_url( '/' ) );
775
776
		$ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url );
777
778
		/**
779
		 * Filter the Infinite Scroll Ajax URL.
780
		 *
781
		 * @module infinite-scroll
782
		 *
783
		 * @since 2.0.0
784
		 *
785
		 * @param string $ajaxurl Infinite Scroll Ajax URL.
786
		 */
787
		return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl );
788
	}
789
790
	/**
791
	 * Our own Ajax response, avoiding calling admin-ajax
792
	 */
793
	function ajax_response() {
794
		// Only proceed if the url query has a key of "Infinity"
795
		if ( ! self::got_infinity() )
796
			return false;
797
798
		// This should already be defined below, but make sure.
799
		if ( ! defined( 'DOING_AJAX' ) ) {
800
			define( 'DOING_AJAX', true );
801
		}
802
803
		@header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
804
		send_nosniff_header();
805
806
		/**
807
		 * Fires at the end of the Infinite Scroll Ajax response.
808
		 *
809
		 * @module infinite-scroll
810
		 *
811
		 * @since 2.0.0
812
		 */
813
		do_action( 'custom_ajax_infinite_scroll' );
814
		die( '0' );
815
	}
816
817
	/**
818
	 * Alias for renamed class method.
819
	 *
820
	 * Previously, JS settings object was unnecessarily output in the document head.
821
	 * When the hook was changed, the method name no longer made sense.
822
	 */
823
	function action_wp_head() {
824
		$this->action_wp_footer_settings();
825
	}
826
827
	/**
828
	 * Prints the relevant infinite scroll settings in JS.
829
	 *
830
	 * @global $wp_rewrite
831
	 * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action, self::get_query_vars
832
	 * @action wp_footer
833
	 * @return string
834
	 */
835
	function action_wp_footer_settings() {
836
		global $wp_rewrite;
837
		global $currentday;
838
839
		// Default click handle text
840
		$click_handle_text = __( 'Older posts', 'jetpack' );
841
842
		// If a single CPT is displayed, use its plural name instead of "posts"
843
		// Could be empty (posts) or an array of multiple post types.
844
		// In the latter two cases cases, the default text is used, leaving the `infinite_scroll_js_settings` filter for further customization.
845
		$post_type = self::wp_query()->get( 'post_type' );
846
847
		// If it's a taxonomy, try to change the button text.
848
		if ( is_tax() ) {
849
			// Get current taxonomy slug.
850
			$taxonomy_slug = self::wp_query()->get( 'taxonomy' );
851
852
			// Get taxonomy settings.
853
			$taxonomy = get_taxonomy( $taxonomy_slug );
854
855
			// Check if the taxonomy is attached to one post type only and use its plural name.
856
			// If not, use "Posts" without confusing the users.
857
			if (
858
				is_a( $taxonomy, 'WP_Taxonomy' )
859
				&& is_countable( $taxonomy->object_type )
860
				&& count( $taxonomy->object_type ) < 2
861
			) {
862
				$post_type = $taxonomy->object_type[0];
863
			}
864
		}
865
866
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
867
			$post_type = get_post_type_object( $post_type );
868
869
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
870
				if ( isset( $post_type->labels->name ) ) {
871
					$cpt_text = $post_type->labels->name;
872
				} elseif ( isset( $post_type->label ) ) {
873
					$cpt_text = $post_type->label;
874
				}
875
876
				if ( isset( $cpt_text ) ) {
877
					/* translators: %s is the name of a custom post type */
878
					$click_handle_text = sprintf( __( 'More %s', 'jetpack' ), $cpt_text );
879
					unset( $cpt_text );
880
				}
881
			}
882
		}
883
884
		unset( $post_type );
885
886
		// Base JS settings
887
		$js_settings = array(
888
			'id'               => self::get_settings()->container,
889
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
890
			'type'             => esc_js( self::get_settings()->type ),
891
			'wrapper'          => self::has_wrapper(),
892
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
893
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
894
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
895
			'text'             => esc_js( $click_handle_text ),
896
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
897
			'currentday'       => $currentday,
898
			'order'            => 'DESC',
899
			'scripts'          => array(),
900
			'styles'           => array(),
901
			'google_analytics' => false,
902
			'offset'           => max( 1, self::wp_query()->get( 'paged' ) ), // Pass through the current page so we can use that to offset the first load.
903
			'history'          => array(
904
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( esc_url( get_home_url() ) ) ),
905
				'path'                 => self::get_request_path(),
906
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
907
				'parameters'           => self::get_request_parameters(),
908
			),
909
			'query_args'      => self::get_query_vars(),
910
			'query_before'    => current_time( 'mysql' ),
911
			'last_post_date'  => self::get_last_post_date(),
912
			'body_class'	  => self::body_class(),
913
			'loading_text'	  => esc_js( __( 'Loading new page', 'jetpack' ) ),
914
		);
915
916
		// Optional order param
917
		if ( isset( $_REQUEST['order'] ) ) {
918
			$order = strtoupper( $_REQUEST['order'] );
919
920
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
921
				$js_settings['order'] = $order;
922
		}
923
924
		/**
925
		 * Filter the Infinite Scroll JS settings outputted in the head.
926
		 *
927
		 * @module infinite-scroll
928
		 *
929
		 * @since 2.0.0
930
		 *
931
		 * @param array $js_settings Infinite Scroll JS settings.
932
		 */
933
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
934
935
		/**
936
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
937
		 *
938
		 * @module infinite-scroll
939
		 *
940
		 * @since 2.0.0
941
		 */
942
		do_action( 'infinite_scroll_wp_head' );
943
944
		?>
945
		<script type="text/javascript">
946
		//<![CDATA[
947
		var infiniteScroll = JSON.parse( decodeURIComponent( '<?php echo
948
			rawurlencode( json_encode( array( 'settings' => $js_settings ) ) );
949
		?>' ) );
950
		//]]>
951
		</script>
952
		<?php
953
	}
954
955
	/**
956
	 * Build path data for current request.
957
	 * Used for Google Analytics and pushState history tracking.
958
	 *
959
	 * @global $wp_rewrite
960
	 * @global $wp
961
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
962
	 * @return string|bool
963
	 */
964
	private function get_request_path() {
965
		global $wp_rewrite;
966
967
		if ( $wp_rewrite->using_permalinks() ) {
968
			global $wp;
969
970
			// If called too early, bail
971
			if ( ! isset( $wp->request ) )
972
				return false;
973
974
			// Determine path for paginated version of current request
975
			if ( false != preg_match( '#' . $wp_rewrite->pagination_base . '/\d+/?$#i', $wp->request ) )
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('#' . $wp_rew...d+/?$#i', $wp->request) of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
976
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
977
			else
978
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
979
980
			// Slashes everywhere we need them
981
			if ( 0 !== strpos( $path, '/' ) )
982
				$path = '/' . $path;
983
984
			$path = user_trailingslashit( $path );
985
		} else {
986
			// Clean up raw $_REQUEST input
987
			$path = array_map( 'sanitize_text_field', $_REQUEST );
988
			$path = array_filter( $path );
989
990
			$path['paged'] = '%d';
991
992
			$path = add_query_arg( $path, '/' );
993
		}
994
995
		return empty( $path ) ? false : $path;
996
	}
997
998
	/**
999
	 * Return query string for current request, prefixed with '?'.
1000
	 *
1001
	 * @return string
1002
	 */
1003
	private function get_request_parameters() {
1004
		$uri = $_SERVER[ 'REQUEST_URI' ];
1005
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
1006
		if ( $count != 1 )
1007
			return '';
1008
		return $uri;
1009
	}
1010
1011
	/**
1012
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
1013
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
1014
	 *
1015
	 * @global $wp_scripts, $wp_styles
1016
	 * @action wp_footer
1017
	 * @return string
1018
	 */
1019
	function action_wp_footer() {
1020
		global $wp_scripts, $wp_styles;
1021
1022
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
1023
		/**
1024
		 * Filter the list of scripts already present on the page.
1025
		 *
1026
		 * @module infinite-scroll
1027
		 *
1028
		 * @since 2.1.2
1029
		 *
1030
		 * @param array $scripts Array of scripts present on the page.
1031
		 */
1032
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
1033
1034
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
1035
		/**
1036
		 * Filter the list of styles already present on the page.
1037
		 *
1038
		 * @module infinite-scroll
1039
		 *
1040
		 * @since 2.1.2
1041
		 *
1042
		 * @param array $styles Array of styles present on the page.
1043
		 */
1044
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
1045
1046
		?><script type="text/javascript">
1047
			(function() {
1048
				var extend = function(out) {
1049
					out = out || {};
1050
1051
					for (var i = 1; i < arguments.length; i++) {
1052
						if (!arguments[i])
1053
						continue;
1054
1055
						for (var key in arguments[i]) {
1056
						if (arguments[i].hasOwnProperty(key))
1057
							out[key] = arguments[i][key];
1058
						}
1059
					}
1060
1061
					return out;
1062
				};
1063
				extend( window.infiniteScroll.settings.scripts, <?php echo wp_json_encode( $scripts ); ?> );
1064
				extend( window.infiniteScroll.settings.styles, <?php echo wp_json_encode( $styles ); ?> );
1065
			})();
1066
		</script>
1067
		<?php
1068
		$aria_live = 'assertive';
1069
		if ( 'scroll' === self::get_settings()->type ) {
1070
			$aria_live = 'polite';
1071
		}
1072
		?>
1073
		<span id="infinite-aria" aria-live="<?php echo esc_attr( $aria_live ); ?>"></span>
1074
		<?php
1075
	}
1076
1077
	/**
1078
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
1079
	 *
1080
	 * @global $wp_scripts
1081
	 * @uses sanitize_text_field, add_query_arg
1082
	 * @filter infinite_scroll_results
1083
	 * @return array
1084
	 */
1085
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
1086
		// Don't bother unless there are posts to display
1087
		if ( 'success' != $results['type'] )
1088
			return $results;
1089
1090
		// Parse and sanitize the script handles already output
1091
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
1092
1093
		if ( is_array( $initial_scripts ) ) {
1094
			global $wp_scripts;
1095
1096
			// Identify new scripts needed by the latest set of IS posts
1097
			$new_scripts = array_filter(
1098
				$wp_scripts->done,
1099
				function ( $script_name ) use ( $initial_scripts ) {
1100
					// Jetpack block scripts should always be sent, even if they've been
1101
					// sent before. These scripts only run once on when loaded, they don't
1102
					// watch for new blocks being added.
1103
					if ( 0 === strpos( $script_name, 'jetpack-block-' ) ) {
1104
						return true;
1105
					}
1106
1107
					return ! in_array( $script_name, $initial_scripts, true );
1108
				}
1109
			);
1110
1111
			// If new scripts are needed, extract relevant data from $wp_scripts
1112
			if ( ! empty( $new_scripts ) ) {
1113
				$results['scripts'] = array();
1114
1115
				foreach ( $new_scripts as $handle ) {
1116
					// Abort if somehow the handle doesn't correspond to a registered script
1117
					// or if the script doesn't have `src` set.
1118
					$script_not_registered = ! isset( $wp_scripts->registered[ $handle ] );
1119
					$empty_src             = empty( $wp_scripts->registered[ $handle ]->src );
1120
					if ( $script_not_registered || $empty_src ) {
1121
						continue;
1122
					}
1123
1124
					// Provide basic script data
1125
					$script_data = array(
1126
						'handle'        => $handle,
1127
						'footer'        => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer, true ) ),
1128
						'extra_data'    => $wp_scripts->print_extra_script( $handle, false ),
1129
						'before_handle' => $wp_scripts->print_inline_script( $handle, 'before', false ),
1130
						'after_handle'  => $wp_scripts->print_inline_script( $handle, 'after', false ),
1131
					);
1132
1133
					// Base source
1134
					$src = $wp_scripts->registered[ $handle ]->src;
1135
1136
					// Take base_url into account
1137
					if ( strpos( $src, 'http' ) !== 0 )
1138
						$src = $wp_scripts->base_url . $src;
1139
1140
					// Version and additional arguments
1141 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
1142
						$ver = '';
1143
					else
1144
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
1145
1146 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
1147
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
1148
1149
					// Full script source with version info
1150
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
1151
1152
					// Add script to data that will be returned to IS JS
1153
					array_push( $results['scripts'], $script_data );
1154
				}
1155
			}
1156
		}
1157
1158
		// Expose additional script data to filters, but only include in final `$results` array if needed.
1159
		if ( ! isset( $results['scripts'] ) )
1160
			$results['scripts'] = array();
1161
1162
		/**
1163
		 * Filter the additional scripts required by the latest set of IS posts.
1164
		 *
1165
		 * @module infinite-scroll
1166
		 *
1167
		 * @since 2.1.2
1168
		 *
1169
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
1170
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
1171
		 * @param array $results Array of Infinite Scroll results.
1172
		 * @param array $query_args Array of Query arguments.
1173
		 * @param WP_Query $wp_query WP Query.
1174
		 */
1175
		$results['scripts'] = apply_filters(
1176
			'infinite_scroll_additional_scripts',
1177
			$results['scripts'],
1178
			$initial_scripts,
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $initial_scripts.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1179
			$results,
1180
			$query_args,
1181
			$wp_query
1182
		);
1183
1184
		if ( empty( $results['scripts'] ) )
1185
			unset( $results['scripts' ] );
1186
1187
		// Parse and sanitize the style handles already output
1188
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
1189
1190
		if ( is_array( $initial_styles ) ) {
1191
			global $wp_styles;
1192
1193
			// Identify new styles needed by the latest set of IS posts
1194
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1195
1196
			// If new styles are needed, extract relevant data from $wp_styles
1197
			if ( ! empty( $new_styles ) ) {
1198
				$results['styles'] = array();
1199
1200
				foreach ( $new_styles as $handle ) {
1201
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1202
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1203
						continue;
1204
1205
					// Provide basic style data
1206
					$style_data = array(
1207
						'handle' => $handle,
1208
						'media'  => 'all'
1209
					);
1210
1211
					// Base source
1212
					$src = $wp_styles->registered[ $handle ]->src;
1213
1214
					// Take base_url into account
1215
					if ( strpos( $src, 'http' ) !== 0 )
1216
						$src = $wp_styles->base_url . $src;
1217
1218
					// Version and additional arguments
1219 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1220
						$ver = '';
1221
					else
1222
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1223
1224 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1225
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1226
1227
					// Full stylesheet source with version info
1228
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1229
1230
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1231
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1232
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1233
						$style_data['conditional'] = str_replace( array(
1234
							'lte',
1235
							'lt',
1236
							'gte',
1237
							'gt'
1238
						), array(
1239
							'%ver <=',
1240
							'%ver <',
1241
							'%ver >=',
1242
							'%ver >',
1243
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1244
1245
						// Next, replace any !IE checks. These shouldn't be present since WP's conditional stylesheet implementation doesn't support them, but someone could be _doing_it_wrong().
1246
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1247
1248
						// Lastly, remove the IE strings
1249
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1250
					}
1251
1252
					// Parse requested media context for stylesheet
1253 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1254
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1255
1256
					// Add stylesheet to data that will be returned to IS JS
1257
					array_push( $results['styles'], $style_data );
1258
				}
1259
			}
1260
		}
1261
1262
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1263
		if ( ! isset( $results['styles'] ) )
1264
			$results['styles'] = array();
1265
1266
		/**
1267
		 * Filter the additional styles required by the latest set of IS posts.
1268
		 *
1269
		 * @module infinite-scroll
1270
		 *
1271
		 * @since 2.1.2
1272
		 *
1273
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1274
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1275
		 * @param array $results Array of Infinite Scroll results.
1276
		 * @param array $query_args Array of Query arguments.
1277
		 * @param WP_Query $wp_query WP Query.
1278
		 */
1279
		$results['styles'] = apply_filters(
1280
			'infinite_scroll_additional_stylesheets',
1281
			$results['styles'],
1282
			$initial_styles,
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $initial_styles.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1283
			$results,
1284
			$query_args,
1285
			$wp_query
1286
		);
1287
1288
		if ( empty( $results['styles'] ) )
1289
			unset( $results['styles' ] );
1290
1291
		// Lastly, return the IS results array
1292
		return $results;
1293
	}
1294
1295
	/**
1296
	 * Runs the query and returns the results via JSON.
1297
	 * Triggered by an AJAX request.
1298
	 *
1299
	 * @global $wp_query
1300
	 * @global $wp_the_query
1301
	 * @uses current_theme_supports, get_option, self::wp_query, current_user_can, apply_filters, self::get_settings, add_filter, WP_Query, remove_filter, have_posts, wp_head, do_action, add_action, this::render, this::has_wrapper, esc_attr, wp_footer, sharing_register_post_for_share_counts, get_the_id
1302
	 * @return string or null
1303
	 */
1304
	function query() {
1305
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1306
			die;
1307
1308
		$page = (int) $_REQUEST['page'];
1309
1310
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1311
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1312
			global $previousday;
1313
			$previousday = $_REQUEST['currentday'];
1314
		}
1315
1316
		$post_status = array( 'publish' );
1317
		if ( current_user_can( 'read_private_posts' ) )
1318
			array_push( $post_status, 'private' );
1319
1320
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1321
1322
		$query_args = array_merge( self::wp_query()->query_vars, array(
1323
			'paged'          => $page,
1324
			'post_status'    => $post_status,
1325
			'posts_per_page' => self::posts_per_page(),
1326
			'order'          => $order
1327
		) );
1328
1329
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1330
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1331
			unset( $query_args['s'] );
1332
		}
1333
1334
		// By default, don't query for a specific page of a paged post object.
1335
		// This argument can come from merging self::wp_query() into $query_args above.
1336
		// Since IS is only used on archives, we should always display the first page of any paged content.
1337
		unset( $query_args['page'] );
1338
1339
		/**
1340
		 * Filter the array of main query arguments.
1341
		 *
1342
		 * @module infinite-scroll
1343
		 *
1344
		 * @since 2.0.1
1345
		 *
1346
		 * @param array $query_args Array of Query arguments.
1347
		 */
1348
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1349
1350
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1351
1352
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = $infinite_scroll_query = new WP_Query();
1353
1354
		$infinite_scroll_query->query( $query_args );
1355
1356
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1357
1358
		$results = array();
1359
1360
		if ( have_posts() ) {
1361
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1362
			ob_start();
1363
			wp_head();
1364
			while ( ob_get_length() ) {
1365
				ob_end_clean();
1366
			}
1367
1368
			$results['type'] = 'success';
1369
1370
			/**
1371
			 * Fires when rendering Infinite Scroll posts.
1372
			 *
1373
			 * @module infinite-scroll
1374
			 *
1375
			 * @since 2.0.0
1376
			 */
1377
			do_action( 'infinite_scroll_render' );
1378
			$results['html'] = ob_get_clean();
1379
			if ( empty( $results['html'] ) ) {
1380
				/**
1381
				 * Gather renderer callbacks. These will be called in order and allow multiple callbacks to be queued. Once content is found, no futher callbacks will run.
1382
				 *
1383
				 * @module infinite-scroll
1384
				 *
1385
				 * @since 6.0.0
1386
				 */
1387
				$callbacks = apply_filters(
1388
					'infinite_scroll_render_callbacks',
1389
					array( self::get_settings()->render ) // This is the setting callback e.g. from add theme support.
1390
				);
1391
1392
				// Append fallback callback. That rhymes.
1393
				$callbacks[] = array( $this, 'render' );
1394
1395
				foreach ( $callbacks as $callback ) {
1396
					if ( false !== $callback && is_callable( $callback ) ) {
1397
						rewind_posts();
1398
						ob_start();
1399
						add_action( 'infinite_scroll_render', $callback );
1400
1401
						/**
1402
						 * This action is already documented above.
1403
						 * See https://github.com/Automattic/jetpack/pull/16317/
1404
						 * for more details as to why it was introduced.
1405
						 */
1406
						do_action( 'infinite_scroll_render' );
1407
1408
						$results['html'] = ob_get_clean();
1409
						remove_action( 'infinite_scroll_render', $callback );
1410
					}
1411
					if ( ! empty( $results['html'] ) ) {
1412
						break;
1413
					}
1414
				}
1415
			}
1416
1417
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1418
			if ( empty( $results['html'] ) ) {
1419
				unset( $results['html'] );
1420
				/**
1421
				 * Fires when Infinite Scoll doesn't render any posts.
1422
				 *
1423
				 * @module infinite-scroll
1424
				 *
1425
				 * @since 2.0.0
1426
				 */
1427
				do_action( 'infinite_scroll_empty' );
1428
				$results['type'] = 'empty';
1429
			} elseif ( $this->has_wrapper() ) {
1430
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1431
				$wrapper_classes .= ' infinite-view-' . $page;
1432
				$wrapper_classes = trim( $wrapper_classes );
1433
				$aria_label = sprintf(
1434
					/* translators: %1$s is the page count */
1435
					__( 'Page: %1$d.', 'jetpack' ),
1436
					$page
1437
				);
1438
1439
				$results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '" role="region" aria-label="' . esc_attr( $aria_label ) . '">' . $results['html'] . '</div>';
1440
			}
1441
1442
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1443
			ob_start();
1444
			wp_footer();
1445
			while ( ob_get_length() ) {
1446
				ob_end_clean();
1447
			}
1448
1449
			if ( 'success' == $results['type'] ) {
1450
				global $currentday;
1451
				$results['lastbatch'] = self::is_last_batch();
1452
				$results['currentday'] = $currentday;
1453
			}
1454
1455
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1456
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1457
				global $jetpack_sharing_counts;
1458
1459
				while( have_posts() ) {
1460
					the_post();
1461
1462
					sharing_register_post_for_share_counts( get_the_ID() );
1463
				}
1464
1465
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1466
			}
1467
		} else {
1468
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1469
			do_action( 'infinite_scroll_empty' );
1470
			$results['type'] = 'empty';
1471
		}
1472
1473
		wp_send_json(
1474
			/**
1475
			 * Filter the Infinite Scroll results.
1476
			 *
1477
			 * @module infinite-scroll
1478
			 *
1479
			 * @since 2.0.0
1480
			 *
1481
			 * @param array $results Array of Infinite Scroll results.
1482
			 * @param array $query_args Array of main query arguments.
1483
			 * @param WP_Query $wp_query WP Query.
1484
			 */
1485
			apply_filters( 'infinite_scroll_results', $results, $query_args, self::wp_query() )
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $query_args.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1486
		);
1487
	}
1488
1489
	/**
1490
	 * Update the $allowed_vars array with the standard WP public and private
1491
	 * query vars, as well as taxonomy vars
1492
	 *
1493
	 * @global $wp
1494
	 * @param array $allowed_vars
1495
	 * @filter infinite_scroll_allowed_vars
1496
	 * @return array
1497
	 */
1498
	function allowed_query_vars( $allowed_vars ) {
1499
		global $wp;
1500
1501
		$allowed_vars += $wp->public_query_vars;
1502
		$allowed_vars += $wp->private_query_vars;
1503
		$allowed_vars += $this->get_taxonomy_vars();
1504
1505
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1506
			unset( $allowed_vars[ $key ] );
1507
		}
1508
1509
		return array_unique( $allowed_vars );
1510
	}
1511
1512
	/**
1513
	 * Returns an array of stock and custom taxonomy query vars
1514
	 *
1515
	 * @global $wp_taxonomies
1516
	 * @return array
1517
	 */
1518
	function get_taxonomy_vars() {
1519
		global $wp_taxonomies;
1520
1521
		$taxonomy_vars = array();
1522
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1523
			if ( $t->query_var )
1524
				$taxonomy_vars[] = $t->query_var;
1525
		}
1526
1527
		// still needed?
1528
		$taxonomy_vars[] = 'tag_id';
1529
1530
		return $taxonomy_vars;
1531
	}
1532
1533
	/**
1534
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1535
	 *
1536
	 * @param array $query_args
1537
	 * @filter infinite_scroll_query_args
1538
	 * @return array
1539
	 */
1540
	function inject_query_args( $query_args ) {
1541
		/**
1542
		 * Filter the array of allowed Infinite Scroll query arguments.
1543
		 *
1544
		 * @module infinite-scroll
1545
		 *
1546
		 * @since 2.6.0
1547
		 *
1548
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1549
		 * @param array $query_args Array of query arguments.
1550
		 */
1551
		$allowed_vars = apply_filters( 'infinite_scroll_allowed_vars', array(), $query_args );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $query_args.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1552
1553
		$query_args = array_merge( $query_args, array(
1554
			'suppress_filters' => false,
1555
		) );
1556
1557
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1558
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1559
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1560
					$query_args[ $var ] = $value;
1561
			}
1562
		}
1563
1564
		return $query_args;
1565
	}
1566
1567
	/**
1568
	 * Rendering fallback used when themes don't specify their own handler.
1569
	 *
1570
	 * @uses have_posts, the_post, get_template_part, get_post_format
1571
	 * @action infinite_scroll_render
1572
	 * @return string
1573
	 */
1574
	function render() {
1575
		while ( have_posts() ) {
1576
			the_post();
1577
1578
			get_template_part( 'content', get_post_format() );
1579
		}
1580
	}
1581
1582
	/**
1583
	 * Allow plugins to filter what archives Infinite Scroll supports
1584
	 *
1585
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1586
	 * @return bool
1587
	 */
1588
	public static function archive_supports_infinity() {
1589
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1590
1591
		// Disable when previewing a non-active theme in the customizer
1592
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1593
			return false;
1594
		}
1595
1596
		/**
1597
		 * Allow plugins to filter what archives Infinite Scroll supports.
1598
		 *
1599
		 * @module infinite-scroll
1600
		 *
1601
		 * @since 2.0.0
1602
		 *
1603
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1604
		 * @param object self::get_settings() IS settings provided by theme.
1605
		 */
1606
		return (bool) apply_filters( 'infinite_scroll_archive_supported', $supported, self::get_settings() );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with self::get_settings().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1607
	}
1608
1609
	/**
1610
	 * The Infinite Blog Footer
1611
	 *
1612
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1613
	 * @return string or null
1614
	 */
1615
	function footer() {
1616
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
1617
			return;
1618
		}
1619
1620
		// Bail if theme requested footer not show
1621
		if ( false == self::get_settings()->footer )
1622
			return;
1623
1624
		// We only need the new footer for the 'scroll' type
1625
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1626
			return;
1627
1628
		if ( self::is_last_batch() ) {
1629
			return;
1630
		}
1631
1632
		// Display a footer, either user-specified or a default
1633
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1634
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1635
		else
1636
			self::default_footer();
1637
	}
1638
1639
	/**
1640
	 * Render default IS footer
1641
	 *
1642
	 * @uses __, wp_get_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1643
	 * @return string
1644
	 *
1645
	 */
1646
	private function default_footer() {
1647
		if ( '' !== get_privacy_policy_url() ) {
1648
			$credits = get_the_privacy_policy_link() . '<span role="separator" aria-hidden="true"> / </span>';
1649
		} else {
1650
			$credits = '';
1651
		}
1652
		$credits .= sprintf(
1653
			'<a href="https://wordpress.org/" rel="noopener noreferrer" target="_blank" rel="generator">%1$s</a> ',
1654
			__( 'Proudly powered by WordPress', 'jetpack' )
1655
		);
1656
		$credits .= sprintf(
1657
			/* translators: %1$s is the name of a theme */
1658
			__( 'Theme: %1$s.', 'jetpack' ),
1659
			wp_get_theme()->Name
1660
		);
1661
		/**
1662
		 * Filter Infinite Scroll's credit text.
1663
		 *
1664
		 * @module infinite-scroll
1665
		 *
1666
		 * @since 2.0.0
1667
		 *
1668
		 * @param string $credits Infinite Scroll credits.
1669
		 */
1670
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1671
1672
		?>
1673
		<div id="infinite-footer">
1674
			<div class="container">
1675
				<div class="blog-info">
1676
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" rel="home">
1677
						<?php bloginfo( 'name' ); ?>
1678
					</a>
1679
				</div>
1680
				<div class="blog-credits">
1681
					<?php echo $credits; ?>
1682
				</div>
1683
			</div>
1684
		</div><!-- #infinite-footer -->
1685
		<?php
1686
	}
1687
1688
	/**
1689
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1690
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1691
	 *
1692
	 * @param string $url
1693
	 * @uses remove_query_arg
1694
	 * @filter grunion_contact_form_redirect_url
1695
	 * @return string
1696
	 */
1697
	public function filter_grunion_redirect_url( $url ) {
1698
		// Remove IS query args, if present
1699
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1700
			$url = remove_query_arg( array(
1701
				'infinity',
1702
				'action',
1703
				'page',
1704
				'order',
1705
				'scripts',
1706
				'styles'
1707
			), $url );
1708
		}
1709
1710
		return $url;
1711
	}
1712
1713
	/**
1714
	 * When the MediaElement is loaded in dynamically, we need to enforce that
1715
	 * its settings are added to the page as well.
1716
	 *
1717
	 * @param array $scripts_data New scripts exposed to the infinite scroll.
1718
	 *
1719
	 * @since 8.4.0
1720
	 */
1721
	public function add_mejs_config( $scripts_data ) {
1722
		foreach ( $scripts_data as $key => $data ) {
1723
			if ( 'mediaelement-core' === $data['handle'] ) {
1724
				$mejs_settings = array(
1725
					'pluginPath'  => includes_url( 'js/mediaelement/', 'relative' ),
1726
					'classPrefix' => 'mejs-',
1727
					'stretching'  => 'responsive',
1728
				);
1729
1730
				$scripts_data[ $key ]['extra_data'] = sprintf(
1731
					'window.%s = %s',
1732
					'_wpmejsSettings',
1733
					wp_json_encode( apply_filters( 'mejs_settings', $mejs_settings ) )
1734
				);
1735
			}
1736
		}
1737
		return $scripts_data;
1738
	}
1739
1740
	/**
1741
	 * Determines whether the legacy AMP Reader post templates are being used.
1742
	 *
1743
	 * @return bool
1744
	 */
1745
	private function is_exempted_amp_page() {
1746
		if ( is_singular( 'web-story' ) ) {
1747
			// Ensure that <amp-next-page> is not injected after <amp-story> as generated by the Web Stories plugin.
1748
			return true;
1749
		}
1750
		if ( function_exists( 'amp_is_legacy' ) ) {
1751
			// Available since AMP v2.0, this will return false if a theme like Twenty Twenty is selected as the Reader theme.
1752
			return amp_is_legacy();
1753
		}
1754
		if ( method_exists( 'AMP_Options_Manager', 'get_option' ) ) {
1755
			// In versions prior to v2.0, checking the template mode as being 'reader' is sufficient.
1756
			return 'reader' === AMP_Options_Manager::get_option( 'theme_support' );
1757
		}
1758
		return false;
1759
	}
1760
1761
	/**
1762
	 * Load AMP specific hooks.
1763
	 *
1764
	 * @return void
1765
	 */
1766
	public function amp_load_hooks() {
1767
		if ( $this->is_exempted_amp_page() ) {
1768
			return;
1769
		}
1770
1771
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
1772
			$template = self::get_settings()->render;
1773
1774
			add_filter( 'jetpack_infinite_scroll_load_scripts_and_styles', '__return_false' );
1775
1776
			add_action( 'template_redirect', array( $this, 'amp_start_output_buffering' ), 0 );
1777
			add_action( 'shutdown', array( $this, 'amp_output_buffer' ), 1 );
1778
1779
			if ( is_callable( "amp_{$template}_hooks" ) ) {
1780
				call_user_func( "amp_{$template}_hooks" );
1781
			}
1782
1783
			// Warms up the amp next page markup.
1784
			// This should be done outside the output buffering callback started in the template_redirect.
1785
			$this->amp_get_footer_template();
1786
		}
1787
	}
1788
1789
	/**
1790
	 * Start the AMP output buffering.
1791
	 *
1792
	 * @return void
1793
	 */
1794
	public function amp_start_output_buffering() {
1795
		ob_start( array( $this, 'amp_finish_output_buffering' ) );
1796
	}
1797
1798
	/**
1799
	 * Flush the AMP output buffer.
1800
	 *
1801
	 * @return void
1802
	 */
1803
	public function amp_output_buffer() {
1804
		if ( ob_get_contents() ) {
1805
			ob_end_flush();
1806
		}
1807
	}
1808
1809
	/**
1810
	 * Filter the AMP output buffer contents.
1811
	 *
1812
	 * @param string $buffer Contents of the output buffer.
1813
	 *
1814
	 * @return string|false
1815
	 */
1816
	public function amp_finish_output_buffering( $buffer ) {
1817
		// Hide WordPress admin bar on next page load.
1818
		$buffer = preg_replace(
1819
			'/id="wpadminbar"/',
1820
			'$0 next-page-hide',
1821
			$buffer
1822
		);
1823
1824
		/**
1825
		 * Get the theme footers.
1826
		 *
1827
		 * @module infinite-scroll
1828
		 *
1829
		 * @since 9.0.0
1830
		 *
1831
		 * @param array  array() An array to store multiple markup entries to be added to the footer.
1832
		 * @param string $buffer The contents of the output buffer.
1833
		 */
1834
		$footers = apply_filters( 'jetpack_amp_infinite_footers', array(), $buffer );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $buffer.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1835
1836
		/**
1837
		 * Filter the output buffer.
1838
		 * Themes can leverage this hook to add custom markup on next page load.
1839
		 *
1840
		 * @module infinite-scroll
1841
		 *
1842
		 * @since 9.0.0
1843
		 *
1844
		 * @param string $buffer The contents of the output buffer.
1845
		 */
1846
		$buffer = apply_filters( 'jetpack_amp_infinite_output', $buffer );
1847
1848
		// Add the amp next page markup.
1849
		$buffer = preg_replace(
1850
			'~</body>~',
1851
			$this->amp_get_footer_template( $footers ) . '$0',
1852
			$buffer
1853
		);
1854
1855
		return $buffer;
1856
	}
1857
1858
	/**
1859
	 * Get AMP next page markup with the custom footers.
1860
	 *
1861
	 * @param string[] $footers The theme footers.
1862
	 *
1863
	 * @return string
1864
	 */
1865
	protected function amp_get_footer_template( $footers = array() ) {
1866
		static $template = null;
1867
1868
		if ( null === $template ) {
1869
			$template = $this->amp_footer_template();
1870
		}
1871
1872
		if ( empty( $footers ) ) {
1873
			return $template;
1874
		}
1875
1876
		return preg_replace(
1877
			'/%%footer%%/',
1878
			implode( '', $footers ),
1879
			$template
1880
		);
1881
	}
1882
1883
	/**
1884
	 * AMP Next Page markup.
1885
	 *
1886
	 * @return string
1887
	 */
1888
	protected function amp_footer_template() {
1889
		ob_start();
1890
		?>
1891
<amp-next-page max-pages="<?php echo esc_attr( $this->amp_get_max_pages() ); ?>">
1892
	<script type="application/json">
1893
		[
1894
			<?php echo wp_json_encode( $this->amp_next_page() ); ?>
1895
		]
1896
	</script>
1897
	<div separator>
1898
		<?php
1899
		echo wp_kses_post(
1900
			/**
1901
			 * AMP infinite scroll separator.
1902
			 *
1903
			 * @module infinite-scroll
1904
			 *
1905
			 * @since 9.0.0
1906
			 *
1907
			 * @param string '' The markup for the next page separator.
1908
			 */
1909
			apply_filters( 'jetpack_amp_infinite_separator', '' )
1910
		);
1911
		?>
1912
	</div>
1913
	<div recommendation-box class="recommendation-box">
1914
		<template type="amp-mustache">
1915
			{{#pages}}
1916
			<?php
1917
			echo wp_kses_post(
1918
				/**
1919
				 * AMP infinite scroll older posts markup.
1920
				 *
1921
				 * @module infinite-scroll
1922
				 *
1923
				 * @since 9.0.0
1924
				 *
1925
				 * @param string '' The markup for the older posts/next page.
1926
				 */
1927
				apply_filters( 'jetpack_amp_infinite_older_posts', '' )
1928
			);
1929
			?>
1930
			{{/pages}}
1931
		</template>
1932
	</div>
1933
	<div footer>
1934
		%%footer%%
1935
	</div>
1936
</amp-next-page>
1937
		<?php
1938
		return ob_get_clean();
1939
	}
1940
1941
	/**
1942
	 * Get the AMP next page information.
1943
	 *
1944
	 * @return array
1945
	 */
1946
	protected function amp_next_page() {
1947
		$title = '';
1948
		$url   = '';
1949
		$image = '';
1950
1951
		if ( ! static::amp_is_last_page() ) {
1952
			$title = sprintf(
1953
				'%s - %s %d - %s',
1954
				wp_title( '', false ),
1955
				__( 'Page', 'jetpack' ),
1956
				max( get_query_var( 'paged', 1 ), 1 ) + 1,
1957
				get_bloginfo( 'name' )
1958
			);
1959
			$url   = get_next_posts_page_link();
1960
		}
1961
1962
		$next_page = array(
1963
			'title' => $title,
1964
			'url'   => $url,
1965
			'image' => $image,
1966
		);
1967
1968
		/**
1969
		 * The next page settings.
1970
		 * An array containing:
1971
		 *  - title => The title to be featured on the browser tab.
1972
		 *  - url   => The URL of next page.
1973
		 *  - image => The image URL. A required AMP setting, not in use currently. Themes are welcome to leverage.
1974
		 *
1975
		 * @module infinite-scroll
1976
		 *
1977
		 * @since 9.0.0
1978
		 *
1979
		 * @param array $next_page The contents of the output buffer.
1980
		 */
1981
		return apply_filters( 'jetpack_amp_infinite_next_page_data', $next_page );
1982
	}
1983
1984
	/**
1985
	 * Get the number of pages left.
1986
	 *
1987
	 * @return int
1988
	 */
1989
	protected static function amp_get_max_pages() {
1990
		global $wp_query;
1991
1992
		return (int) $wp_query->max_num_pages - $wp_query->query_vars['paged'];
1993
	}
1994
1995
	/**
1996
	 * Is the last page.
1997
	 *
1998
	 * @return bool
1999
	 */
2000
	protected static function amp_is_last_page() {
2001
		return 0 === static::amp_get_max_pages();
2002
	}
2003
};
2004
2005
/**
2006
 * Initialize The_Neverending_Home_Page
2007
 */
2008
function the_neverending_home_page_init() {
2009
	if ( ! current_theme_supports( 'infinite-scroll' ) )
2010
		return;
2011
2012
	new The_Neverending_Home_Page();
2013
}
2014
add_action( 'init', 'the_neverending_home_page_init', 20 );
2015
2016
/**
2017
 * Check whether the current theme is infinite-scroll aware.
2018
 * If so, include the files which add theme support.
2019
 */
2020
function the_neverending_home_page_theme_support() {
2021
	if (
2022
			defined( 'IS_WPCOM' ) && IS_WPCOM &&
2023
			defined( 'REST_API_REQUEST' ) && REST_API_REQUEST &&
2024
			! doing_action( 'restapi_theme_after_setup_theme' )
2025
	) {
2026
		// Don't source theme compat files until we're in the site's context
2027
		return;
2028
	}
2029
	$theme_name = get_stylesheet();
2030
2031
	/**
2032
	 * Filter the path to the Infinite Scroll compatibility file.
2033
	 *
2034
	 * @module infinite-scroll
2035
	 *
2036
	 * @since 2.0.0
2037
	 *
2038
	 * @param string $str IS compatibility file path.
2039
	 * @param string $theme_name Theme name.
2040
	 */
2041
	$customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name );
0 ignored issues
show
Unused Code introduced by
The call to apply_filters() has too many arguments starting with $theme_name.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2042
2043
	if ( is_readable( $customization_file ) )
2044
		require_once( $customization_file );
2045
}
2046
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
2047
2048
/**
2049
 * Early accommodation of the Infinite Scroll AJAX request
2050
 */
2051
if ( The_Neverending_Home_Page::got_infinity() ) {
2052
	/**
2053
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
2054
	 * indicate it as early as possible for actions like init
2055
	 */
2056
	if ( ! defined( 'DOING_AJAX' ) &&
2057
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
2058
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
2059
	) {
2060
		define( 'DOING_AJAX', true );
2061
	}
2062
2063
	// Don't load the admin bar when doing the AJAX response.
2064
	show_admin_bar( false );
2065
}
2066