Completed
Push — add/sync-partial-sync-checksum... ( 93f4e2...c9ed3f )
by
unknown
253:26 queued 243:12
created

The_Neverending_Home_Page::get_taxonomy_vars()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 14
rs 9.7998
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
	/**
24
	 * Register actions and filters, plus parse IS settings
25
	 *
26
	 * @uses add_action, add_filter, self::get_settings
27
	 * @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...
28
	 */
29
	function __construct() {
30
		add_action( 'pre_get_posts', array( $this, 'posts_per_page_query' ) );
31
		add_action( 'admin_init', array( $this, 'settings_api_init' ) );
32
		add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
33
		add_action( 'customize_preview_init', array( $this, 'init_customizer_assets' ) );
34
		add_action( 'template_redirect', array( $this, 'ajax_response' ) );
35
		add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) );
36
		add_filter( 'infinite_scroll_query_args', array( $this, 'inject_query_args' ) );
37
		add_filter( 'infinite_scroll_allowed_vars', array( $this, 'allowed_query_vars' ) );
38
		add_action( 'the_post', array( $this, 'preserve_more_tag' ) );
39
		add_action( 'wp_footer', array( $this, 'footer' ) );
40
		add_filter( 'infinite_scroll_additional_scripts', array( $this, 'add_mejs_config' ) );
41
42
		// Plugin compatibility
43
		add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
44
45
		// AMP compatibility
46
		// needs to happen after parse_query so that Jetpack_AMP_Support::is_amp_request() is ready.
47
		add_action( 'wp', array( $this, 'amp_load_hooks' ) );
48
49
		// Parse IS settings from theme
50
		self::get_settings();
51
	}
52
53
	/**
54
	 * Initialize our static variables
55
	 */
56
	static $the_time = null;
57
	static $settings = null; // Don't access directly, instead use self::get_settings().
58
59
	static $option_name_enabled = 'infinite_scroll';
60
61
	/**
62
	 * Parse IS settings provided by theme
63
	 *
64
	 * @uses get_theme_support, infinite_scroll_has_footer_widgets, sanitize_title, add_action, get_option, wp_parse_args, is_active_sidebar
65
	 * @return object
66
	 */
67
	static function get_settings() {
68
		if ( is_null( self::$settings ) ) {
69
			$css_pattern = '#[^A-Z\d\-_]#i';
70
71
			$settings = $defaults = array(
72
				'type'            => 'scroll', // scroll | click
73
				'requested_type'  => 'scroll', // store the original type for use when logic overrides it
74
				'footer_widgets'  => false, // true | false | sidebar_id | array of sidebar_ids -- last two are checked with is_active_sidebar
75
				'container'       => 'content', // container html id
76
				'wrapper'         => true, // true | false | html class
77
				'render'          => false, // optional function, otherwise the `content` template part will be used
78
				'footer'          => true, // boolean to enable or disable the infinite footer | string to provide an html id to derive footer width from
79
				'footer_callback' => false, // function to be called to render the IS footer, in place of the default
80
				'posts_per_page'  => false, // int | false to set based on IS type
81
				'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`.
82
			);
83
84
			// Validate settings passed through add_theme_support()
85
			$_settings = get_theme_support( 'infinite-scroll' );
86
87
			if ( is_array( $_settings ) ) {
88
				// Preferred implementation, where theme provides an array of options
89
				if ( isset( $_settings[0] ) && is_array( $_settings[0] ) ) {
90
					foreach ( $_settings[0] as $key => $value ) {
91
						switch ( $key ) {
92
							case 'type' :
93
								if ( in_array( $value, array( 'scroll', 'click' ) ) )
94
									$settings[ $key ] = $settings['requested_type'] = $value;
95
96
								break;
97
98
							case 'footer_widgets' :
99
								if ( is_string( $value ) )
100
									$settings[ $key ] = sanitize_title( $value );
101
								elseif ( is_array( $value ) )
102
									$settings[ $key ] = array_map( 'sanitize_title', $value );
103
								elseif ( is_bool( $value ) )
104
									$settings[ $key ] = $value;
105
106
								break;
107
108
							case 'container' :
109 View Code Duplication
							case 'wrapper' :
110
								if ( 'wrapper' == $key && is_bool( $value ) ) {
111
									$settings[ $key ] = $value;
112
								} else {
113
									$value = preg_replace( $css_pattern, '', $value );
114
115
									if ( ! empty( $value ) )
116
										$settings[ $key ] = $value;
117
								}
118
119
								break;
120
121
							case 'render' :
122
								if ( false !== $value && is_callable( $value ) ) {
123
									$settings[ $key ] = $value;
124
								}
125
126
								break;
127
128 View Code Duplication
							case 'footer' :
129
								if ( is_bool( $value ) ) {
130
									$settings[ $key ] = $value;
131
								} elseif ( is_string( $value ) ) {
132
									$value = preg_replace( $css_pattern, '', $value );
133
134
									if ( ! empty( $value ) )
135
										$settings[ $key ] = $value;
136
								}
137
138
								break;
139
140
							case 'footer_callback' :
141
								if ( is_callable( $value ) )
142
									$settings[ $key ] = $value;
143
								else
144
									$settings[ $key ] = false;
145
146
								break;
147
148
							case 'posts_per_page' :
149
								if ( is_numeric( $value ) )
150
									$settings[ $key ] = (int) $value;
151
152
								break;
153
154
							case 'click_handle' :
155
								if ( is_bool( $value ) ) {
156
									$settings[ $key ] = $value;
157
								}
158
159
								break;
160
161
							default:
162
								break;
163
						}
164
					}
165
				} elseif ( is_string( $_settings[0] ) ) {
166
					// Checks below are for backwards compatibility
167
168
					// Container to append new posts to
169
					$settings['container'] = preg_replace( $css_pattern, '', $_settings[0] );
170
171
					// Wrap IS elements?
172
					if ( isset( $_settings[1] ) )
173
						$settings['wrapper'] = (bool) $_settings[1];
174
				}
175
			}
176
177
			// Always ensure all values are present in the final array
178
			$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...
179
180
			// Check if a legacy `infinite_scroll_has_footer_widgets()` function is defined and override the footer_widgets parameter's value.
181
			// Otherwise, if a widget area ID or array of IDs was provided in the footer_widgets parameter, check if any contains any widgets.
182
			// 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.
183
			if ( function_exists( 'infinite_scroll_has_footer_widgets' ) ) {
184
				$settings['footer_widgets'] = (bool) infinite_scroll_has_footer_widgets();
185
			} elseif ( is_array( $settings['footer_widgets'] ) ) {
186
				$sidebar_ids = $settings['footer_widgets'];
187
				$settings['footer_widgets'] = false;
188
189
				foreach ( $sidebar_ids as $sidebar_id ) {
190
					if ( is_active_sidebar( $sidebar_id ) ) {
191
						$settings['footer_widgets'] = true;
192
						break;
193
					}
194
				}
195
196
				unset( $sidebar_ids );
197
				unset( $sidebar_id );
198
			} elseif ( is_string( $settings['footer_widgets'] ) ) {
199
				$settings['footer_widgets'] = (bool) is_active_sidebar( $settings['footer_widgets'] );
200
			}
201
202
			/**
203
			 * Filter Infinite Scroll's `footer_widgets` parameter.
204
			 *
205
			 * @module infinite-scroll
206
			 *
207
			 * @since 2.0.0
208
			 *
209
			 * @param bool $settings['footer_widgets'] Does the current theme have Footer Widgets.
210
			 */
211
			$settings['footer_widgets'] = apply_filters( 'infinite_scroll_has_footer_widgets', $settings['footer_widgets'] );
212
213
			// Finally, after all of the sidebar checks and filtering, ensure that a boolean value is present, otherwise set to default of `false`.
214
			if ( ! is_bool( $settings['footer_widgets'] ) )
215
				$settings['footer_widgets'] = false;
216
217
			// Ensure that IS is enabled and no footer widgets exist if the IS type isn't already "click".
218
			if ( 'click' != $settings['type'] ) {
219
				// Check the setting status
220
				$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
221
222
				// Footer content or Reading option check
223
				if ( $settings['footer_widgets'] || $disabled )
224
					$settings['type'] = 'click';
225
			}
226
227
			// Force display of the click handler and attendant bits when the type isn't `click`
228
			if ( 'click' !== $settings['type'] ) {
229
				$settings['click_handle'] = true;
230
			}
231
232
			// Store final settings in a class static to avoid reparsing
233
			/**
234
			 * Filter the array of Infinite Scroll settings.
235
			 *
236
			 * @module infinite-scroll
237
			 *
238
			 * @since 2.0.0
239
			 *
240
			 * @param array $settings Array of Infinite Scroll settings.
241
			 */
242
			self::$settings = apply_filters( 'infinite_scroll_settings', $settings );
243
		}
244
245
		/** This filter is already documented in modules/infinite-scroll/infinity.php */
246
		return (object) apply_filters( 'infinite_scroll_settings', self::$settings );
247
	}
