Completed
Push — feature/infinite-scroll ( de9103 )
by
unknown
07:30
created

The_Neverending_Home_Page::amp_load_hooks()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 0
loc 18
rs 9.6666
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 ( 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 ( count( $taxonomy->object_type ) < 2 ) {
842
				$post_type = $taxonomy->object_type[0];
843
			}
844
		}
845
846
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
847
			$post_type = get_post_type_object( $post_type );
848
849
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
850
				if ( isset( $post_type->labels->name ) ) {
851
					$cpt_text = $post_type->labels->name;
852
				} elseif ( isset( $post_type->label ) ) {
853
					$cpt_text = $post_type->label;
854
				}
855
856
				if ( isset( $cpt_text ) ) {
857
					/* translators: %s is the name of a custom post type */
858
					$click_handle_text = sprintf( __( 'More %s', 'jetpack' ), $cpt_text );
859
					unset( $cpt_text );
860
				}
861
			}
862
		}
863
864
		unset( $post_type );
865
866
		// Base JS settings
867
		$js_settings = array(
868
			'id'               => self::get_settings()->container,
869
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
870
			'type'             => esc_js( self::get_settings()->type ),
871
			'wrapper'          => self::has_wrapper(),
872
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
873
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
874
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
875
			'text'             => esc_js( $click_handle_text ),
876
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
877
			'currentday'       => $currentday,
878
			'order'            => 'DESC',
879
			'scripts'          => array(),
880
			'styles'           => array(),
881
			'google_analytics' => false,
882
			'offset'           => max( 1, self::wp_query()->get( 'paged' ) ), // Pass through the current page so we can use that to offset the first load.
883
			'history'          => array(
884
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( esc_url( get_home_url() ) ) ),
885
				'path'                 => self::get_request_path(),
886
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
887
				'parameters'           => self::get_request_parameters(),
888
			),
889
			'query_args'      => self::get_query_vars(),
890
			'query_before'    => current_time( 'mysql' ),
891
			'last_post_date'  => self::get_last_post_date(),
892
			'body_class'	  => self::body_class(),
893
		);
894
895
		// Optional order param
896
		if ( isset( $_REQUEST['order'] ) ) {
897
			$order = strtoupper( $_REQUEST['order'] );
898
899
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
900
				$js_settings['order'] = $order;
901
		}
902
903
		/**
904
		 * Filter the Infinite Scroll JS settings outputted in the head.
905
		 *
906
		 * @module infinite-scroll
907
		 *
908
		 * @since 2.0.0
909
		 *
910
		 * @param array $js_settings Infinite Scroll JS settings.
911
		 */
912
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
913
914
		/**
915
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
916
		 *
917
		 * @module infinite-scroll
918
		 *
919
		 * @since 2.0.0
920
		 */
921
		do_action( 'infinite_scroll_wp_head' );
922
923
		?>
924
		<script type="text/javascript">
925
		//<![CDATA[
