Completed
Push — add/redirect-everything ( 50f94e...5deed2 )
by
unknown
181:35 queued 174:43
created

The_Neverending_Home_Page::get_settings()   F

Complexity

Conditions 43
Paths 601

Size

Total Lines 181

Duplication

Lines 22
Ratio 12.15 %

Importance

Changes 0
Metric Value
cc 43
nc 601
nop 0
dl 22
loc 181
rs 0.4433
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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