248
249
	/**
250
	 * Number of posts per page.
251
	 *
252
	 * @uses self::wp_query, self::get_settings, apply_filters
253
	 * @return int
254
	 */
255
	static function posts_per_page() {
256
		$posts_per_page             = self::get_settings()->posts_per_page ? self::get_settings()->posts_per_page : self::wp_query()->get( 'posts_per_page' );
257
		$posts_per_page_core_option = get_option( 'posts_per_page' );
258
259
		// If Infinite Scroll is set to click, and if the site owner changed posts_per_page, let's use that.
260
		if (
261
			'click' === self::get_settings()->type
262
				&& ( '10' !== $posts_per_page_core_option )
263
		) {
264
			$posts_per_page = $posts_per_page_core_option;
265
		}
266
267
		// Take JS query into consideration here.
268
		if ( true === isset( $_REQUEST['query_args']['posts_per_page'] ) ) {
269
			$posts_per_page = $_REQUEST['query_args']['posts_per_page'];
270
		}
271
272
		/**
273
		 * Filter the number of posts per page.
274
		 *
275
		 * @module infinite-scroll
276
		 *
277
		 * @since 6.0.0
278
		 *
279
		 * @param int $posts_per_page The number of posts to display per page.
280
		 */
281
		return (int) apply_filters( 'infinite_scroll_posts_per_page', $posts_per_page );
282
	}
283
284
	/**
285
	 * Retrieve the query used with Infinite Scroll
286
	 *
287
	 * @global $wp_the_query
288
	 * @uses apply_filters
289
	 * @return object
290
	 */
291
	static function wp_query() {
292
		global $wp_the_query;
293
		/**
294
		 * Filter the Infinite Scroll query object.
295
		 *
296
		 * @module infinite-scroll
297
		 *
298
		 * @since 2.2.1
299
		 *
300
		 * @param WP_Query $wp_the_query WP Query.
301
		 */
302
		return apply_filters( 'infinite_scroll_query_object', $wp_the_query );
303
	}
304
305
	/**
306
	 * Has infinite scroll been triggered?
307
	 */
308
	static function got_infinity() {
309
		/**
310
		 * Filter the parameter used to check if Infinite Scroll has been triggered.
311
		 *
312
		 * @module infinite-scroll
313
		 *
314
		 * @since 3.9.0
315
		 *
316
		 * @param bool isset( $_GET[ 'infinity' ] ) Return true if the "infinity" parameter is set.
317
		 */
318
		return apply_filters( 'infinite_scroll_got_infinity', isset( $_GET[ 'infinity' ] ) );
319
	}
320
321
	/**
322
	 * Is this guaranteed to be the last batch of posts?
323
	 */
324
	static function is_last_batch() {
325
		/**
326
		 * Override whether or not this is the last batch for a request
327
		 *
328
		 * @module infinite-scroll
329
		 *
330
		 * @since 4.8.0
331
		 *
332
		 * @param bool|null null                 Bool if value should be overridden, null to determine from query
333
		 * @param object    self::wp_query()     WP_Query object for current request
334
		 * @param object    self::get_settings() Infinite Scroll settings
335
		 */
336
		$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...
337
		if ( is_bool( $override ) ) {
338
			return $override;
339
		}
340
341
		$entries = (int) self::wp_query()->found_posts;
342
		$posts_per_page = self::posts_per_page();
343
344
		// This is to cope with an issue in certain themes or setups where posts are returned but found_posts is 0.
345
		if ( 0 == $entries ) {
346
			return (bool) ( count( self::wp_query()->posts ) < $posts_per_page );
347
		}
348
		$paged = max( 1, self::wp_query()->get( 'paged' ) );
349
350
		// Are there enough posts for more than the first page?
351
		if ( $entries <= $posts_per_page ) {
352
			return true;
353
		}
354
355
		// Calculate entries left after a certain number of pages
356
		if ( $paged && $paged > 1 ) {
357
			$entries -= $posts_per_page * $paged;
358
		}
359
360
		// Are there some entries left to display?
361
		return $entries <= 0;
362
	}
363
364
	/**
365
	 * The more tag will be ignored by default if the blog page isn't our homepage.
366
	 * Let's force the $more global to false.
367
	 */
368
	function preserve_more_tag( $array ) {
369
		global $more;
370
371
		if ( self::got_infinity() )
372
			$more = 0; //0 = show content up to the more tag. Add more link.
373
374
		return $array;
375
	}
376
377
	/**
378
	 * Add a checkbox field to Settings > Reading
379
	 * for enabling infinite scroll.
380
	 *
381
	 * Only show if the current theme supports infinity.
382
	 *
383
	 * @uses current_theme_supports, add_settings_field, __, register_setting
384
	 * @action admin_init
385
	 * @return null
386
	 */
387
	function settings_api_init() {
388
		if ( ! current_theme_supports( 'infinite-scroll' ) )
389
			return;
390
391
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
392
			// This setting is no longer configurable in wp-admin on WordPress.com -- leave a pointer
393
			add_settings_field( self::$option_name_enabled,
394
				'<span id="infinite-scroll-options">' . esc_html__( 'Infinite Scroll Behavior', 'jetpack' ) . '</span>',
395
				array( $this, 'infinite_setting_html_calypso_placeholder' ),
396
				'reading'
397
			);
398
			return;
399
		}
400
401
		// Add the setting field [infinite_scroll] and place it in Settings > Reading
402
		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' );
403
		register_setting( 'reading', self::$option_name_enabled, 'esc_attr' );
404
	}
405
406
	function infinite_setting_html_calypso_placeholder() {
407
		$details     = get_blog_details();
408
		$writing_url = Redirect::get_url( 'calypso-settings-writing', array( 'site' => $details->domain ) );
409
		echo '<span>' . sprintf(
410
			/* translators: Variables are the enclosing link to the settings page */
411
			esc_html__( 'This option has moved. You can now manage it %1$shere%2$s.', 'jetpack' ),
412
			'<a href="' . esc_url( $writing_url ) . '">',
413
			'</a>'
414
		) . '</span>';
415
	}
416
417
	/**
418
	 * HTML code to display a checkbox true/false option
419
	 * for the infinite_scroll setting.
420
	 */
421
	function infinite_setting_html() {
422
		$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>';
423
424
		// If the blog has footer widgets, show a notice instead of the checkbox
425
		if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) {
426
			echo '<label>' . $notice . '</label>';
427
		} else {
428
			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>';
429
			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>';
430
		}
431
	}
432
433
	/**
434
	 * Does the legwork to determine whether the feature is enabled.
435
	 *
436
	 * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action
437
	 * @action template_redirect
438
	 * @return null
439
	 */
440
	function action_template_redirect() {
441
		// Check that we support infinite scroll, and are on the home page.
442
		if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
443
			return;
444
445
		$id = self::get_settings()->container;
446
447
		// Check that we have an id.
448
		if ( empty( $id ) )
449
			return;
450
451
		// AMP infinite scroll functionality will start on amp_load_hooks().
452
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
453
			return;
454
		}
455
456
		// Add our scripts.
457
		wp_register_script(
458
			'the-neverending-homepage',
459
			Assets::get_file_url_for_environment(
460
				'_inc/build/infinite-scroll/infinity.min.js',
461
				'modules/infinite-scroll/infinity.js'
462
			),
463
			array(),
464
			JETPACK__VERSION . '-is5.0.0', // Added for ability to cachebust on WP.com.
465
			true
466
		);
467
468
		// Add our default styles.
469
		wp_register_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20140422' );
470
471
		// Make sure there are enough posts for IS