926
		var infiniteScroll = JSON.parse( decodeURIComponent( '<?php echo
927
			rawurlencode( json_encode( array( 'settings' => $js_settings ) ) );
928
		?>' ) );
929
		//]]>
930
		</script>
931
		<?php
932
	}
933
934
	/**
935
	 * Build path data for current request.
936
	 * Used for Google Analytics and pushState history tracking.
937
	 *
938
	 * @global $wp_rewrite
939
	 * @global $wp
940
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
941
	 * @return string|bool
942
	 */
943
	private function get_request_path() {
944
		global $wp_rewrite;
945
946
		if ( $wp_rewrite->using_permalinks() ) {
947
			global $wp;
948
949
			// If called too early, bail
950
			if ( ! isset( $wp->request ) )
951
				return false;
952
953
			// Determine path for paginated version of current request
954
			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...
955
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
956
			else
957
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
958
959
			// Slashes everywhere we need them
960
			if ( 0 !== strpos( $path, '/' ) )
961
				$path = '/' . $path;
962
963
			$path = user_trailingslashit( $path );
964
		} else {
965
			// Clean up raw $_REQUEST input
966
			$path = array_map( 'sanitize_text_field', $_REQUEST );
967
			$path = array_filter( $path );
968
969
			$path['paged'] = '%d';
970
971
			$path = add_query_arg( $path, '/' );
972
		}
973
974
		return empty( $path ) ? false : $path;
975
	}
976
977
	/**
978
	 * Return query string for current request, prefixed with '?'.
979
	 *
980
	 * @return string
981
	 */
982
	private function get_request_parameters() {
983
		$uri = $_SERVER[ 'REQUEST_URI' ];
984
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
985
		if ( $count != 1 )
986
			return '';
987
		return $uri;
988
	}
989
990
	/**
991
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
992
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
993
	 *
994
	 * @global $wp_scripts, $wp_styles
995
	 * @action wp_footer
996
	 * @return string
997
	 */
998
	function action_wp_footer() {
999
		global $wp_scripts, $wp_styles;
1000
1001
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
1002
		/**
1003
		 * Filter the list of scripts already present on the page.
1004
		 *
1005
		 * @module infinite-scroll
1006
		 *
1007
		 * @since 2.1.2
1008
		 *
1009
		 * @param array $scripts Array of scripts present on the page.
1010
		 */
1011
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
1012
1013
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
1014
		/**
1015
		 * Filter the list of styles already present on the page.
1016
		 *
1017
		 * @module infinite-scroll
1018
		 *
1019
		 * @since 2.1.2
1020
		 *
1021
		 * @param array $styles Array of styles present on the page.
1022
		 */
1023
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
1024
1025
		?><script type="text/javascript">
1026
			(function() {
1027
				var extend = function(out) {
1028
					out = out || {};
1029
1030
					for (var i = 1; i < arguments.length; i++) {
1031
						if (!arguments[i])
1032
						continue;
1033
1034
						for (var key in arguments[i]) {
1035
						if (arguments[i].hasOwnProperty(key))
1036
							out[key] = arguments[i][key];
1037
						}
1038
					}
1039
1040
					return out;
1041
				};
1042
				extend( window.infiniteScroll.settings.scripts, <?php echo wp_json_encode( $scripts ); ?> );
1043
				extend( window.infiniteScroll.settings.styles, <?php echo wp_json_encode( $styles ); ?> );
1044
			})();
1045
		</script><?php
1046
	}
1047
1048
	/**
1049
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
1050
	 *
1051
	 * @global $wp_scripts
1052
	 * @uses sanitize_text_field, add_query_arg
1053
	 * @filter infinite_scroll_results
1054
	 * @return array
1055
	 */
1056
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
1057
		// Don't bother unless there are posts to display
1058
		if ( 'success' != $results['type'] )
1059
			return $results;
1060
1061
		// Parse and sanitize the script handles already output
1062
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
1063
1064
		if ( is_array( $initial_scripts ) ) {
1065
			global $wp_scripts;
1066
1067
			// Identify new scripts needed by the latest set of IS posts
1068
			$new_scripts = array_filter(
1069
				$wp_scripts->done,
1070
				function ( $script_name ) use ( $initial_scripts ) {
1071
					// Jetpack block scripts should always be sent, even if they've been
1072
					// sent before. These scripts only run once on when loaded, they don't
1073
					// watch for new blocks being added.
1074
					if ( 0 === strpos( $script_name, 'jetpack-block-' ) ) {
1075
						return true;
1076
					}
1077
1078
					return ! in_array( $script_name, $initial_scripts, true );
1079
				}
1080
			);
1081
1082
			// If new scripts are needed, extract relevant data from $wp_scripts
1083
			if ( ! empty( $new_scripts ) ) {
1084
				$results['scripts'] = array();
1085
1086
				foreach ( $new_scripts as $handle ) {
1087
					// Abort if somehow the handle doesn't correspond to a registered script
1088
					// or if the script doesn't have `src` set.
1089
					$script_not_registered = ! isset( $wp_scripts->registered[ $handle ] );
1090
					$empty_src             = empty( $wp_scripts->registered[ $handle ]->src );
1091
					if ( $script_not_registered || $empty_src ) {
1092
						continue;
1093
					}
1094
1095
					// Provide basic script data
1096
					$script_data = array(
1097
						'handle'        => $handle,
1098
						'footer'        => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer, true ) ),
1099
						'extra_data'    => $wp_scripts->print_extra_script( $handle, false ),
1100
						'before_handle' => $wp_scripts->print_inline_script( $handle, 'before', false ),
1101
						'after_handle'  => $wp_scripts->print_inline_script( $handle, 'after', false ),
1102
					);
1103
1104
					// Base source
1105
					$src = $wp_scripts->registered[ $handle ]->src;
1106
1107
					// Take base_url into account
1108
					if ( strpos( $src, 'http' ) !== 0 )
1109
						$src = $wp_scripts->base_url . $src;
1110
1111
					// Version and additional arguments
1112 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
1113
						$ver = '';
1114
					else
1115
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
1116
1117 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
1118
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
1119
1120
					// Full script source with version info
1121
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
1122
1123
					// Add script to data that will be returned to IS JS
1124
					array_push( $results['scripts'], $script_data );
1125
				}
1126
			}
