Completed
Push — renovate/automattic-social-pre... ( e1704e...3b6d89 )
by
unknown
11:58 queued 04:03
created

The_Neverending_Home_Page::footer()   B

Complexity

Conditions 9
Paths 6

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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