472
		if ( self::is_last_batch() ) {
473
			return;
474
		}
475
476
		// Add our scripts.
477
		wp_enqueue_script( 'the-neverending-homepage' );
478
479
		// Add our default styles.
480
		wp_enqueue_style( 'the-neverending-homepage' );
481
482
		add_action( 'wp_footer', array( $this, 'action_wp_footer_settings' ), 2 );
483
484
		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
485
486
		add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ), 10, 3 );
487
	}
488
489
	/**
490
	 * Initialize the Customizer logic separately from the main JS.
491
	 *
492
	 * @since 8.4.0
493
	 */
494
	public function init_customizer_assets() {
495
		// Add our scripts.
496
		wp_register_script(
497
			'the-neverending-homepage-customizer',
498
			Assets::get_file_url_for_environment(
499
				'_inc/build/infinite-scroll/infinity-customizer.min.js',
500
				'modules/infinite-scroll/infinity-customizer.js'
501
			),
502
			array( 'customize-base' ),
503
			JETPACK__VERSION . '-is5.0.0', // Added for ability to cachebust on WP.com.
504
			true
505
		);
506
507
		wp_enqueue_script( 'the-neverending-homepage-customizer' );
508
	}
509
510
	/**
511
	 * Returns classes to be added to <body>. If it's enabled, 'infinite-scroll'. If set to continuous scroll, adds 'neverending' too.
512
	 *
513
	 * @since 4.7.0 No longer added as a 'body_class' filter but passed to JS environment and added using JS.
514
	 *
515
	 * @return string
516
	 */
517
	function body_class() {
518
		$classes = '';
519
		// Do not add infinity-scroll class if disabled through the Reading page
520
		$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
521
		if ( ! $disabled || 'click' == self::get_settings()->type ) {
522
			$classes = 'infinite-scroll';
523
524
			if ( 'scroll' == self::get_settings()->type )
525
				$classes .= ' neverending';
526
		}
527
528
		return $classes;
529
	}
530
531
	/**
532
	 * 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
533
	 *
534
	 * @uses self::wp_query
535
	 * @uses self::get_last_post_date
536
	 * @uses self::has_only_title_matching_posts
537
	 * @return array
538
	 */
539
	function get_excluded_posts() {
540
541
		$excluded_posts = array();
542
		//loop through posts returned by wp_query call
543
		foreach( self::wp_query()->get_posts() as $post ) {
544
545
			$orderby = isset( self::wp_query()->query_vars['orderby'] ) ? self::wp_query()->query_vars['orderby'] : '';
546
			$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
547
			if ( 'modified' === $orderby || false === $post_date ) {
548
				$post_date = $post->post_modified;
549
			}
550
551
			//in case all posts initially displayed match the keyword by title we add em all to excluded posts array
552
			//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
553
			if ( self::has_only_title_matching_posts() || $post_date <= self::get_last_post_date() ) {
554
				array_push( $excluded_posts, $post->ID );
555
			}
556
		}
557
		return $excluded_posts;
558
	}
559
560
	/**
561
	 * 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
562
	 *
563
	 * @uses self::wp_query
564
	 * @uses self::get_excluded_posts
565
	 * @return array
566
	 */
567
	function get_query_vars() {
568
569
		$query_vars = self::wp_query()->query_vars;
570
		//applies to search page only
571
		if ( true === self::wp_query()->is_search() ) {
572
			//set post__not_in array in query_vars in case it does not exists
573
			if ( false === isset( $query_vars['post__not_in'] ) ) {
574
				$query_vars['post__not_in'] = array();
575
			}
576
			//get excluded posts
577
			$excluded = self::get_excluded_posts();
578
			//merge them with other post__not_in posts (eg.: sticky posts)
579
			$query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $excluded );
580
		}
581
		return $query_vars;
582
	}
583
584
	/**
585
	 * This function checks whether all posts returned by initial wp_query match the keyword by title
586
	 * The code used in this function is borrowed from WP_Query class where it is used to construct like conditions for keywords
587
	 *
588
	 * @uses self::wp_query
589
	 * @return bool
590
	 */
591
	function has_only_title_matching_posts() {
592
593
		//apply following logic for search page results only
594
		if ( false === self::wp_query()->is_search() ) {
595
			return false;
596
		}
597
598
		//grab the last posts in the stack as if the last one is title-matching the rest is title-matching as well
599
		$post = end( self::wp_query()->posts );
600
601
		//code inspired by WP_Query class
602
		if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', self::wp_query()->get( 's' ), $matches ) ) {
603
			$search_terms = self::wp_query()->query_vars['search_terms'];
604
			// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
605
			if ( empty( $search_terms ) || count( $search_terms ) > 9 ) {
606
				$search_terms = array( self::wp_query()->get( 's' ) );
607
			}
608
		} else {
609
			$search_terms = array( self::wp_query()->get( 's' ) );
610
		}
611
612
		//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
613
		$term = current( $search_terms );
614
		if ( ! empty( $term ) && false !== strpos( $post->post_title, $term ) ) {
615
			return true;
616
		}
617
618
		return false;
619
	}
620
621
	/**
622
	 * Grab the timestamp for the initial query's last post.
623
	 *
624
	 * This takes into account the query's 'orderby' parameter and returns
625
	 * false if the posts are not ordered by date.
626
	 *
627
	 * @uses self::got_infinity
628
	 * @uses self::has_only_title_matching_posts
629
	 * @uses self::wp_query
630
	 * @return string 'Y-m-d H:i:s' or false
631
	 */
632
	function get_last_post_date() {
633
		if ( self::got_infinity() )
634
			return;
635
636
		if ( ! self::wp_query()->have_posts() ) {
637
			return null;
638
		}
639
640
		//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
641
		if ( true === self::has_only_title_matching_posts() ) {
642
			return false;
643
		}
644
645
		$post = end( self::wp_query()->posts );
646
		$orderby = isset( self::wp_query()->query_vars['orderby'] ) ?
647
			self::wp_query()->query_vars['orderby'] : '';
648
		$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
649 View Code Duplication
		switch ( $orderby ) {
650
			case 'modified':
651
				return $post->post_modified;
652
			case 'date':
653
			case '':
654
				return $post_date;
655
			default:
656
				return false;
657
		}
658
	}
659
660
	/**
661
	 * Returns the appropriate `wp_posts` table field for a given query's
662
	 * 'orderby' parameter, if applicable.
663
	 *
664
	 * @param optional object $query
665
	 * @uses self::wp_query
666
	 * @return string or false
667
	 */
668
	function get_query_sort_field( $query = null ) {
669
		if ( empty( $query ) )
670
			$query = self::wp_query();
671
672
		$orderby = isset( $query->query_vars['orderby'] ) ? $query->query_vars['orderby'] : '';
673
674 View Code Duplication
		switch ( $orderby ) {
675
			case 'modified':
676
				return 'post_modified';
677
			case 'date':
678
			case '':
679
				return 'post_date';
680
			default:
681
				return false;
682
		}
683
	}
684
685
	/**
686
	 * Create a where clause that will make sure post queries return posts
687
	 * in the correct order, without duplicates, if a new post is added
688
	 * and we're sorting by post date.
689
	 *
690
	 * @global $wpdb
691
	 * @param string $where
692
	 * @param object $query
693
	 * @uses apply_filters
694
	 * @filter posts_where
695
	 * @return string
696
	 */
697
	function query_time_filter( $where, $query ) {
698
		if ( self::got_infinity() ) {
699
			global $wpdb;
700
701
			$sort_field = self::get_query_sort_field( $query );
702
703
			if ( 'post_date' !== $sort_field || 'DESC' !== $_REQUEST['query_args']['order'] ) {
704
				return $where;
705
			}
706
707
			$query_before = sanitize_text_field( wp_unslash( $_REQUEST['query_before'] ) );
708
709
			if ( empty( $query_before ) ) {
710
				return $where;
711
			}
712
713
			// Construct the date query using our timestamp
714
			$clause = $wpdb->prepare( " AND {$wpdb->posts}.post_date <= %s", $query_before );
715
716
			/**
717
			 * Filter Infinite Scroll's SQL date query making sure post queries
718
			 * will always return results prior to (descending sort)
719
			 * or before (ascending sort) the last post date.
720
			 *
721
			 * @module infinite-scroll
722
			 *
723
			 * @param string $clause SQL Date query.
724
			 * @param object $query Query.
725
			 * @param string $operator @deprecated Query operator.
726
			 * @param string $last_post_date @deprecated Last Post Date timestamp.
727
			 */
728
			$operator       = 'ASC' === $_REQUEST['query_args']['order'] ? '>' : '<';
729
			$last_post_date = sanitize_text_field( wp_unslash( $_REQUEST['last_post_date'] ) );
730
			$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...
731
		}
732
733
		return $where;
734
	}