1127
		}
1128
1129
		// Expose additional script data to filters, but only include in final `$results` array if needed.
1130
		if ( ! isset( $results['scripts'] ) )
1131
			$results['scripts'] = array();
1132
1133
		/**
1134
		 * Filter the additional scripts required by the latest set of IS posts.
1135
		 *
1136
		 * @module infinite-scroll
1137
		 *
1138
		 * @since 2.1.2
1139
		 *
1140
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
1141
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
1142
		 * @param array $results Array of Infinite Scroll results.
1143
		 * @param array $query_args Array of Query arguments.
1144
		 * @param WP_Query $wp_query WP Query.
1145
		 */
1146
		$results['scripts'] = apply_filters(
1147
			'infinite_scroll_additional_scripts',
1148
			$results['scripts'],
1149
			$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...
1150
			$results,
1151
			$query_args,
1152
			$wp_query
1153
		);
1154
1155
		if ( empty( $results['scripts'] ) )
1156
			unset( $results['scripts' ] );
1157
1158
		// Parse and sanitize the style handles already output
1159
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
1160
1161
		if ( is_array( $initial_styles ) ) {
1162
			global $wp_styles;
1163
1164
			// Identify new styles needed by the latest set of IS posts
1165
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1166
1167
			// If new styles are needed, extract relevant data from $wp_styles
1168
			if ( ! empty( $new_styles ) ) {
1169
				$results['styles'] = array();
1170
1171
				foreach ( $new_styles as $handle ) {
1172
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1173
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1174
						continue;
1175
1176
					// Provide basic style data
1177
					$style_data = array(
1178
						'handle' => $handle,
1179
						'media'  => 'all'
1180
					);
1181
1182
					// Base source
1183
					$src = $wp_styles->registered[ $handle ]->src;
1184
1185
					// Take base_url into account
1186
					if ( strpos( $src, 'http' ) !== 0 )
1187
						$src = $wp_styles->base_url . $src;
1188
1189
					// Version and additional arguments
1190 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1191
						$ver = '';
1192
					else
1193
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1194
1195 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1196
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1197
1198
					// Full stylesheet source with version info
1199
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1200
1201
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1202
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1203
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1204
						$style_data['conditional'] = str_replace( array(
1205
							'lte',
1206
							'lt',
1207
							'gte',
1208
							'gt'
1209
						), array(
1210
							'%ver <=',
1211
							'%ver <',
1212
							'%ver >=',
1213
							'%ver >',
1214
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1215
1216
						// 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().
1217
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1218
1219
						// Lastly, remove the IE strings
1220
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1221
					}
1222
1223
					// Parse requested media context for stylesheet
1224 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1225
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1226
1227
					// Add stylesheet to data that will be returned to IS JS
1228
					array_push( $results['styles'], $style_data );
1229
				}
1230
			}
1231
		}
1232
1233
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1234
		if ( ! isset( $results['styles'] ) )
1235
			$results['styles'] = array();
1236
1237
		/**
1238
		 * Filter the additional styles required by the latest set of IS posts.
1239
		 *
1240
		 * @module infinite-scroll
1241
		 *
1242
		 * @since 2.1.2
1243
		 *
1244
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1245
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1246
		 * @param array $results Array of Infinite Scroll results.
1247
		 * @param array $query_args Array of Query arguments.
1248
		 * @param WP_Query $wp_query WP Query.
1249
		 */
1250
		$results['styles'] = apply_filters(
1251
			'infinite_scroll_additional_stylesheets',
1252
			$results['styles'],
1253
			$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...
1254
			$results,
1255
			$query_args,
1256
			$wp_query
1257
		);
1258
1259
		if ( empty( $results['styles'] ) )
1260
			unset( $results['styles' ] );
1261
1262
		// Lastly, return the IS results array
1263
		return $results;
1264
	}
1265
1266
	/**
1267
	 * Runs the query and returns the results via JSON.
1268
	 * Triggered by an AJAX request.
1269
	 *
1270
	 * @global $wp_query
1271
	 * @global $wp_the_query
1272
	 * @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
1273
	 * @return string or null
1274
	 */
1275
	function query() {
1276
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1277
			die;
1278
1279
		$page = (int) $_REQUEST['page'];
1280
1281
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1282
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1283
			global $previousday;
1284
			$previousday = $_REQUEST['currentday'];
1285
		}
1286
1287
		$post_status = array( 'publish' );
1288
		if ( current_user_can( 'read_private_posts' ) )
1289
			array_push( $post_status, 'private' );
1290
1291
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1292
1293
		$query_args = array_merge( self::wp_query()->query_vars, array(
1294
			'paged'          => $page,
1295
			'post_status'    => $post_status,
1296
			'posts_per_page' => self::posts_per_page(),
1297
			'order'          => $order
1298
		) );
1299
1300
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1301
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1302
			unset( $query_args['s'] );
1303
		}
1304
1305
		// By default, don't query for a specific page of a paged post object.
1306
		// This argument can come from merging self::wp_query() into $query_args above.
1307
		// Since IS is only used on archives, we should always display the first page of any paged content.
1308
		unset( $query_args['page'] );
1309
1310
		/**
1311
		 * Filter the array of main query arguments.
1312
		 *
1313
		 * @module infinite-scroll
1314
		 *
1315
		 * @since 2.0.1
1316
		 *
1317
		 * @param array $query_args Array of Query arguments.
1318
		 */
1319
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1320
1321
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1322
1323
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = $infinite_scroll_query = new WP_Query();
1324
1325
		$infinite_scroll_query->query( $query_args );
1326
1327
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1328
1329
		$results = array();
1330
1331
		if ( have_posts() ) {
1332
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1333
			ob_start();
1334
			wp_head();
1335
			while ( ob_get_length() ) {
1336
				ob_end_clean();
1337
			}
1338
1339
			$results['type'] = 'success';
1340
1341
			/**
1342
			 * 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.
1343
			 *
1344
			 * @module infinite-scroll
1345
			 *
1346
			 * @since 6.0.0
1347
			 */
1348
			$callbacks = apply_filters( 'infinite_scroll_render_callbacks', array(
1349
				self::get_settings()->render, // This is the setting callback e.g. from add theme support.
1350
			) );
1351
1352
			// Append fallback callback. That rhymes.
1353
			$callbacks[] = array( $this, 'render' );
1354
1355
			foreach ( $callbacks as $callback ) {
1356
				if ( false !== $callback && is_callable( $callback ) ) {
1357
					rewind_posts();
1358
					ob_start();
1359
					add_action( 'infinite_scroll_render', $callback );
1360
1361
					/**
1362
					 * Fires when rendering Infinite Scroll posts.
1363
					 *
1364
					 * @module infinite-scroll
1365
					 *
1366
					 * @since 2.0.0
1367
					 */
1368
					do_action( 'infinite_scroll_render' );
1369
1370
					$results['html'] = ob_get_clean();
1371
					remove_action( 'infinite_scroll_render', $callback );
1372
				}
1373
				if ( ! empty( $results['html'] ) ) {
1374
					break;
1375
				}
1376
			}
1377
1378
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1379
			if ( empty( $results['html'] ) ) {
1380
				unset( $results['html'] );
1381
				/**
1382
				 * Fires when Infinite Scoll doesn't render any posts.
1383
				 *
1384
				 * @module infinite-scroll
1385
				 *
1386
				 * @since 2.0.0
1387
				 */
1388
				do_action( 'infinite_scroll_empty' );
1389
				$results['type'] = 'empty';
1390
			} elseif ( $this->has_wrapper() ) {
1391
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1392
				$wrapper_classes .= ' infinite-view-' . $page;
1393
				$wrapper_classes = trim( $wrapper_classes );
1394
1395
				$results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
1396
			}
1397
1398
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1399
			ob_start();
1400
			wp_footer();
1401
			while ( ob_get_length() ) {
1402
				ob_end_clean();
1403
			}
1404
1405
			if ( 'success' == $results['type'] ) {
1406
				global $currentday;
1407
				$results['lastbatch'] = self::is_last_batch();
1408
				$results['currentday'] = $currentday;
1409
			}
1410
1411
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1412
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1413
				global $jetpack_sharing_counts;
1414
1415
				while( have_posts() ) {
1416
					the_post();
1417
1418
					sharing_register_post_for_share_counts( get_the_ID() );
1419
				}
1420
1421
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1422
			}