735
736
	/**
737
	 * Let's overwrite the default post_per_page setting to always display a fixed amount.
738
	 *
739
	 * @param object $query
740
	 * @uses is_admin, self::archive_supports_infinity, self::get_settings
741
	 * @return null
742
	 */
743
	function posts_per_page_query( $query ) {
744
		if ( ! is_admin() && self::archive_supports_infinity() && $query->is_main_query() )
745
			$query->set( 'posts_per_page', self::posts_per_page() );
746
	}
747
748
	/**
749
	 * Check if the IS output should be wrapped in a div.
750
	 * Setting value can be a boolean or a string specifying the class applied to the div.
751
	 *
752
	 * @uses self::get_settings
753
	 * @return bool
754
	 */
755
	function has_wrapper() {
756
		return (bool) self::get_settings()->wrapper;
757
	}
758
759
	/**
760
	 * Returns the Ajax url
761
	 *
762
	 * @global $wp
763
	 * @uses home_url, add_query_arg, apply_filters
764
	 * @return string
765
	 */
766
	function ajax_url() {
767
		$base_url = set_url_scheme( home_url( '/' ) );
768
769
		$ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url );
770
771
		/**
772
		 * Filter the Infinite Scroll Ajax URL.
773
		 *
774
		 * @module infinite-scroll
775
		 *
776
		 * @since 2.0.0
777
		 *
778
		 * @param string $ajaxurl Infinite Scroll Ajax URL.
779
		 */
780
		return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl );
781
	}
782
783
	/**
784
	 * Our own Ajax response, avoiding calling admin-ajax
785
	 */
786
	function ajax_response() {
787
		// Only proceed if the url query has a key of "Infinity"
788
		if ( ! self::got_infinity() )
789
			return false;
790
791
		// This should already be defined below, but make sure.
792
		if ( ! defined( 'DOING_AJAX' ) ) {
793
			define( 'DOING_AJAX', true );
794
		}
795
796
		@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...
797
		send_nosniff_header();
798
799
		/**
800
		 * Fires at the end of the Infinite Scroll Ajax response.
801
		 *
802
		 * @module infinite-scroll
803
		 *
804
		 * @since 2.0.0
805
		 */
806
		do_action( 'custom_ajax_infinite_scroll' );
807
		die( '0' );
808
	}
809
810
	/**
811
	 * Alias for renamed class method.
812
	 *
813
	 * Previously, JS settings object was unnecessarily output in the document head.
814
	 * When the hook was changed, the method name no longer made sense.
815
	 */
816
	function action_wp_head() {
817
		$this->action_wp_footer_settings();
818
	}
819
820
	/**
821
	 * Prints the relevant infinite scroll settings in JS.
822
	 *
823
	 * @global $wp_rewrite
824
	 * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action, self::get_query_vars
825
	 * @action wp_footer
826
	 * @return string
827
	 */
828
	function action_wp_footer_settings() {
829
		global $wp_rewrite;
830
		global $currentday;
831
832
		// Default click handle text
833
		$click_handle_text = __( 'Older posts', 'jetpack' );
834
835
		// If a single CPT is displayed, use its plural name instead of "posts"
836
		// Could be empty (posts) or an array of multiple post types.
837
		// In the latter two cases cases, the default text is used, leaving the `infinite_scroll_js_settings` filter for further customization.
838
		$post_type = self::wp_query()->get( 'post_type' );
839
840
		// If it's a taxonomy, try to change the button text.
841
		if ( is_tax() ) {
842
			// Get current taxonomy slug.
843
			$taxonomy_slug = self::wp_query()->get( 'taxonomy' );
844
845
			// Get taxonomy settings.
846
			$taxonomy = get_taxonomy( $taxonomy_slug );
847
848
			// Check if the taxonomy is attached to one post type only and use its plural name.
849
			// If not, use "Posts" without confusing the users.
850
			if (
851
				is_a( $taxonomy, 'WP_Taxonomy' )
852
				&& is_countable( $taxonomy->object_type )
853
				&& count( $taxonomy->object_type ) < 2
854
			) {
855
				$post_type = $taxonomy->object_type[0];
856
			}
857
		}
858
859
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
860
			$post_type = get_post_type_object( $post_type );
861
862
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
863
				if ( isset( $post_type->labels->name ) ) {
864
					$cpt_text = $post_type->labels->name;
865
				} elseif ( isset( $post_type->label ) ) {
866
					$cpt_text = $post_type->label;
867
				}
868
869
				if ( isset( $cpt_text ) ) {
870
					/* translators: %s is the name of a custom post type */
871
					$click_handle_text = sprintf( __( 'More %s', 'jetpack' ), $cpt_text );
872
					unset( $cpt_text );
873
				}
874
			}
875
		}
876
877
		unset( $post_type );
878
879
		// Base JS settings
880
		$js_settings = array(
881
			'id'               => self::get_settings()->container,
882
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
883
			'type'             => esc_js( self::get_settings()->type ),
884
			'wrapper'          => self::has_wrapper(),
885
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
886
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
887
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
888
			'text'             => esc_js( $click_handle_text ),
889
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
890
			'currentday'       => $currentday,
891
			'order'            => 'DESC',
892
			'scripts'          => array(),
893
			'styles'           => array(),
894
			'google_analytics' => false,
895
			'offset'           => max( 1, self::wp_query()->get( 'paged' ) ), // Pass through the current page so we can use that to offset the first load.
896
			'history'          => array(
897
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( esc_url( get_home_url() ) ) ),
898
				'path'                 => self::get_request_path(),
899
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
900
				'parameters'           => self::get_request_parameters(),
901
			),
902
			'query_args'      => self::get_query_vars(),
903
			'query_before'    => current_time( 'mysql' ),
904
			'last_post_date'  => self::get_last_post_date(),
905
			'body_class'	  => self::body_class(),
906
			'loading_text'	  => esc_js( __( 'Loading new page', 'jetpack' ) ),
907
		);
908
909
		// Optional order param
910
		if ( isset( $_REQUEST['order'] ) ) {
911
			$order = strtoupper( $_REQUEST['order'] );
912
913
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
914
				$js_settings['order'] = $order;
915
		}
916
917
		/**
918
		 * Filter the Infinite Scroll JS settings outputted in the head.
919
		 *
920
		 * @module infinite-scroll
921
		 *
922
		 * @since 2.0.0
923
		 *
924
		 * @param array $js_settings Infinite Scroll JS settings.
925
		 */
926
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
927
928
		/**
929
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
930
		 *
931
		 * @module infinite-scroll
932
		 *
933
		 * @since 2.0.0
934
		 */
935
		do_action( 'infinite_scroll_wp_head' );
936
937
		?>
938
		<script type="text/javascript">
939
		//<![CDATA[
940
		var infiniteScroll = JSON.parse( decodeURIComponent( '<?php echo
941
			rawurlencode( json_encode( array( 'settings' => $js_settings ) ) );
942
		?>' ) );
943
		//]]>
944
		</script>
945
		<?php
946
	}
947
948
	/**
949
	 * Build path data for current request.
950
	 * Used for Google Analytics and pushState history tracking.
951
	 *
952
	 * @global $wp_rewrite
953
	 * @global $wp
954
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
955
	 * @return string|bool
956
	 */
957
	private function get_request_path() {
958
		global $wp_rewrite;
959
960
		if ( $wp_rewrite->using_permalinks() ) {
961
			global $wp;
962
963
			// If called too early, bail
964
			if ( ! isset( $wp->request ) )
965
				return false;
966
967
			// Determine path for paginated version of current request
968
			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...
969
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
970
			else
971
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
972
973
			// Slashes everywhere we need them
974
			if ( 0 !== strpos( $path, '/' ) )
975
				$path = '/' . $path;
976
977
			$path = user_trailingslashit( $path );
978
		} else {
979
			// Clean up raw $_REQUEST input
980
			$path = array_map( 'sanitize_text_field', $_REQUEST );
981
			$path = array_filter( $path );
982
983
			$path['paged'] = '%d';
984
985
			$path = add_query_arg( $path, '/' );
986
		}
987
988
		return empty( $path ) ? false : $path;
989
	}
990
991
	/**
992
	 * Return query string for current request, prefixed with '?'.
993
	 *
994
	 * @return string
995
	 */
996
	private function get_request_parameters() {
997
		$uri = $_SERVER[ 'REQUEST_URI' ];
998
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
999
		if ( $count != 1 )
1000
			return '';
1001
		return $uri;
1002
	}
1003
1004
	/**
1005
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
1006
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
1007
	 *
1008
	 * @global $wp_scripts, $wp_styles
1009
	 * @action wp_footer
1010
	 * @return string
1011
	 */
1012
	function action_wp_footer() {
1013
		global $wp_scripts, $wp_styles;
1014
1015
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
1016
		/**
1017
		 * Filter the list of scripts already present on the page.
1018
		 *
1019
		 * @module infinite-scroll
1020
		 *
1021
		 * @since 2.1.2
1022
		 *
1023
		 * @param array $scripts Array of scripts present on the page.
1024
		 */
1025
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
1026
1027
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
1028
		/**
1029
		 * Filter the list of styles already present on the page.
1030
		 *
1031
		 * @module infinite-scroll
1032
		 *
1033
		 * @since 2.1.2
1034
		 *
1035
		 * @param array $styles Array of styles present on the page.
1036
		 */
1037
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
1038
1039
		?><script type="text/javascript">
1040
			(function() {
1041
				var extend = function(out) {
1042
					out = out || {};
1043
1044
					for (var i = 1; i < arguments.length; i++) {
1045
						if (!arguments[i])
1046
						continue;
1047
1048
						for (var key in arguments[i]) {
1049
						if (arguments[i].hasOwnProperty(key))
1050
							out[key] = arguments[i][key];
1051
						}
1052
					}
1053
1054
					return out;
1055
				};
1056
				extend( window.infiniteScroll.settings.scripts, <?php echo wp_json_encode( $scripts ); ?> );
1057
				extend( window.infiniteScroll.settings.styles, <?php echo wp_json_encode( $styles ); ?> );
1058
			})();
1059
		</script>
1060
		<?php
1061
		$aria_live = 'assertive';
1062
		if ( 'scroll' === self::get_settings()->type ) {
1063
			$aria_live = 'polite';
1064
		}
1065
		?>
1066
		<span id="infinite-aria" aria-live="<?php echo esc_attr( $aria_live ); ?>"></span>
1067
		<?php
1068
	}
1069
1070
	/**
1071
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
1072
	 *
1073
	 * @global $wp_scripts
1074
	 * @uses sanitize_text_field, add_query_arg
1075
	 * @filter infinite_scroll_results
1076
	 * @return array
1077
	 */
1078
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
1079
		// Don't bother unless there are posts to display
1080
		if ( 'success' != $results['type'] )
1081
			return $results;
1082
1083
		// Parse and sanitize the script handles already output
1084
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
1085
1086
		if ( is_array( $initial_scripts ) ) {
1087
			global $wp_scripts;
1088
1089
			// Identify new scripts needed by the latest set of IS posts
1090
			$new_scripts = array_filter(
1091
				$wp_scripts->done,
1092
				function ( $script_name ) use ( $initial_scripts ) {
1093
					// Jetpack block scripts should always be sent, even if they've been
1094
					// sent before. These scripts only run once on when loaded, they don't
1095
					// watch for new blocks being added.
1096
					if ( 0 === strpos( $script_name, 'jetpack-block-' ) ) {
1097
						return true;
1098
					}
1099
1100
					return ! in_array( $script_name, $initial_scripts, true );
1101
				}
1102
			);
1103
1104
			// If new scripts are needed, extract relevant data from $wp_scripts
1105
			if ( ! empty( $new_scripts ) ) {
1106
				$results['scripts'] = array();
1107
1108
				foreach ( $new_scripts as $handle ) {
1109
					// Abort if somehow the handle doesn't correspond to a registered script
1110
					// or if the script doesn't have `src` set.
1111
					$script_not_registered = ! isset( $wp_scripts->registered[ $handle ] );
1112
					$empty_src             = empty( $wp_scripts->registered[ $handle ]->src );
1113
					if ( $script_not_registered || $empty_src ) {
1114
						continue;
1115
					}
1116
1117
					// Provide basic script data
1118
					$script_data = array(
1119
						'handle'        => $handle,
1120
						'footer'        => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer, true ) ),
1121
						'extra_data'    => $wp_scripts->print_extra_script( $handle, false ),
1122
						'before_handle' => $wp_scripts->print_inline_script( $handle, 'before', false ),
1123
						'after_handle'  => $wp_scripts->print_inline_script( $handle, 'after', false ),
1124
					);
1125
1126
					// Base source
1127
					$src = $wp_scripts->registered[ $handle ]->src;
1128
1129
					// Take base_url into account
1130
					if ( strpos( $src, 'http' ) !== 0 )
1131
						$src = $wp_scripts->base_url . $src;
1132
1133
					// Version and additional arguments
1134 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
1135
						$ver = '';
1136
					else
1137
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
1138
1139 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
1140
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
1141
1142
					// Full script source with version info
1143
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
1144
1145
					// Add script to data that will be returned to IS JS
1146
					array_push( $results['scripts'], $script_data );
1147
				}
1148
			}
1149
		}
1150
1151
		// Expose additional script data to filters, but only include in final `$results` array if needed.
1152
		if ( ! isset( $results['scripts'] ) )
1153
			$results['scripts'] = array();
1154
1155
		/**
1156
		 * Filter the additional scripts required by the latest set of IS posts.
1157
		 *
1158
		 * @module infinite-scroll
1159
		 *
1160
		 * @since 2.1.2
1161
		 *
1162
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
1163
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
1164
		 * @param array $results Array of Infinite Scroll results.
1165
		 * @param array $query_args Array of Query arguments.
1166
		 * @param WP_Query $wp_query WP Query.
1167
		 */
1168
		$results['scripts'] = apply_filters(
1169
			'infinite_scroll_additional_scripts',
1170
			$results['scripts'],
1171
			$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...
1172
			$results,
1173
			$query_args,
1174
			$wp_query
1175
		);
1176
1177
		if ( empty( $results['scripts'] ) )
1178
			unset( $results['scripts' ] );
1179
1180
		// Parse and sanitize the style handles already output
1181
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
1182
1183
		if ( is_array( $initial_styles ) ) {
1184
			global $wp_styles;
1185
1186
			// Identify new styles needed by the latest set of IS posts
1187
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1188
1189
			// If new styles are needed, extract relevant data from $wp_styles
1190
			if ( ! empty( $new_styles ) ) {
1191
				$results['styles'] = array();
1192
1193
				foreach ( $new_styles as $handle ) {
1194
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1195
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1196
						continue;
1197
1198
					// Provide basic style data
1199
					$style_data = array(
1200
						'handle' => $handle,
1201
						'media'  => 'all'
1202
					);
1203
1204
					// Base source
1205
					$src = $wp_styles->registered[ $handle ]->src;
1206
1207
					// Take base_url into account
1208
					if ( strpos( $src, 'http' ) !== 0 )
1209
						$src = $wp_styles->base_url . $src;
1210
1211
					// Version and additional arguments
1212 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1213
						$ver = '';
1214
					else
1215
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1216
1217 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1218
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1219
1220
					// Full stylesheet source with version info
1221
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1222
1223
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1224
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1225
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1226
						$style_data['conditional'] = str_replace( array(
1227
							'lte',
1228
							'lt',
1229
							'gte',
1230
							'gt'
1231
						), array(
1232
							'%ver <=',
1233
							'%ver <',
1234
							'%ver >=',
1235
							'%ver >',
1236
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1237
1238
						// 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().
1239
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1240
1241
						// Lastly, remove the IE strings
1242
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1243
					}
1244
1245
					// Parse requested media context for stylesheet
1246 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1247
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1248
1249
					// Add stylesheet to data that will be returned to IS JS
1250
					array_push( $results['styles'], $style_data );
1251
				}
1252
			}
1253
		}
1254
1255
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1256
		if ( ! isset( $results['styles'] ) )
1257
			$results['styles'] = array();
1258
1259
		/**
1260
		 * Filter the additional styles required by the latest set of IS posts.
1261
		 *
1262
		 * @module infinite-scroll
1263
		 *
1264
		 * @since 2.1.2
1265
		 *
1266
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1267
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1268
		 * @param array $results Array of Infinite Scroll results.
1269
		 * @param array $query_args Array of Query arguments.
1270
		 * @param WP_Query $wp_query WP Query.
1271
		 */
1272
		$results['styles'] = apply_filters(
1273
			'infinite_scroll_additional_stylesheets',
1274
			$results['styles'],
1275
			$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...
1276
			$results,
1277
			$query_args,
1278
			$wp_query
1279
		);
1280
1281
		if ( empty( $results['styles'] ) )
1282
			unset( $results['styles' ] );
1283
1284
		// Lastly, return the IS results array
1285
		return $results;
1286
	}