1423
		} else {
1424
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1425
			do_action( 'infinite_scroll_empty' );
1426
			$results['type'] = 'empty';
1427
		}
1428
1429
		wp_send_json(
1430
			/**
1431
			 * Filter the Infinite Scroll results.
1432
			 *
1433
			 * @module infinite-scroll
1434
			 *
1435
			 * @since 2.0.0
1436
			 *
1437
			 * @param array $results Array of Infinite Scroll results.
1438
			 * @param array $query_args Array of main query arguments.
1439
			 * @param WP_Query $wp_query WP Query.
1440
			 */
1441
			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...
1442
		);
1443
	}
1444
1445
	/**
1446
	 * Update the $allowed_vars array with the standard WP public and private
1447
	 * query vars, as well as taxonomy vars
1448
	 *
1449
	 * @global $wp
1450
	 * @param array $allowed_vars
1451
	 * @filter infinite_scroll_allowed_vars
1452
	 * @return array
1453
	 */
1454
	function allowed_query_vars( $allowed_vars ) {
1455
		global $wp;
1456
1457
		$allowed_vars += $wp->public_query_vars;
1458
		$allowed_vars += $wp->private_query_vars;
1459
		$allowed_vars += $this->get_taxonomy_vars();
1460
1461
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1462
			unset( $allowed_vars[ $key ] );
1463
		}
1464
1465
		return array_unique( $allowed_vars );
1466
	}
1467
1468
	/**
1469
	 * Returns an array of stock and custom taxonomy query vars
1470
	 *
1471
	 * @global $wp_taxonomies
1472
	 * @return array
1473
	 */
1474
	function get_taxonomy_vars() {
1475
		global $wp_taxonomies;
1476
1477
		$taxonomy_vars = array();
1478
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1479
			if ( $t->query_var )
1480
				$taxonomy_vars[] = $t->query_var;
1481
		}
1482
1483
		// still needed?
1484
		$taxonomy_vars[] = 'tag_id';
1485
1486
		return $taxonomy_vars;
1487
	}
1488
1489
	/**
1490
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1491
	 *
1492
	 * @param array $query_args
1493
	 * @filter infinite_scroll_query_args
1494
	 * @return array
1495
	 */
1496
	function inject_query_args( $query_args ) {
1497
		/**
1498
		 * Filter the array of allowed Infinite Scroll query arguments.
1499
		 *
1500
		 * @module infinite-scroll
1501
		 *
1502
		 * @since 2.6.0
1503
		 *
1504
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1505
		 * @param array $query_args Array of query arguments.
1506
		 */
1507
		$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...
1508
1509
		$query_args = array_merge( $query_args, array(
1510
			'suppress_filters' => false,
1511
		) );
1512
1513
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1514
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1515
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1516
					$query_args[ $var ] = $value;
1517
			}
1518
		}
1519
1520
		return $query_args;
1521
	}
1522
1523
	/**
1524
	 * Rendering fallback used when themes don't specify their own handler.
1525
	 *
1526
	 * @uses have_posts, the_post, get_template_part, get_post_format
1527
	 * @action infinite_scroll_render
1528
	 * @return string
1529
	 */
1530
	function render() {
1531
		while ( have_posts() ) {
1532
			the_post();
1533
1534
			get_template_part( 'content', get_post_format() );
1535
		}
1536
	}