1287
1288
	/**
1289
	 * Runs the query and returns the results via JSON.
1290
	 * Triggered by an AJAX request.
1291
	 *
1292
	 * @global $wp_query
1293
	 * @global $wp_the_query
1294
	 * @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
1295
	 * @return string or null
1296
	 */
1297
	function query() {
1298
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1299
			die;
1300
1301
		$page = (int) $_REQUEST['page'];
1302
1303
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1304
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1305
			global $previousday;
1306
			$previousday = $_REQUEST['currentday'];
1307
		}
1308
1309
		$post_status = array( 'publish' );
1310
		if ( current_user_can( 'read_private_posts' ) )
1311
			array_push( $post_status, 'private' );
1312
1313
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1314
1315
		$query_args = array_merge( self::wp_query()->query_vars, array(
1316
			'paged'          => $page,
1317
			'post_status'    => $post_status,
1318
			'posts_per_page' => self::posts_per_page(),
1319
			'order'          => $order
1320
		) );
1321
1322
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1323
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1324
			unset( $query_args['s'] );
1325
		}
1326
1327
		// By default, don't query for a specific page of a paged post object.
1328
		// This argument can come from merging self::wp_query() into $query_args above.
1329
		// Since IS is only used on archives, we should always display the first page of any paged content.
1330
		unset( $query_args['page'] );
1331
1332
		/**
1333
		 * Filter the array of main query arguments.
1334
		 *
1335
		 * @module infinite-scroll
1336
		 *
1337
		 * @since 2.0.1
1338
		 *
1339
		 * @param array $query_args Array of Query arguments.
1340
		 */
1341
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1342
1343
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1344
1345
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = $infinite_scroll_query = new WP_Query();
1346
1347
		$infinite_scroll_query->query( $query_args );
1348
1349
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1350
1351
		$results = array();
1352
1353
		if ( have_posts() ) {
1354
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1355
			ob_start();
1356
			wp_head();
1357
			while ( ob_get_length() ) {
1358
				ob_end_clean();
1359
			}
1360
1361
			$results['type'] = 'success';
1362
1363
			/**
1364
			 * Fires when rendering Infinite Scroll posts.
1365
			 *
1366
			 * @module infinite-scroll
1367
			 *
1368
			 * @since 2.0.0
1369
			 */
1370
			do_action( 'infinite_scroll_render' );
1371
			$results['html'] = ob_get_clean();
1372
			if ( empty( $results['html'] ) ) {
1373
				/**
1374
				 * 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.
1375
				 *
1376
				 * @module infinite-scroll
1377
				 *
1378
				 * @since 6.0.0
1379
				 */
1380
				$callbacks = apply_filters(
1381
					'infinite_scroll_render_callbacks',
1382
					array( self::get_settings()->render ) // This is the setting callback e.g. from add theme support.
1383
				);
1384
1385
				// Append fallback callback. That rhymes.
1386
				$callbacks[] = array( $this, 'render' );
1387
1388
				foreach ( $callbacks as $callback ) {
1389
					if ( false !== $callback && is_callable( $callback ) ) {
1390
						rewind_posts();
1391
						ob_start();
1392
						add_action( 'infinite_scroll_render', $callback );
1393
1394
						/**
1395
						 * This action is already documented above.
1396
						 * See https://github.com/Automattic/jetpack/pull/16317/
1397
						 * for more details as to why it was introduced.
1398
						 */
1399
						do_action( 'infinite_scroll_render' );
1400
1401
						$results['html'] = ob_get_clean();
1402
						remove_action( 'infinite_scroll_render', $callback );
1403
					}
1404
					if ( ! empty( $results['html'] ) ) {
1405
						break;
1406
					}
1407
				}
1408
			}
1409
1410
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1411
			if ( empty( $results['html'] ) ) {
1412
				unset( $results['html'] );
1413
				/**
1414
				 * Fires when Infinite Scoll doesn't render any posts.
1415
				 *
1416
				 * @module infinite-scroll
1417
				 *
1418
				 * @since 2.0.0
1419
				 */
1420
				do_action( 'infinite_scroll_empty' );
1421
				$results['type'] = 'empty';
1422
			} elseif ( $this->has_wrapper() ) {
1423
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1424
				$wrapper_classes .= ' infinite-view-' . $page;
1425
				$wrapper_classes = trim( $wrapper_classes );
1426
				$aria_label = sprintf(
1427
					/* translators: %1$s is the page count */
1428
					__( 'Page: %1$d.', 'jetpack' ),
1429
					$page
1430
				);
1431
1432
				$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>';
1433
			}
1434
1435
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1436
			ob_start();
1437
			wp_footer();
1438
			while ( ob_get_length() ) {
1439
				ob_end_clean();
1440
			}
1441
1442
			if ( 'success' == $results['type'] ) {
1443
				global $currentday;
1444
				$results['lastbatch'] = self::is_last_batch();
1445
				$results['currentday'] = $currentday;
1446
			}
1447
1448
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1449
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1450
				global $jetpack_sharing_counts;
1451
1452
				while( have_posts() ) {
1453
					the_post();
1454
1455
					sharing_register_post_for_share_counts( get_the_ID() );
1456
				}
1457
1458
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1459
			}
1460
		} else {
1461
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1462
			do_action( 'infinite_scroll_empty' );
1463
			$results['type'] = 'empty';
1464
		}
1465
1466
		wp_send_json(
1467
			/**
1468
			 * Filter the Infinite Scroll results.
1469
			 *
1470
			 * @module infinite-scroll
1471
			 *
1472
			 * @since 2.0.0
1473
			 *
1474
			 * @param array $results Array of Infinite Scroll results.
1475
			 * @param array $query_args Array of main query arguments.
1476
			 * @param WP_Query $wp_query WP Query.
1477
			 */
1478
			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...
1479
		);
1480
	}
1481
1482
	/**
1483
	 * Update the $allowed_vars array with the standard WP public and private
1484
	 * query vars, as well as taxonomy vars
1485
	 *
1486
	 * @global $wp
1487
	 * @param array $allowed_vars
1488
	 * @filter infinite_scroll_allowed_vars
1489
	 * @return array
1490
	 */
1491
	function allowed_query_vars( $allowed_vars ) {
1492
		global $wp;
1493
1494
		$allowed_vars += $wp->public_query_vars;
1495
		$allowed_vars += $wp->private_query_vars;
1496
		$allowed_vars += $this->get_taxonomy_vars();
1497
1498
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1499
			unset( $allowed_vars[ $key ] );
1500
		}
1501
1502
		return array_unique( $allowed_vars );
1503
	}
1504
1505
	/**
1506
	 * Returns an array of stock and custom taxonomy query vars
1507
	 *
1508
	 * @global $wp_taxonomies
1509
	 * @return array
1510
	 */
1511
	function get_taxonomy_vars() {
1512
		global $wp_taxonomies;
1513
1514
		$taxonomy_vars = array();
1515
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1516
			if ( $t->query_var )
1517
				$taxonomy_vars[] = $t->query_var;
1518
		}
1519
1520
		// still needed?
1521
		$taxonomy_vars[] = 'tag_id';
1522
1523
		return $taxonomy_vars;
1524
	}