1537
1538
	/**
1539
	 * Allow plugins to filter what archives Infinite Scroll supports
1540
	 *
1541
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1542
	 * @return bool
1543
	 */
1544
	public static function archive_supports_infinity() {
1545
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1546
1547
		// Disable when previewing a non-active theme in the customizer
1548
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1549
			return false;
1550
		}
1551
1552
		/**
1553
		 * Allow plugins to filter what archives Infinite Scroll supports.
1554
		 *
1555
		 * @module infinite-scroll
1556
		 *
1557
		 * @since 2.0.0
1558
		 *
1559
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1560
		 * @param object self::get_settings() IS settings provided by theme.
1561
		 */
1562
		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...
1563
	}
1564
1565
	/**
1566
	 * The Infinite Blog Footer
1567
	 *
1568
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1569
	 * @return string or null
1570
	 */
1571
	function footer() {
1572
		if ( Jetpack_AMP_Support::is_amp_request() ) {
1573
			return;
1574
		}
1575
1576
		// Bail if theme requested footer not show
1577
		if ( false == self::get_settings()->footer )
1578
			return;
1579
1580
		// We only need the new footer for the 'scroll' type
1581
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1582
			return;
1583
1584
		if ( self::is_last_batch() ) {
1585
			return;
1586
		}
1587
1588
		// Display a footer, either user-specified or a default
1589
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1590
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1591
		else
1592
			self::default_footer();
1593
	}
1594
1595
	/**
1596
	 * Render default IS footer
1597
	 *
1598
	 * @uses __, wp_get_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1599
	 * @return string
1600
	 *
1601
	 */
1602
	private function default_footer() {
1603
		if ( '' !== get_privacy_policy_url() ) {
1604
			$credits = get_the_privacy_policy_link() . '<span role="separator" aria-hidden="true"> / </span>';
1605
		} else {
1606
			$credits = '';
1607
		}
1608
		$credits .= sprintf(
1609
			'<a href="https://wordpress.org/" rel="noopener noreferrer" target="_blank" rel="generator">%1$s</a> ',
1610
			__( 'Proudly powered by WordPress', 'jetpack' )
1611
		);
1612
		$credits .= sprintf(
1613
			/* translators: %1$s is the name of a theme */
1614
			__( 'Theme: %1$s.', 'jetpack' ),
1615
			wp_get_theme()->Name
1616
		);
1617
		/**
1618
		 * Filter Infinite Scroll's credit text.
1619
		 *
1620
		 * @module infinite-scroll
1621
		 *
1622
		 * @since 2.0.0
1623
		 *
1624
		 * @param string $credits Infinite Scroll credits.
1625
		 */
1626
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1627
1628
		?>
1629
		<div id="infinite-footer">
1630
			<div class="container">
1631
				<div class="blog-info">
1632
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" rel="home">
1633
						<?php bloginfo( 'name' ); ?>
1634
					</a>
1635
				</div>
1636
				<div class="blog-credits">
1637
					<?php echo $credits; ?>
1638
				</div>
1639
			</div>
1640
		</div><!-- #infinite-footer -->
1641
		<?php
1642
	}
1643
1644
	/**
1645
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1646
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1647
	 *
1648
	 * @param string $url
1649
	 * @uses remove_query_arg
1650
	 * @filter grunion_contact_form_redirect_url
1651
	 * @return string
1652
	 */
1653
	public function filter_grunion_redirect_url( $url ) {
1654
		// Remove IS query args, if present
1655
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1656
			$url = remove_query_arg( array(
1657
				'infinity',
1658
				'action',
1659
				'page',
1660
				'order',
1661
				'scripts',
1662
				'styles'
1663
			), $url );
1664
		}
1665
1666
		return $url;
1667
	}
1668
1669
	/**
1670
	 * When the MediaElement is loaded in dynamically, we need to enforce that
1671
	 * its settings are added to the page as well.
1672
	 *
1673
	 * @param array $scripts_data New scripts exposed to the infinite scroll.
1674
	 *
1675
	 * @since 8.4.0
1676
	 */