1525
1526
	/**
1527
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1528
	 *
1529
	 * @param array $query_args
1530
	 * @filter infinite_scroll_query_args
1531
	 * @return array
1532
	 */
1533
	function inject_query_args( $query_args ) {
1534
		/**
1535
		 * Filter the array of allowed Infinite Scroll query arguments.
1536
		 *
1537
		 * @module infinite-scroll
1538
		 *
1539
		 * @since 2.6.0
1540
		 *
1541
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1542
		 * @param array $query_args Array of query arguments.
1543
		 */
1544
		$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...
1545
1546
		$query_args = array_merge( $query_args, array(
1547
			'suppress_filters' => false,
1548
		) );
1549
1550
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1551
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1552
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1553
					$query_args[ $var ] = $value;
1554
			}
1555
		}
1556
1557
		return $query_args;
1558
	}
1559
1560
	/**
1561
	 * Rendering fallback used when themes don't specify their own handler.
1562
	 *
1563
	 * @uses have_posts, the_post, get_template_part, get_post_format
1564
	 * @action infinite_scroll_render
1565
	 * @return string
1566
	 */
1567
	function render() {
1568
		while ( have_posts() ) {
1569
			the_post();
1570
1571
			get_template_part( 'content', get_post_format() );
1572
		}
1573
	}
1574
1575
	/**
1576
	 * Allow plugins to filter what archives Infinite Scroll supports
1577
	 *
1578
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1579
	 * @return bool
1580
	 */
1581
	public static function archive_supports_infinity() {
1582
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1583
1584
		// Disable when previewing a non-active theme in the customizer
1585
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1586
			return false;
1587
		}
1588
1589
		/**
1590
		 * Allow plugins to filter what archives Infinite Scroll supports.
1591
		 *
1592
		 * @module infinite-scroll
1593
		 *
1594
		 * @since 2.0.0
1595
		 *
1596
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1597
		 * @param object self::get_settings() IS settings provided by theme.
1598
		 */
1599
		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...
1600
	}
1601
1602
	/**
1603
	 * The Infinite Blog Footer
1604
	 *
1605
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1606
	 * @return string or null
1607
	 */
1608
	function footer() {
1609
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
1610
			return;
1611
		}
1612
1613
		// Bail if theme requested footer not show
1614
		if ( false == self::get_settings()->footer )
1615
			return;
1616
1617
		// We only need the new footer for the 'scroll' type
1618
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1619
			return;
1620
1621
		if ( self::is_last_batch() ) {
1622
			return;
1623
		}
1624
1625
		// Display a footer, either user-specified or a default
1626
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1627
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1628
		else
1629
			self::default_footer();
1630
	}
1631
1632
	/**
1633
	 * Render default IS footer
1634
	 *
1635
	 * @uses __, wp_get_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1636
	 * @return string
1637
	 *
1638
	 */
1639
	private function default_footer() {
1640
		if ( '' !== get_privacy_policy_url() ) {
1641
			$credits = get_the_privacy_policy_link() . '<span role="separator" aria-hidden="true"> / </span>';
1642
		} else {
1643
			$credits = '';
1644
		}
1645
		$credits .= sprintf(
1646
			'<a href="https://wordpress.org/" rel="noopener noreferrer" target="_blank" rel="generator">%1$s</a> ',
1647
			__( 'Proudly powered by WordPress', 'jetpack' )
1648
		);
1649
		$credits .= sprintf(
1650
			/* translators: %1$s is the name of a theme */
1651
			__( 'Theme: %1$s.', 'jetpack' ),
1652
			wp_get_theme()->Name
1653
		);
1654
		/**
1655
		 * Filter Infinite Scroll's credit text.
1656
		 *
1657
		 * @module infinite-scroll
1658
		 *
1659
		 * @since 2.0.0
1660
		 *
1661
		 * @param string $credits Infinite Scroll credits.
1662
		 */
1663
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1664
1665
		?>
1666
		<div id="infinite-footer">
1667
			<div class="container">
1668
				<div class="blog-info">
1669
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" rel="home">
1670
						<?php bloginfo( 'name' ); ?>
1671
					</a>
1672
				</div>
1673
				<div class="blog-credits">
1674
					<?php echo $credits; ?>
1675
				</div>
1676
			</div>
1677
		</div><!-- #infinite-footer -->
1678
		<?php
1679
	}
1680
1681
	/**
1682
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1683
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1684
	 *
1685
	 * @param string $url
1686
	 * @uses remove_query_arg
1687
	 * @filter grunion_contact_form_redirect_url
1688
	 * @return string
1689
	 */
1690
	public function filter_grunion_redirect_url( $url ) {
1691
		// Remove IS query args, if present
1692
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1693
			$url = remove_query_arg( array(
1694
				'infinity',
1695
				'action',
1696
				'page',
1697
				'order',
1698
				'scripts',
1699
				'styles'
1700
			), $url );
1701
		}
1702
1703
		return $url;
1704
	}
1705
1706
	/**
1707
	 * When the MediaElement is loaded in dynamically, we need to enforce that
1708
	 * its settings are added to the page as well.
1709
	 *
1710
	 * @param array $scripts_data New scripts exposed to the infinite scroll.
1711
	 *
1712
	 * @since 8.4.0
1713
	 */
1714
	public function add_mejs_config( $scripts_data ) {
1715
		foreach ( $scripts_data as $key => $data ) {
1716
			if ( 'mediaelement-core' === $data['handle'] ) {
1717
				$mejs_settings = array(
1718
					'pluginPath'  => includes_url( 'js/mediaelement/', 'relative' ),
1719
					'classPrefix' => 'mejs-',
1720
					'stretching'  => 'responsive',
1721
				);
1722
1723
				$scripts_data[ $key ]['extra_data'] = sprintf(
1724
					'window.%s = %s',
1725
					'_wpmejsSettings',
1726
					wp_json_encode( apply_filters( 'mejs_settings', $mejs_settings ) )
1727
				);
1728
			}
1729
		}
1730
		return $scripts_data;
1731
	}
1732
1733
	/**
1734
	 * Determines whether the legacy AMP Reader post templates are being used.
1735
	 *
1736
	 * @return bool
1737
	 */
1738
	private function is_exempted_amp_page() {
1739
		if ( is_singular( 'web-story' ) ) {
1740
			// Ensure that <amp-next-page> is not injected after <amp-story> as generated by the Web Stories plugin.
1741
			return true;
1742
		}
1743
		if ( function_exists( 'amp_is_legacy' ) ) {
1744
			// Available since AMP v2.0, this will return false if a theme like Twenty Twenty is selected as the Reader theme.
1745
			return amp_is_legacy();
1746
		}
1747
		if ( method_exists( 'AMP_Options_Manager', 'get_option' ) ) {
1748
			// In versions prior to v2.0, checking the template mode as being 'reader' is sufficient.
1749
			return 'reader' === AMP_Options_Manager::get_option( 'theme_support' );
1750
		}
1751
		return false;
1752
	}
1753
1754
	/**
1755
	 * Load AMP specific hooks.
1756
	 *
1757
	 * @return void
1758
	 */
1759
	public function amp_load_hooks() {
1760
		if ( $this->is_exempted_amp_page() ) {
1761
			return;
1762
		}
1763
1764
		if ( class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request() ) {
1765
			$template = self::get_settings()->render;
1766
1767
			add_filter( 'jetpack_infinite_scroll_load_scripts_and_styles', '__return_false' );
1768
1769
			add_action( 'template_redirect', array( $this, 'amp_start_output_buffering' ), 0 );
1770
			add_action( 'shutdown', array( $this, 'amp_output_buffer' ), 1 );
1771
1772
			if ( is_callable( "amp_{$template}_hooks" ) ) {
1773
				call_user_func( "amp_{$template}_hooks" );
1774
			}
1775
1776
			// Warms up the amp next page markup.
1777
			// This should be done outside the output buffering callback started in the template_redirect.
1778
			$this->amp_get_footer_template();
1779
		}
1780
	}
1781
1782
	/**
1783
	 * Start the AMP output buffering.
1784
	 *
1785
	 * @return void
1786
	 */
1787
	public function amp_start_output_buffering() {
1788
		ob_start( array( $this, 'amp_finish_output_buffering' ) );
1789
	}
1790
1791
	/**
1792
	 * Flush the AMP output buffer.
1793
	 *
1794
	 * @return void
1795
	 */
1796
	public function amp_output_buffer() {
1797
		if ( ob_get_contents() ) {
1798
			ob_end_flush();
1799
		}
1800
	}
1801
1802
	/**
1803
	 * Filter the AMP output buffer contents.
1804
	 *
1805
	 * @param string $buffer Contents of the output buffer.
1806
	 *
1807
	 * @return string|false
1808
	 */
1809
	public function amp_finish_output_buffering( $buffer ) {
1810
		// Hide WordPress admin bar on next page load.
1811
		$buffer = preg_replace(
1812
			'/id="wpadminbar"/',
1813
			'$0 next-page-hide',
1814
			$buffer
1815
		);
1816
1817
		/**
1818
		 * Get the theme footers.
1819
		 *
1820
		 * @module infinite-scroll
1821
		 *
1822
		 * @since 9.0.0
1823
		 *
1824
		 * @param array  array() An array to store multiple markup entries to be added to the footer.
1825
		 * @param string $buffer The contents of the output buffer.
1826
		 */
1827
		$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...
1828
1829
		/**
1830
		 * Filter the output buffer.
1831
		 * Themes can leverage this hook to add custom markup on next page load.
1832
		 *
1833
		 * @module infinite-scroll
1834
		 *
1835
		 * @since 9.0.0
1836
		 *
1837
		 * @param string $buffer The contents of the output buffer.
1838
		 */
1839
		$buffer = apply_filters( 'jetpack_amp_infinite_output', $buffer );
1840
1841
		// Add the amp next page markup.
1842
		$buffer = preg_replace(
1843
			'~</body>~',
1844
			$this->amp_get_footer_template( $footers ) . '$0',
1845
			$buffer
1846
		);
1847
1848
		return $buffer;
1849
	}
1850
1851
	/**
1852
	 * Get AMP next page markup with the custom footers.
1853
	 *
1854
	 * @param string[] $footers The theme footers.
1855
	 *
1856
	 * @return string
1857
	 */
1858
	protected function amp_get_footer_template( $footers = array() ) {
1859
		static $template = null;
1860
1861
		if ( null === $template ) {
1862
			$template = $this->amp_footer_template();
1863
		}
1864
1865
		if ( empty( $footers ) ) {
1866
			return $template;
1867
		}
1868
1869
		return preg_replace(
1870
			'/%%footer%%/',
1871
			implode( '', $footers ),
1872
			$template
1873
		);
1874
	}
1875
1876
	/**
1877
	 * AMP Next Page markup.
1878
	 *
1879
	 * @return string
1880
	 */
1881
	protected function amp_footer_template() {
1882
		ob_start();
1883
		?>
1884
<amp-next-page max-pages="<?php echo esc_attr( $this->amp_get_max_pages() ); ?>">
1885
	<script type="application/json">
1886
		[
1887
			<?php echo wp_json_encode( $this->amp_next_page() ); ?>
1888
		]
1889
	</script>
1890
	<div separator>
1891
		<?php
1892
		echo wp_kses_post(
1893
			/**
1894
			 * AMP infinite scroll separator.
1895
			 *
1896
			 * @module infinite-scroll
1897
			 *
1898
			 * @since 9.0.0
1899
			 *
1900
			 * @param string '' The markup for the next page separator.
1901
			 */
1902
			apply_filters( 'jetpack_amp_infinite_separator', '' )
1903
		);
1904
		?>
1905
	</div>
1906
	<div recommendation-box class="recommendation-box">
1907
		<template type="amp-mustache">
1908
			{{#pages}}
1909
			<?php
1910
			echo wp_kses_post(
1911
				/**
1912
				 * AMP infinite scroll older posts markup.
1913
				 *
1914
				 * @module infinite-scroll
1915
				 *
1916
				 * @since 9.0.0
1917
				 *
1918
				 * @param string '' The markup for the older posts/next page.
1919
				 */
1920
				apply_filters( 'jetpack_amp_infinite_older_posts', '' )
1921
			);
1922
			?>
1923
			{{/pages}}
1924
		</template>
1925
	</div>
1926
	<div footer>
1927
		%%footer%%
1928
	</div>
1929
</amp-next-page>
1930
		<?php
1931
		return ob_get_clean();
1932
	}
1933
1934
	/**
1935
	 * Get the AMP next page information.
1936
	 *
1937
	 * @return array
1938
	 */
1939
	protected function amp_next_page() {
1940
		$title = '';
1941
		$url   = '';
1942
		$image = '';
1943
1944
		if ( ! static::amp_is_last_page() ) {
1945
			$title = sprintf(
1946
				'%s - %s %d - %s',
1947
				wp_title( '', false ),
1948
				__( 'Page', 'jetpack' ),
1949
				max( get_query_var( 'paged', 1 ), 1 ) + 1,
1950
				get_bloginfo( 'name' )
1951
			);
1952
			$url   = get_next_posts_page_link();
1953
		}
1954
1955
		$next_page = array(
1956
			'title' => $title,
1957
			'url'   => $url,
1958
			'image' => $image,
1959
		);
1960
1961
		/**
1962
		 * The next page settings.
1963
		 * An array containing:
1964
		 *  - title => The title to be featured on the browser tab.
1965
		 *  - url   => The URL of next page.
1966
		 *  - image => The image URL. A required AMP setting, not in use currently. Themes are welcome to leverage.
1967
		 *
1968
		 * @module infinite-scroll
1969
		 *
1970
		 * @since 9.0.0
1971
		 *
1972
		 * @param array $next_page The contents of the output buffer.
1973
		 */
1974
		return apply_filters( 'jetpack_amp_infinite_next_page_data', $next_page );
1975
	}
1976
1977
	/**
1978
	 * Get the number of pages left.
1979
	 *
1980
	 * @return int
1981
	 */
1982
	protected static function amp_get_max_pages() {
1983
		global $wp_query;
1984
1985
		return (int) $wp_query->max_num_pages - $wp_query->query_vars['paged'];
1986
	}
1987
1988
	/**
1989
	 * Is the last page.
1990
	 *
1991
	 * @return bool
1992
	 */
1993
	protected static function amp_is_last_page() {
1994
		return 0 === static::amp_get_max_pages();
1995
	}
1996
};
1997
1998
/**
1999
 * Initialize The_Neverending_Home_Page
2000
 */
2001
function the_neverending_home_page_init() {
2002
	if ( ! current_theme_supports( 'infinite-scroll' ) )
2003
		return;
2004
2005
	new The_Neverending_Home_Page();
2006
}
2007
add_action( 'init', 'the_neverending_home_page_init', 20 );
2008
2009
/**
2010
 * Check whether the current theme is infinite-scroll aware.
2011
 * If so, include the files which add theme support.
2012
 */
2013
function the_neverending_home_page_theme_support() {
2014
	if (
2015
			defined( 'IS_WPCOM' ) && IS_WPCOM &&
2016
			defined( 'REST_API_REQUEST' ) && REST_API_REQUEST &&
2017
			! doing_action( 'restapi_theme_after_setup_theme' )
2018
	) {
2019
		// Don't source theme compat files until we're in the site's context
2020
		return;
2021
	}
2022
	$theme_name = get_stylesheet();
2023
2024
	/**
2025
	 * Filter the path to the Infinite Scroll compatibility file.
2026
	 *
2027
	 * @module infinite-scroll
2028
	 *
2029
	 * @since 2.0.0
2030
	 *
2031
	 * @param string $str IS compatibility file path.
2032
	 * @param string $theme_name Theme name.
2033
	 */
2034
	$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...
2035
2036
	if ( is_readable( $customization_file ) )
2037
		require_once( $customization_file );
2038
}
2039
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
2040
2041
/**
2042
 * Early accommodation of the Infinite Scroll AJAX request
2043
 */
2044
if ( The_Neverending_Home_Page::got_infinity() ) {
2045
	/**
2046
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
2047
	 * indicate it as early as possible for actions like init
2048
	 */
2049
	if ( ! defined( 'DOING_AJAX' ) &&
2050
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
2051
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
2052
	) {
2053
		define( 'DOING_AJAX', true );
2054
	}
2055
2056
	// Don't load the admin bar when doing the AJAX response.
2057
	show_admin_bar( false );
2058
}
2059