1677
	public function add_mejs_config( $scripts_data ) {
1678
		foreach ( $scripts_data as $key => $data ) {
1679
			if ( 'mediaelement-core' === $data['handle'] ) {
1680
				$mejs_settings = array(
1681
					'pluginPath'  => includes_url( 'js/mediaelement/', 'relative' ),
1682
					'classPrefix' => 'mejs-',
1683
					'stretching'  => 'responsive',
1684
				);
1685
1686
				$scripts_data[ $key ]['extra_data'] = sprintf(
1687
					'window.%s = %s',
1688
					'_wpmejsSettings',
1689
					wp_json_encode( apply_filters( 'mejs_settings', $mejs_settings ) )
1690
				);
1691
			}
1692
		}
1693
		return $scripts_data;
1694
	}
1695
1696
	/**
1697
	 * Load AMP specific hooks.
1698
	 *
1699
	 * @return void
1700
	 */
1701
	public function amp_load_hooks() {
1702
		if ( Jetpack_AMP_Support::is_amp_request() ) {
1703
			$template = self::get_settings()->render;
1704
1705
			add_filter( 'jetpack_infinite_scroll_load_scripts_and_styles', '__return_false' );
1706
1707
			add_action( 'template_redirect', array( $this, 'amp_start_output_buffering' ), 0 );
1708
			add_action( 'shutdown', array( $this, 'amp_output_buffer' ), 1 );
1709
1710
			if ( is_callable( "amp_{$template}_hooks" ) ) {
1711
				call_user_func( "amp_{$template}_hooks" );
1712
			}
1713
1714
			// Warms up the amp next page markup.
1715
			// This should be done outside the output buffering callback started in the template_redirect.
1716
			$this->amp_get_footer_template();
1717
		}
1718
	}
1719
1720
	/**
1721
	 * Start the AMP output buffering.
1722
	 *
1723
	 * @return void
1724
	 */
1725
	public function amp_start_output_buffering() {
1726
		ob_start( array( $this, 'amp_finish_output_buffering' ) );
1727
	}
1728
1729
	/**
1730
	 * Flush the AMP output buffer.
1731
	 *
1732
	 * @return void
1733
	 */
1734
	public function amp_output_buffer() {
1735
		if ( ob_get_contents() ) {
1736
			ob_end_flush();
1737
		}
1738
	}
1739
1740
	/**
1741
	 * Filter the AMP output buffer contents.
1742
	 *
1743
	 * @param string $buffer Contents of the output buffer.
1744
	 *
1745
	 * @return string|false
1746
	 */
1747
	public function amp_finish_output_buffering( $buffer ) {
1748
		// Hide WordPress admin bar on next page load.
1749
		$buffer = preg_replace(
1750
			'/id="wpadminbar"/',
1751
			'$0 next-page-hide',
1752
			$buffer
1753
		);
1754
1755
		// Get the theme footers.
1756
		$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...
1757
1758
		// Hook for themes to filter custom markup on next page load.
1759
		$buffer = apply_filters( 'jetpack_amp_infinite_output', $buffer );
1760
1761
		// Add the amp next page markup.
1762
		$buffer = preg_replace(
1763
			'~</body>~',
1764
			$this->amp_get_footer_template( $footers ) . '$0',
1765
			$buffer
1766
		);
1767
1768
		return $buffer;
1769
	}
1770
1771
	/**
1772
	 * Get AMP next page markup with the custom footers.
1773
	 *
1774
	 * @param string[] $footers The theme footers.
1775
	 *
1776
	 * @return string
1777
	 */
1778
	protected function amp_get_footer_template( $footers = array() ) {
1779
		static $template = null;
1780
1781
		if ( null === $template ) {
1782
			$template = $this->amp_footer_template();
1783
		}
1784
1785
		if ( empty( $footers ) ) {
1786
			return $template;
1787
		}
1788
1789
		return preg_replace(
1790
			'/%%footer%%/',
1791
			implode( '', $footers ),
1792
			$template
1793
		);
1794
	}
1795
1796
	/**
1797
	 * AMP Next Page markup.
1798
	 *
1799
	 * @return string
1800
	 */
1801
	protected function amp_footer_template() {
1802
		ob_start();
1803
		?>
1804
<amp-next-page max-pages="<?php echo esc_attr( $this->amp_get_max_pages() ); ?>">
1805
	<script type="application/json">
1806
		[
1807
			<?php echo wp_json_encode( $this->amp_next_page() ); ?>
1808
		]
1809
	</script>
1810
	<div separator>
1811
		<?php echo wp_kses_post( apply_filters( 'jetpack_amp_infinite_separator', '' ) ); ?>
1812
	</div>
1813
	<div recommendation-box class="recommendation-box">
1814
		<template type="amp-mustache">
1815
			{{#pages}}
1816
			<?php echo wp_kses_post( apply_filters( 'jetpack_amp_infinite_older_posts', '' ) ); ?>
1817
			{{/pages}}
1818
		</template>
1819
	</div>
1820
	<div footer>
1821
		%%footer%%
1822
	</div>
1823
</amp-next-page>
1824
		<?php
1825
		return ob_get_clean();
1826
	}
1827
1828
	/**
1829
	 * Get the AMP next page information.
1830
	 *
1831
	 * @return array
1832
	 */
1833
	protected static function amp_next_page() {
1834
		$title = esc_html__( 'Home page', 'jetpack' );
1835
		$url   = home_url();
1836
		$image = '';
1837
1838
		if ( ! static::amp_is_last_page() ) {
1839
			$title = esc_html__( 'Older posts', 'jetpack' );
1840
			$url   = get_next_posts_page_link();
1841
		}
1842
1843
		$next_page = array(
1844
			'title' => $title,
1845
			'url'   => $url,
1846
			'image' => $image,
1847
		);
1848
1849
		return apply_filters( 'jetpack_amp_infinite_next_page_data', $next_page );
1850
	}
1851
1852
	/**
1853
	 * Get the number of pages left.
1854
	 *
1855
	 * @return int
1856
	 */
1857
	protected static function amp_get_max_pages() {
1858
		global $wp_query;
1859
1860
		return (int) $wp_query->max_num_pages - $wp_query->query_vars['paged'];
1861
	}
1862
1863
	/**
1864
	 * Is the last page.
1865
	 *
1866
	 * @return bool
1867
	 */
1868
	protected static function amp_is_last_page() {
1869
		return 0 === static::amp_get_max_pages();
1870
	}
1871
};
1872
1873
/**
1874
 * Initialize The_Neverending_Home_Page
1875
 */
1876
function the_neverending_home_page_init() {
1877
	if ( ! current_theme_supports( 'infinite-scroll' ) )
1878
		return;
1879
1880
	new The_Neverending_Home_Page();
1881
}
1882
add_action( 'init', 'the_neverending_home_page_init', 20 );
1883
1884
/**
1885
 * Check whether the current theme is infinite-scroll aware.
1886
 * If so, include the files which add theme support.
1887
 */
1888
function the_neverending_home_page_theme_support() {
1889
	if (
1890
			defined( 'IS_WPCOM' ) && IS_WPCOM &&
1891
			defined( 'REST_API_REQUEST' ) && REST_API_REQUEST &&
1892
			! doing_action( 'restapi_theme_after_setup_theme' )
1893
	) {
1894
		// Don't source theme compat files until we're in the site's context
1895
		return;
1896
	}
1897
	$theme_name = get_stylesheet();
1898
1899
	/**
1900
	 * Filter the path to the Infinite Scroll compatibility file.
1901
	 *
1902
	 * @module infinite-scroll
1903
	 *
1904
	 * @since 2.0.0
1905
	 *
1906
	 * @param string $str IS compatibility file path.
1907
	 * @param string $theme_name Theme name.
1908
	 */
1909
	$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...
1910
1911
	if ( is_readable( $customization_file ) )
1912
		require_once( $customization_file );
1913
}
1914
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
1915
1916
/**
1917
 * Early accommodation of the Infinite Scroll AJAX request
1918
 */
1919
if ( The_Neverending_Home_Page::got_infinity() ) {
1920
	/**
1921
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
1922
	 * indicate it as early as possible for actions like init
1923
	 */
1924
	if ( ! defined( 'DOING_AJAX' ) &&
1925
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
1926
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
1927
	) {
1928
		define( 'DOING_AJAX', true );
1929
	}
1930
1931
	// Don't load the admin bar when doing the AJAX response.
1932
	show_admin_bar( false );
1933
}
1934