Completed
Push — branch-4.0 ( fc0d6f...bf547c )
by
unknown
08:22
created

The_Neverending_Home_Page::allowed_query_vars()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
Plugin Name: The Neverending Home Page.
5
Plugin URI: http://automattic.com/
6
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.
7
Version: 1.1
8
Author: Automattic
9
Author URI: http://automattic.com/
10
License: GNU General Public License v2 or later
11
License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
*/
13
14
/**
15
 * Class: The_Neverending_Home_Page relies on add_theme_support, expects specific
16
 * styling from each theme; including fixed footer.
17
 */
18
class The_Neverending_Home_Page {
19
	/**
20
	 * Register actions and filters, plus parse IS settings
21
	 *
22
	 * @uses add_action, add_filter, self::get_settings
23
	 * @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...
24
	 */
25
	function __construct() {
26
		add_action( 'pre_get_posts',                  array( $this, 'posts_per_page_query' ) );
27
28
		add_action( 'admin_init',                     array( $this, 'settings_api_init' ) );
29
		add_action( 'template_redirect',              array( $this, 'action_template_redirect' ) );
30
		add_action( 'template_redirect',              array( $this, 'ajax_response' ) );
31
		add_action( 'custom_ajax_infinite_scroll',    array( $this, 'query' ) );
32
		add_filter( 'infinite_scroll_query_args',     array( $this, 'inject_query_args' ) );
33
		add_filter( 'infinite_scroll_allowed_vars',   array( $this, 'allowed_query_vars' ) );
34
		add_action( 'the_post',                       array( $this, 'preserve_more_tag' ) );
35
		add_action( 'wp_footer',                      array( $this, 'footer' ) );
36
37
		// Plugin compatibility
38
		add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
39
40
		// Parse IS settings from theme
41
		self::get_settings();
42
	}
43
44
	/**
45
	 * Initialize our static variables
46
	 */
47
	static $the_time = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $the_time.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
48
	static $settings = null; // Don't access directly, instead use self::get_settings().
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $settings.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
49
50
	static $option_name_enabled = 'infinite_scroll';
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $option_name_enabled.

The PSR-2 coding standard requires that all properties in a class have their visibility explicitly declared. If you declare a property using

class A {
    var $property;
}

the property is implicitly global.

To learn more about the PSR-2, please see the PHP-FIG site on the PSR-2.

Loading history...
51
52
	/**
53
	 * Parse IS settings provided by theme
54
	 *
55
	 * @uses get_theme_support, infinite_scroll_has_footer_widgets, sanitize_title, add_action, get_option, wp_parse_args, is_active_sidebar
56
	 * @return object
57
	 */
58
	static function get_settings() {
59
		if ( is_null( self::$settings ) ) {
60
			$css_pattern = '#[^A-Z\d\-_]#i';
61
62
			$settings = $defaults = array(
63
				'type'            => 'scroll', // scroll | click
64
				'requested_type'  => 'scroll', // store the original type for use when logic overrides it
65
				'footer_widgets'  => false, // true | false | sidebar_id | array of sidebar_ids -- last two are checked with is_active_sidebar
66
				'container'       => 'content', // container html id
67
				'wrapper'         => true, // true | false | html class
68
				'render'          => false, // optional function, otherwise the `content` template part will be used
69
				'footer'          => true, // boolean to enable or disable the infinite footer | string to provide an html id to derive footer width from
70
				'footer_callback' => false, // function to be called to render the IS footer, in place of the default
71
				'posts_per_page'  => false, // int | false to set based on IS type
72
				'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`.
73
			);
74
75
			// Validate settings passed through add_theme_support()
76
			$_settings = get_theme_support( 'infinite-scroll' );
77
78
			if ( is_array( $_settings ) ) {
79
				// Preferred implementation, where theme provides an array of options
80
				if ( isset( $_settings[0] ) && is_array( $_settings[0] ) ) {
81
					foreach ( $_settings[0] as $key => $value ) {
82
						switch ( $key ) {
83
							case 'type' :
84
								if ( in_array( $value, array( 'scroll', 'click' ) ) )
85
									$settings[ $key ] = $settings['requested_type'] = $value;
86
87
								break;
88
89
							case 'footer_widgets' :
90
								if ( is_string( $value ) )
91
									$settings[ $key ] = sanitize_title( $value );
92
								elseif ( is_array( $value ) )
93
									$settings[ $key ] = array_map( 'sanitize_title', $value );
94
								elseif ( is_bool( $value ) )
95
									$settings[ $key ] = $value;
96
97
								break;
98
99
							case 'container' :
100 View Code Duplication
							case 'wrapper' :
101
								if ( 'wrapper' == $key && is_bool( $value ) ) {
102
									$settings[ $key ] = $value;
103
								} else {
104
									$value = preg_replace( $css_pattern, '', $value );
105
106
									if ( ! empty( $value ) )
107
										$settings[ $key ] = $value;
108
								}
109
110
								break;
111
112
							case 'render' :
113
								if ( false !== $value && is_callable( $value ) ) {
114
									$settings[ $key ] = $value;
115
116
									add_action( 'infinite_scroll_render', $value );
117
								}
118
119
								break;
120
121 View Code Duplication
							case 'footer' :
122
								if ( is_bool( $value ) ) {
123
									$settings[ $key ] = $value;
124
								} elseif ( is_string( $value ) ) {
125
									$value = preg_replace( $css_pattern, '', $value );
126
127
									if ( ! empty( $value ) )
128
										$settings[ $key ] = $value;
129
								}
130
131
								break;
132
133
							case 'footer_callback' :
134
								if ( is_callable( $value ) )
135
									$settings[ $key ] = $value;
136
								else
137
									$settings[ $key ] = false;
138
139
								break;
140
141
							case 'posts_per_page' :
142
								if ( is_numeric( $value ) )
143
									$settings[ $key ] = (int) $value;
144
145
								break;
146
147
							case 'click_handle' :
148
								if ( is_bool( $value ) ) {
149
									$settings[ $key ] = $value;
150
								}
151
152
								break;
153
154
							default:
155
								continue;
156
157
								break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
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
			// posts_per_page defaults to 7 for scroll, posts_per_page option for click
223
			if ( false === $settings['posts_per_page'] ) {
224
				if ( 'scroll' === $settings['type'] ) {
225
					$settings['posts_per_page'] = 7;
226
				}
227
				else {
228
					$settings['posts_per_page'] = (int) get_option( 'posts_per_page' );
229
				}
230
			}
231
232
			// If IS is set to click, and if the site owner changed posts_per_page, let's use that
233
			if (
234
				'click' == $settings['type']
235
				&& ( '10' !== get_option( 'posts_per_page' ) )
236
			) {
237
				$settings['posts_per_page'] = (int) get_option( 'posts_per_page' );
238
			}
239
240
			// Force display of the click handler and attendant bits when the type isn't `click`
241
			if ( 'click' !== $settings['type'] ) {
242
				$settings['click_handle'] = true;
243
			}
244
245
			// Store final settings in a class static to avoid reparsing
246
			/**
247
			 * Filter the array of Infinite Scroll settings.
248
			 *
249
			 * @module infinite-scroll
250
			 *
251
			 * @since 2.0.0
252
			 *
253
			 * @param array $settings Array of Infinite Scroll settings.
254
			 */
255
			self::$settings = apply_filters( 'infinite_scroll_settings', $settings );
256
		}
257
258
		/** This filter is documented in modules/infinite-scroll/infinity.php */
259
		return (object) apply_filters( 'infinite_scroll_settings', self::$settings );
260
	}
261
262
	/**
263
	 * Retrieve the query used with Infinite Scroll
264
	 *
265
	 * @global $wp_the_query
266
	 * @uses apply_filters
267
	 * @return object
268
	 */
269
	static function wp_query() {
270
		global $wp_the_query;
271
		/**
272
		 * Filter the Infinite Scroll query object.
273
		 *
274
		 * @module infinite-scroll
275
		 *
276
		 * @since 2.2.1
277
		 *
278
		 * @param WP_Query $wp_the_query WP Query.
279
		 */
280
		return apply_filters( 'infinite_scroll_query_object', $wp_the_query );
281
	}
282
283
	/**
284
	 * Has infinite scroll been triggered?
285
	 */
286
	static function got_infinity() {
287
		/**
288
		 * Filter the parameter used to check if Infinite Scroll has been triggered.
289
		 *
290
		 * @module infinite-scroll
291
		 *
292
		 * @since 3.9.0
293
		 *
294
		 * @param bool isset( $_GET[ 'infinity' ] ) Return true if the "infinity" parameter is set.
295
		 */
296
		return apply_filters( 'infinite_scroll_got_infinity', isset( $_GET[ 'infinity' ] ) );
297
	}
298
299
	/**
300
	 * Is this guaranteed to be the last batch of posts?
301
	 */
302
	static function is_last_batch() {
303
		return (bool) ( count( self::wp_query()->posts ) < self::get_settings()->posts_per_page );
304
	}
305
306
	/**
307
	 * The more tag will be ignored by default if the blog page isn't our homepage.
308
	 * Let's force the $more global to false.
309
	 */
310
	function preserve_more_tag( $array ) {
311
		global $more;
312
313
		if ( self::got_infinity() )
314
			$more = 0; //0 = show content up to the more tag. Add more link.
315
316
		return $array;
317
	}
318
319
	/**
320
	 * Add a checkbox field to Settings > Reading
321
	 * for enabling infinite scroll.
322
	 *
323
	 * Only show if the current theme supports infinity.
324
	 *
325
	 * @uses current_theme_supports, add_settings_field, __, register_setting
326
	 * @action admin_init
327
	 * @return null
328
	 */
329
	function settings_api_init() {
330
		if ( ! current_theme_supports( 'infinite-scroll' ) )
331
			return;
332
333
		// Add the setting field [infinite_scroll] and place it in Settings > Reading
334
		add_settings_field( self::$option_name_enabled, '<span id="infinite-scroll-options">' . __( 'To infinity and beyond', 'jetpack' ) . '</span>', array( $this, 'infinite_setting_html' ), 'reading' );
335
		register_setting( 'reading', self::$option_name_enabled, 'esc_attr' );
336
	}
337
338
	/**
339
	 * HTML code to display a checkbox true/false option
340
	 * for the infinite_scroll setting.
341
	 */
342
	function infinite_setting_html() {
343
		$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>';
344
345
		// If the blog has footer widgets, show a notice instead of the checkbox
346
		if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) {
347
			echo '<label>' . $notice . '</label>';
348
		} else {
349
			echo '<label><input name="infinite_scroll" type="checkbox" value="1" ' . checked( 1, '' !== get_option( self::$option_name_enabled ), false ) . ' /> ' . __( 'Scroll Infinitely', 'jetpack' ) . '</br><small>' . sprintf( __( '(Shows %s posts on each load)', 'jetpack' ), number_format_i18n( self::get_settings()->posts_per_page ) ) . '</small>' . '</label>';
350
		}
351
	}
352
353
	/**
354
	 * Does the legwork to determine whether the feature is enabled.
355
	 *
356
	 * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action
357
	 * @action template_redirect
358
	 * @return null
359
	 */
360
	function action_template_redirect() {
361
		// Check that we support infinite scroll, and are on the home page.
362
		if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
363
			return;
364
365
		$id = self::get_settings()->container;
366
367
		// Check that we have an id.
368
		if ( empty( $id ) )
369
			return;
370
371
		// Make sure there are enough posts for IS
372
		if ( 'click' == self::get_settings()->type && self::is_last_batch() )
373
			return;
374
375
		// Add a class to the body.
376
		add_filter( 'body_class', array( $this, 'body_class' ) );
377
378
		// Add our scripts.
379
		wp_enqueue_script( 'the-neverending-homepage', plugins_url( 'infinity.js', __FILE__ ), array( 'jquery' ), '4.0.0', true );
380
381
		// Add our default styles.
382
		wp_enqueue_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20140422' );
383
384
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_spinner_scripts' ) );
385
386
		add_action( 'wp_footer', array( $this, 'action_wp_footer_settings' ), 2 );
387
388
		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
389
390
		add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ), 10, 3 );
391
	}
392
393
	/**
394
	 * Enqueue spinner scripts.
395
	 */
396
	function enqueue_spinner_scripts() {
397
		wp_enqueue_script( 'jquery.spin' );
398
	}
399
400
	/**
401
	 * Adds an 'infinite-scroll' class to the body.
402
	 */
403
	function body_class( $classes ) {
404
		// Do not add infinity-scroll class if disabled through the Reading page
405
		$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
406
		if ( ! $disabled || 'click' == self::get_settings()->type ) {
407
			$classes[] = 'infinite-scroll';
408
409
			if ( 'scroll' == self::get_settings()->type )
410
				$classes[] = 'neverending';
411
		}
412
413
		return $classes;
414
	}
415
416
	/**
417
	 * 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
418
	 *
419
	 * @uses self::wp_query
420
	 * @uses self::get_last_post_date
421
	 * @uses self::has_only_title_matching_posts
422
	 * @return array
423
	 */
424
	function get_excluded_posts() {
425
426
		$excluded_posts = array();
427
		//loop through posts returned by wp_query call
428
		foreach( self::wp_query()->get_posts() as $post ) {
429
430
			$orderby = isset( self::wp_query()->query_vars['orderby'] ) ? self::wp_query()->query_vars['orderby'] : '';
431
			$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
432
			if ( 'modified' === $orderby || false === $post_date ) {
433
				$post_date = $post->post_modified;
434
			}
435
436
			//in case all posts initially displayed match the keyword by title we add em all to excluded posts array
437
			//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
438
			if ( self::has_only_title_matching_posts() || $post_date <= self::get_last_post_date() ) {
439
				array_push( $excluded_posts, $post->ID );
440
			}
441
		}
442
		return $excluded_posts;
443
	}
444
445
	/**
446
	 * 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
447
	 *
448
	 * @uses self::wp_query
449
	 * @uses self::get_excluded_posts
450
	 * @return array
451
	 */
452
	function get_query_vars() {
453
454
		$query_vars = self::wp_query()->query_vars;
455
		//applies to search page only
456
		if ( true === self::wp_query()->is_search() ) {
457
			//set post__not_in array in query_vars in case it does not exists
458
			if ( false === isset( $query_vars['post__not_in'] ) ) {
459
				$query_vars['post__not_in'] = array();
460
			}
461
			//get excluded posts
462
			$excluded = self::get_excluded_posts();
463
			//merge them with other post__not_in posts (eg.: sticky posts)
464
			$query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $excluded );
465
		}
466
		return $query_vars;
467
	}
468
469
	/**
470
	 * This function checks whether all posts returned by initial wp_query match the keyword by title
471
	 * The code used in this function is borrowed from WP_Query class where it is used to construct like conditions for keywords
472
	 *
473
	 * @uses self::wp_query
474
	 * @return bool
475
	 */
476
	function has_only_title_matching_posts() {
477
478
		//apply following logic for search page results only
479
		if ( false === self::wp_query()->is_search() ) {
480
			return false;
481
		}
482
483
		//grab the last posts in the stack as if the last one is title-matching the rest is title-matching as well
484
		$post = end( self::wp_query()->posts );
485
486
		//code inspired by WP_Query class
487
		if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', self::wp_query()->get( 's' ), $matches ) ) {
488
			$search_terms = self::wp_query()->query_vars['search_terms'];
489
			// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
490
			if ( empty( $search_terms ) || count( $search_terms ) > 9 ) {
491
				$search_terms = array( self::wp_query()->get( 's' ) );
492
			}
493
		} else {
494
			$search_terms = array( self::wp_query()->get( 's' ) );
495
		}
496
497
		//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
498
		$term = current( $search_terms );
499
		if ( ! empty( $term ) && false !== strpos( $post->post_title, $term ) ) {
500
			return true;
501
		}
502
503
		return false;
504
	}
505
506
	/**
507
	 * Grab the timestamp for the initial query's last post.
508
	 *
509
	 * This takes into account the query's 'orderby' parameter and returns
510
	 * false if the posts are not ordered by date.
511
	 *
512
	 * @uses self::got_infinity
513
	 * @uses self::has_only_title_matching_posts
514
	 * @uses self::wp_query
515
	 * @return string 'Y-m-d H:i:s' or false
516
	 */
517
	function get_last_post_date() {
518
		if ( self::got_infinity() )
519
			return;
520
521
		if ( ! self::wp_query()->have_posts() ) {
522
			return null;
523
		}
524
525
		//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
526
		if ( true === self::has_only_title_matching_posts() ) {
527
			return false;
528
		}
529
530
		$post = end( self::wp_query()->posts );
531
		$orderby = isset( self::wp_query()->query_vars['orderby'] ) ?
532
			self::wp_query()->query_vars['orderby'] : '';
533
		$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
534 View Code Duplication
		switch ( $orderby ) {
535
			case 'modified':
536
				return $post->post_modified;
537
			case 'date':
538
			case '':
539
				return $post_date;
540
			default:
541
				return false;
542
		}
543
	}
544
545
	/**
546
	 * Returns the appropriate `wp_posts` table field for a given query's
547
	 * 'orderby' parameter, if applicable.
548
	 *
549
	 * @param optional object $query
550
	 * @uses self::wp_query
551
	 * @return string or false
552
	 */
553
	function get_query_sort_field( $query = null ) {
554
		if ( empty( $query ) )
555
			$query = self::wp_query();
556
557
		$orderby = isset( $query->query_vars['orderby'] ) ? $query->query_vars['orderby'] : '';
558
559 View Code Duplication
		switch ( $orderby ) {
560
			case 'modified':
561
				return 'post_modified';
562
			case 'date':
563
			case '':
564
				return 'post_date';
565
			default:
566
				return false;
567
		}
568
	}
569
570
	/**
571
	 * Create a where clause that will make sure post queries
572
	 * will always return results prior to (descending sort)
573
	 * or before (ascending sort) the last post date.
574
	 *
575
	 * @global $wpdb
576
	 * @param string $where
577
	 * @param object $query
578
	 * @uses apply_filters
579
	 * @filter posts_where
580
	 * @return string
581
	 */
582
	function query_time_filter( $where, $query ) {
583
		if ( self::got_infinity() ) {
584
			global $wpdb;
585
586
			$sort_field = self::get_query_sort_field( $query );
587
			if ( false == $sort_field )
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $sort_field of type string|false against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
588
				return $where;
589
590
			$last_post_date = $_REQUEST['last_post_date'];
591
			// Sanitize timestamp
592
			if ( empty( $last_post_date ) || !preg_match( '|\d{4}\-\d{2}\-\d{2}|', $last_post_date ) )
593
				return $where;
594
595
			$operator = 'ASC' == $_REQUEST['query_args']['order'] ? '>' : '<';
596
597
			// Construct the date query using our timestamp
598
			$clause = $wpdb->prepare( " AND {$wpdb->posts}.{$sort_field} {$operator} %s", $last_post_date );
599
600
			/**
601
			 * Filter Infinite Scroll's SQL date query making sure post queries
602
			 * will always return results prior to (descending sort)
603
			 * or before (ascending sort) the last post date.
604
			 *
605
			 * @module infinite-scroll
606
			 *
607
			 * @param string $clause SQL Date query.
608
			 * @param object $query Query.
609
			 * @param string $operator Query operator.
610
			 * @param string $last_post_date Last Post Date timestamp.
611
			 */
612
			$where .= apply_filters( 'infinite_scroll_posts_where', $clause, $query, $operator, $last_post_date );
613
		}
614
615
		return $where;
616
	}
617
618
	/**
619
	 * Let's overwrite the default post_per_page setting to always display a fixed amount.
620
	 *
621
	 * @param object $query
622
	 * @uses is_admin, self::archive_supports_infinity, self::get_settings
623
	 * @return null
624
	 */
625
	function posts_per_page_query( $query ) {
626
		if ( ! is_admin() && self::archive_supports_infinity() && $query->is_main_query() )
627
			$query->set( 'posts_per_page', self::get_settings()->posts_per_page );
628
	}
629
630
	/**
631
	 * Check if the IS output should be wrapped in a div.
632
	 * Setting value can be a boolean or a string specifying the class applied to the div.
633
	 *
634
	 * @uses self::get_settings
635
	 * @return bool
636
	 */
637
	function has_wrapper() {
638
		return (bool) self::get_settings()->wrapper;
639
	}
640
641
	/**
642
	 * Returns the Ajax url
643
	 *
644
	 * @global $wp
645
	 * @uses home_url, add_query_arg, apply_filters
646
	 * @return string
647
	 */
648
	function ajax_url() {
649
		$base_url = set_url_scheme( home_url( '/' ) );
650
651
		$ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url );
652
653
		/**
654
		 * Filter the Infinite Scroll Ajax URL.
655
		 *
656
		 * @module infinite-scroll
657
		 *
658
		 * @since 2.0.0
659
		 *
660
		 * @param string $ajaxurl Infinite Scroll Ajax URL.
661
		 */
662
		return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl );
663
	}
664
665
	/**
666
	 * Our own Ajax response, avoiding calling admin-ajax
667
	 */
668
	function ajax_response() {
669
		// Only proceed if the url query has a key of "Infinity"
670
		if ( ! self::got_infinity() )
671
			return false;
672
673
		// This should already be defined below, but make sure.
674
		if ( ! defined( 'DOING_AJAX' ) ) {
675
			define( 'DOING_AJAX', true );
676
		}
677
678
		@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...
679
		send_nosniff_header();
680
681
		/**
682
		 * Fires at the end of the Infinite Scroll Ajax response.
683
		 *
684
		 * @module infinite-scroll
685
		 *
686
		 * @since 2.0.0
687
		 */
688
		do_action( 'custom_ajax_infinite_scroll' );
689
		die( '0' );
0 ignored issues
show
Coding Style Compatibility introduced by
The method ajax_response() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
690
	}
691
692
	/**
693
	 * Alias for renamed class method.
694
	 *
695
	 * Previously, JS settings object was unnecessarily output in the document head.
696
	 * When the hook was changed, the method name no longer made sense.
697
	 */
698
	function action_wp_head() {
699
		$this->action_wp_footer_settings();
700
	}
701
702
	/**
703
	 * Prints the relevant infinite scroll settings in JS.
704
	 *
705
	 * @global $wp_rewrite
706
	 * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action, self::get_query_vars
707
	 * @action wp_footer
708
	 * @return string
709
	 */
710
	function action_wp_footer_settings() {
711
		global $wp_rewrite;
712
		global $currentday;
713
714
		// Default click handle text
715
		$click_handle_text = __( 'Older posts', 'jetpack' );
716
717
		// If a single CPT is displayed, use its plural name instead of "posts"
718
		// Could be empty (posts) or an array of multiple post types.
719
		// In the latter two cases cases, the default text is used, leaving the `infinite_scroll_js_settings` filter for further customization.
720
		$post_type = self::wp_query()->get( 'post_type' );
721
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
722
			$post_type = get_post_type_object( $post_type );
723
724
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
725
				if ( isset( $post_type->labels->name ) ) {
726
					$cpt_text = $post_type->labels->name;
727
				} elseif ( isset( $post_type->label ) ) {
728
					$cpt_text = $post_type->label;
729
				}
730
731
				if ( isset( $cpt_text ) ) {
732
					$click_handle_text = sprintf( __( 'Older %s', 'jetpack' ), $cpt_text );
733
					unset( $cpt_text );
734
				}
735
			}
736
		}
737
		unset( $post_type );
738
739
		// Base JS settings
740
		$js_settings = array(
741
			'id'               => self::get_settings()->container,
742
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
743
			'type'             => esc_js( self::get_settings()->type ),
744
			'wrapper'          => self::has_wrapper(),
745
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
746
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
747
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
748
			'text'             => esc_js( $click_handle_text ),
749
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
750
			'currentday'       => $currentday,
751
			'order'            => 'DESC',
752
			'scripts'          => array(),
753
			'styles'           => array(),
754
			'google_analytics' => false,
755
			'offset'           => self::wp_query()->get( 'paged' ),
756
			'history'          => array(
757
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( get_option( 'home' ) ) ),
758
				'path'                 => self::get_request_path(),
759
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
760
				'parameters'           => self::get_request_parameters(),
761
			),
762
			'query_args'      => self::get_query_vars(),
763
			'last_post_date'  => self::get_last_post_date(),
764
		);
765
766
		// Optional order param
767
		if ( isset( $_REQUEST['order'] ) ) {
768
			$order = strtoupper( $_REQUEST['order'] );
769
770
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
771
				$js_settings['order'] = $order;
772
		}
773
774
		/**
775
		 * Filter the Infinite Scroll JS settings outputted in the head.
776
		 *
777
		 * @module infinite-scroll
778
		 *
779
		 * @since 2.0.0
780
		 *
781
		 * @param array $js_settings Infinite Scroll JS settings.
782
		 */
783
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
784
785
		/**
786
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
787
		 *
788
		 * @module infinite-scroll
789
		 *
790
		 * @since 2.0.0
791
		 */
792
		do_action( 'infinite_scroll_wp_head' );
793
794
		?>
795
		<script type="text/javascript">
796
		//<![CDATA[
797
		var infiniteScroll = <?php echo json_encode( array( 'settings' => $js_settings ) ); ?>;
798
		//]]>
799
		</script>
800
		<?php
801
	}
802
803
	/**
804
	 * Build path data for current request.
805
	 * Used for Google Analytics and pushState history tracking.
806
	 *
807
	 * @global $wp_rewrite
808
	 * @global $wp
809
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
810
	 * @return string|bool
811
	 */
812
	private function get_request_path() {
813
		global $wp_rewrite;
814
815
		if ( $wp_rewrite->using_permalinks() ) {
816
			global $wp;
817
818
			// If called too early, bail
819
			if ( ! isset( $wp->request ) )
820
				return false;
821
822
			// Determine path for paginated version of current request
823
			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...
824
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
825
			else
826
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
827
828
			// Slashes everywhere we need them
829
			if ( 0 !== strpos( $path, '/' ) )
830
				$path = '/' . $path;
831
832
			$path = user_trailingslashit( $path );
833
		} else {
834
			// Clean up raw $_REQUEST input
835
			$path = array_map( 'sanitize_text_field', $_REQUEST );
836
			$path = array_filter( $path );
837
838
			$path['paged'] = '%d';
839
840
			$path = add_query_arg( $path, '/' );
841
		}
842
843
		return empty( $path ) ? false : $path;
844
	}
845
846
	/**
847
	 * Return query string for current request, prefixed with '?'.
848
	 *
849
	 * @return string
850
	 */
851
	private function get_request_parameters() {
852
		$uri = $_SERVER[ 'REQUEST_URI' ];
853
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
854
		if ( $count != 1 )
855
			return '';
856
		return $uri;
857
	}
858
859
	/**
860
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
861
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
862
	 *
863
	 * @global $wp_scripts, $wp_styles
864
	 * @action wp_footer
865
	 * @return string
866
	 */
867
	function action_wp_footer() {
868
		global $wp_scripts, $wp_styles;
869
870
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
871
		/**
872
		 * Filter the list of scripts already present on the page.
873
		 *
874
		 * @module infinite-scroll
875
		 *
876
		 * @since 2.1.2
877
		 *
878
		 * @param array $scripts Array of scripts present on the page.
879
		 */
880
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
881
882
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
883
		/**
884
		 * Filter the list of styles already present on the page.
885
		 *
886
		 * @module infinite-scroll
887
		 *
888
		 * @since 2.1.2
889
		 *
890
		 * @param array $styles Array of styles present on the page.
891
		 */
892
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
893
894
		?><script type="text/javascript">
895
			jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> );
896
			jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> );
897
		</script><?php
898
	}
899
900
	/**
901
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
902
	 *
903
	 * @global $wp_scripts
904
	 * @uses sanitize_text_field, add_query_arg
905
	 * @filter infinite_scroll_results
906
	 * @return array
907
	 */
908
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
909
		// Don't bother unless there are posts to display
910
		if ( 'success' != $results['type'] )
911
			return $results;
912
913
		// Parse and sanitize the script handles already output
914
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
915
916
		if ( is_array( $initial_scripts ) ) {
917
			global $wp_scripts;
918
919
			// Identify new scripts needed by the latest set of IS posts
920
			$new_scripts = array_diff( $wp_scripts->done, $initial_scripts );
921
922
			// If new scripts are needed, extract relevant data from $wp_scripts
923
			if ( ! empty( $new_scripts ) ) {
924
				$results['scripts'] = array();
925
926
				foreach ( $new_scripts as $handle ) {
927
					// Abort if somehow the handle doesn't correspond to a registered script
928
					if ( ! isset( $wp_scripts->registered[ $handle ] ) )
929
						continue;
930
931
					// Provide basic script data
932
					$script_data = array(
933
						'handle'     => $handle,
934
						'footer'     => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ),
935
						'extra_data' => $wp_scripts->print_extra_script( $handle, false )
936
					);
937
938
					// Base source
939
					$src = $wp_scripts->registered[ $handle ]->src;
940
941
					// Take base_url into account
942
					if ( strpos( $src, 'http' ) !== 0 )
943
						$src = $wp_scripts->base_url . $src;
944
945
					// Version and additional arguments
946 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
947
						$ver = '';
948
					else
949
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
950
951 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
952
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
953
954
					// Full script source with version info
955
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
956
957
					// Add script to data that will be returned to IS JS
958
					array_push( $results['scripts'], $script_data );
959
				}
960
			}
961
		}
962
963
		// Expose additional script data to filters, but only include in final `$results` array if needed.
964
		if ( ! isset( $results['scripts'] ) )
965
			$results['scripts'] = array();
966
967
		/**
968
		 * Filter the additional scripts required by the latest set of IS posts.
969
		 *
970
		 * @module infinite-scroll
971
		 *
972
		 * @since 2.1.2
973
		 *
974
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
975
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
976
		 * @param array $results Array of Infinite Scroll results.
977
		 * @param array $query_args Array of Query arguments.
978
		 * @param WP_Query $wp_query WP Query.
979
		 */
980
		$results['scripts'] = apply_filters(
981
			'infinite_scroll_additional_scripts',
982
			$results['scripts'],
983
			$initial_scripts,
984
			$results,
985
			$query_args,
986
			$wp_query
987
		);
988
989
		if ( empty( $results['scripts'] ) )
990
			unset( $results['scripts' ] );
991
992
		// Parse and sanitize the style handles already output
993
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
994
995
		if ( is_array( $initial_styles ) ) {
996
			global $wp_styles;
997
998
			// Identify new styles needed by the latest set of IS posts
999
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1000
1001
			// If new styles are needed, extract relevant data from $wp_styles
1002
			if ( ! empty( $new_styles ) ) {
1003
				$results['styles'] = array();
1004
1005
				foreach ( $new_styles as $handle ) {
1006
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1007
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1008
						continue;
1009
1010
					// Provide basic style data
1011
					$style_data = array(
1012
						'handle' => $handle,
1013
						'media'  => 'all'
1014
					);
1015
1016
					// Base source
1017
					$src = $wp_styles->registered[ $handle ]->src;
1018
1019
					// Take base_url into account
1020
					if ( strpos( $src, 'http' ) !== 0 )
1021
						$src = $wp_styles->base_url . $src;
1022
1023
					// Version and additional arguments
1024 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1025
						$ver = '';
1026
					else
1027
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1028
1029 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1030
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1031
1032
					// Full stylesheet source with version info
1033
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1034
1035
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1036
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1037
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1038
						$style_data['conditional'] = str_replace( array(
1039
							'lte',
1040
							'lt',
1041
							'gte',
1042
							'gt'
1043
						), array(
1044
							'%ver <=',
1045
							'%ver <',
1046
							'%ver >=',
1047
							'%ver >',
1048
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1049
1050
						// 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().
1051
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1052
1053
						// Lastly, remove the IE strings
1054
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1055
					}
1056
1057
					// Parse requested media context for stylesheet
1058 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1059
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1060
1061
					// Add stylesheet to data that will be returned to IS JS
1062
					array_push( $results['styles'], $style_data );
1063
				}
1064
			}
1065
		}
1066
1067
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1068
		if ( ! isset( $results['styles'] ) )
1069
			$results['styles'] = array();
1070
1071
		/**
1072
		 * Filter the additional styles required by the latest set of IS posts.
1073
		 *
1074
		 * @module infinite-scroll
1075
		 *
1076
		 * @since 2.1.2
1077
		 *
1078
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1079
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1080
		 * @param array $results Array of Infinite Scroll results.
1081
		 * @param array $query_args Array of Query arguments.
1082
		 * @param WP_Query $wp_query WP Query.
1083
		 */
1084
		$results['styles'] = apply_filters(
1085
			'infinite_scroll_additional_stylesheets',
1086
			$results['styles'],
1087
			$initial_styles,
1088
			$results,
1089
			$query_args,
1090
			$wp_query
1091
		);
1092
1093
		if ( empty( $results['styles'] ) )
1094
			unset( $results['styles' ] );
1095
1096
		// Lastly, return the IS results array
1097
		return $results;
1098
	}
1099
1100
	/**
1101
	 * Runs the query and returns the results via JSON.
1102
	 * Triggered by an AJAX request.
1103
	 *
1104
	 * @global $wp_query
1105
	 * @global $wp_the_query
1106
	 * @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
1107
	 * @return string or null
1108
	 */
1109
	function query() {
1110
		global $wp_customize;
1111
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1112
			die;
0 ignored issues
show
Coding Style Compatibility introduced by
The method query() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1113
1114
		$page = (int) $_REQUEST['page'];
1115
1116
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1117
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1118
			global $previousday;
1119
			$previousday = $_REQUEST['currentday'];
1120
		}
1121
1122
		$sticky = get_option( 'sticky_posts' );
1123
		$post__not_in = self::wp_query()->get( 'post__not_in' );
1124
1125
		//we have to take post__not_in args into consideration here not only sticky posts
1126
		if ( true === isset( $_REQUEST['query_args']['post__not_in'] ) ) {
1127
			$post__not_in = array_merge( $post__not_in, array_map( 'intval', (array) $_REQUEST['query_args']['post__not_in'] ) );
1128
		}
1129
1130
		if ( ! empty( $post__not_in ) )
1131
			$sticky = array_unique( array_merge( $sticky, $post__not_in ) );
1132
1133
		$post_status = array( 'publish' );
1134
		if ( current_user_can( 'read_private_posts' ) )
1135
			array_push( $post_status, 'private' );
1136
1137
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1138
1139
		$query_args = array_merge( self::wp_query()->query_vars, array(
1140
			'paged'          => $page,
1141
			'post_status'    => $post_status,
1142
			'posts_per_page' => self::get_settings()->posts_per_page,
1143
			'post__not_in'   => ( array ) $sticky,
1144
			'order'          => $order
1145
		) );
1146
1147
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1148
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1149
			unset( $query_args['s'] );
1150
		}
1151
1152
		// By default, don't query for a specific page of a paged post object.
1153
		// This argument can come from merging self::wp_query() into $query_args above.
1154
		// Since IS is only used on archives, we should always display the first page of any paged content.
1155
		unset( $query_args['page'] );
1156
1157
		/**
1158
		 * Filter the array of main query arguments.
1159
		 *
1160
		 * @module infinite-scroll
1161
		 *
1162
		 * @since 2.0.1
1163
		 *
1164
		 * @param array $query_args Array of Query arguments.
1165
		 */
1166
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1167
1168
		// Add query filter that checks for posts below the date
1169
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1170
1171
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = new WP_Query( $query_args );
1172
1173
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1174
1175
		$results = array();
1176
1177
		if ( have_posts() ) {
1178
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1179
			ob_start();
1180
			wp_head();
1181
			while ( ob_get_length() ) {
1182
				ob_end_clean();
1183
			}
1184
1185
			$results['type'] = 'success';
1186
1187
			// First, try theme's specified rendering handler, either specified via `add_theme_support` or by hooking to this action directly.
1188
			ob_start();
1189
			/**
1190
			 * Fires when rendering Infinite Scroll posts.
1191
			 *
1192
			 * @module infinite-scroll
1193
			 *
1194
			 * @since 2.0.0
1195
			 */
1196
			do_action( 'infinite_scroll_render' );
1197
			$results['html'] = ob_get_clean();
1198
1199
			// Fall back if a theme doesn't specify a rendering function. Because themes may hook additional functions to the `infinite_scroll_render` action, `has_action()` is ineffective here.
1200
			if ( empty( $results['html'] ) ) {
1201
				add_action( 'infinite_scroll_render', array( $this, 'render' ) );
1202
				rewind_posts();
1203
1204
				ob_start();
1205
				/** This action is already documented in modules/infinite-scroll/infinity.php */
1206
				do_action( 'infinite_scroll_render' );
1207
				$results['html'] = ob_get_clean();
1208
			}
1209
1210
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1211
			if ( empty( $results['html'] ) ) {
1212
				unset( $results['html'] );
1213
				/**
1214
				 * Fires when Infinite Scoll doesn't render any posts.
1215
				 *
1216
				 * @module infinite-scroll
1217
				 *
1218
				 * @since 2.0.0
1219
				 */
1220
				do_action( 'infinite_scroll_empty' );
1221
				$results['type'] = 'empty';
1222
			} elseif ( $this->has_wrapper() ) {
1223
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1224
				$wrapper_classes .= ' infinite-view-' . $page;
1225
				$wrapper_classes = trim( $wrapper_classes );
1226
1227
				$results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
1228
			}
1229
1230
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1231
			ob_start();
1232
			wp_footer();
1233
			while ( ob_get_length() ) {
1234
				ob_end_clean();
1235
			}
1236
1237
			if ( 'success' == $results['type'] ) {
1238
				global $currentday;
1239
				$results['lastbatch'] = self::is_last_batch();
1240
				$results['currentday'] = $currentday;
1241
			}
1242
1243
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1244
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1245
				global $jetpack_sharing_counts;
1246
1247
				while( have_posts() ) {
1248
					the_post();
1249
1250
					sharing_register_post_for_share_counts( get_the_ID() );
1251
				}
1252
1253
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1254
			}
1255
		} else {
1256
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1257
			do_action( 'infinite_scroll_empty' );
1258
			$results['type'] = 'empty';
1259
		}
1260
1261
		if ( is_customize_preview() ) {
1262
			$wp_customize->remove_preview_signature();
1263
		}
1264
1265
		wp_send_json(
1266
			/**
1267
			 * Filter the Infinite Scroll results.
1268
			 *
1269
			 * @module infinite-scroll
1270
			 *
1271
			 * @since 2.0.0
1272
			 *
1273
			 * @param array $results Array of Infinite Scroll results.
1274
			 * @param array $query_args Array of main query arguments.
1275
			 * @param WP_Query $wp_query WP Query.
1276
			 */
1277
			apply_filters( 'infinite_scroll_results', $results, $query_args, self::wp_query() )
1278
		);
1279
	}
1280
1281
	/**
1282
	 * Update the $allowed_vars array with the standard WP public and private
1283
	 * query vars, as well as taxonomy vars
1284
	 *
1285
	 * @global $wp
1286
	 * @param array $allowed_vars
1287
	 * @filter infinite_scroll_allowed_vars
1288
	 * @return array
1289
	 */
1290
	function allowed_query_vars( $allowed_vars ) {
1291
		global $wp;
1292
1293
		$allowed_vars += $wp->public_query_vars;
1294
		$allowed_vars += $wp->private_query_vars;
1295
		$allowed_vars += $this->get_taxonomy_vars();
1296
1297
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1298
			unset( $allowed_vars[ $key ] );
1299
		}
1300
1301
		return array_unique( $allowed_vars );
1302
	}
1303
1304
	/**
1305
	 * Returns an array of stock and custom taxonomy query vars
1306
	 *
1307
	 * @global $wp_taxonomies
1308
	 * @return array
1309
	 */
1310
	function get_taxonomy_vars() {
1311
		global $wp_taxonomies;
1312
1313
		$taxonomy_vars = array();
1314
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1315
			if ( $t->query_var )
1316
				$taxonomy_vars[] = $t->query_var;
1317
		}
1318
1319
		// still needed?
1320
		$taxonomy_vars[] = 'tag_id';
1321
1322
		return $taxonomy_vars;
1323
	}
1324
1325
	/**
1326
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1327
	 *
1328
	 * @param array $query_args
1329
	 * @filter infinite_scroll_query_args
1330
	 * @return array
1331
	 */
1332
	function inject_query_args( $query_args ) {
1333
		/**
1334
		 * Filter the array of allowed Infinite Scroll query arguments.
1335
		 *
1336
		 * @module infinite-scroll
1337
		 *
1338
		 * @since 2.6.0
1339
		 *
1340
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1341
		 * @param array $query_args Array of query arguments.
1342
		 */
1343
		$allowed_vars = apply_filters( 'infinite_scroll_allowed_vars', array(), $query_args );
1344
1345
		$query_args = array_merge( $query_args, array(
1346
			'suppress_filters' => false,
1347
		) );
1348
1349
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1350
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1351
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1352
					$query_args[ $var ] = $value;
1353
			}
1354
		}
1355
1356
		return $query_args;
1357
	}
1358
1359
	/**
1360
	 * Rendering fallback used when themes don't specify their own handler.
1361
	 *
1362
	 * @uses have_posts, the_post, get_template_part, get_post_format
1363
	 * @action infinite_scroll_render
1364
	 * @return string
1365
	 */
1366
	function render() {
1367
		while ( have_posts() ) {
1368
			the_post();
1369
1370
			get_template_part( 'content', get_post_format() );
1371
		}
1372
	}
1373
1374
	/**
1375
	 * Allow plugins to filter what archives Infinite Scroll supports
1376
	 *
1377
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1378
	 * @return bool
1379
	 */
1380
	public static function archive_supports_infinity() {
1381
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1382
1383
		// Disable when previewing a non-active theme in the customizer
1384
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1385
			return false;
1386
		}
1387
1388
		/**
1389
		 * Allow plugins to filter what archives Infinite Scroll supports.
1390
		 *
1391
		 * @module infinite-scroll
1392
		 *
1393
		 * @since 2.0.0
1394
		 *
1395
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1396
		 * @param object self::get_settings() IS settings provided by theme.
1397
		 */
1398
		return (bool) apply_filters( 'infinite_scroll_archive_supported', $supported, self::get_settings() );
1399
	}
1400
1401
	/**
1402
	 * The Infinite Blog Footer
1403
	 *
1404
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1405
	 * @return string or null
1406
	 */
1407
	function footer() {
1408
		// Bail if theme requested footer not show
1409
		if ( false == self::get_settings()->footer )
1410
			return;
1411
1412
		// We only need the new footer for the 'scroll' type
1413
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1414
			return;
1415
1416
		// Display a footer, either user-specified or a default
1417
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1418
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1419
		else
1420
			self::default_footer();
1421
	}
1422
1423
	/**
1424
	 * Render default IS footer
1425
	 *
1426
	 * @uses __, wp_get_theme, get_current_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1427
	 * @return string
1428
	 */
1429
	private function default_footer() {
1430
		$credits = sprintf(
1431
			'<a href="http://wordpress.org/" target="_blank" rel="generator">%1$s</a> ',
1432
			__( 'Proudly powered by WordPress', 'jetpack' )
1433
		);
1434
		$credits .= sprintf(
1435
			__( 'Theme: %1$s.', 'jetpack' ),
1436
			function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme()
1437
		);
1438
		/**
1439
		 * Filter Infinite Scroll's credit text.
1440
		 *
1441
		 * @module infinite-scroll
1442
		 *
1443
		 * @since 2.0.0
1444
		 *
1445
		 * @param string $credits Infinite Scroll credits.
1446
		 */
1447
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1448
1449
		?>
1450
		<div id="infinite-footer">
1451
			<div class="container">
1452
				<div class="blog-info">
1453
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" target="_blank" rel="home">
1454
						<?php bloginfo( 'name' ); ?>
1455
					</a>
1456
				</div>
1457
				<div class="blog-credits">
1458
					<?php echo $credits; ?>
1459
				</div>
1460
			</div>
1461
		</div><!-- #infinite-footer -->
1462
		<?php
1463
	}
1464
1465
	/**
1466
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1467
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1468
	 *
1469
	 * @param string $url
1470
	 * @uses remove_query_arg
1471
	 * @filter grunion_contact_form_redirect_url
1472
	 * @return string
1473
	 */
1474
	public function filter_grunion_redirect_url( $url ) {
1475
		// Remove IS query args, if present
1476
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1477
			$url = remove_query_arg( array(
1478
				'infinity',
1479
				'action',
1480
				'page',
1481
				'order',
1482
				'scripts',
1483
				'styles'
1484
			), $url );
1485
		}
1486
1487
		return $url;
1488
	}
1489
};
1490
1491
/**
1492
 * Initialize The_Neverending_Home_Page
1493
 */
1494
function the_neverending_home_page_init() {
1495
	if ( ! current_theme_supports( 'infinite-scroll' ) )
1496
		return;
1497
1498
	new The_Neverending_Home_Page;
1499
}
1500
add_action( 'init', 'the_neverending_home_page_init', 20 );
1501
1502
/**
1503
 * Check whether the current theme is infinite-scroll aware.
1504
 * If so, include the files which add theme support.
1505
 */
1506
function the_neverending_home_page_theme_support() {
1507
	$theme_name = get_stylesheet();
1508
1509
	/**
1510
	 * Filter the path to the Infinite Scroll compatibility file.
1511
	 *
1512
	 * @module infinite-scroll
1513
	 *
1514
	 * @since 2.0.0
1515
	 *
1516
	 * @param string $str IS compatibility file path.
1517
	 * @param string $theme_name Theme name.
1518
	 */
1519
	$customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name );
1520
1521
	if ( is_readable( $customization_file ) )
1522
		require_once( $customization_file );
1523
}
1524
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
1525
1526
/**
1527
 * Early accommodation of the Infinite Scroll AJAX request
1528
 */
1529
if ( The_Neverending_Home_Page::got_infinity() ) {
1530
	/**
1531
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
1532
	 * indicate it as early as possible for actions like init
1533
	 */
1534
	if ( ! defined( 'DOING_AJAX' ) &&
1535
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
1536
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
1537
	) {
1538
		define( 'DOING_AJAX', true );
1539
	}
1540
1541
	// Don't load the admin bar when doing the AJAX response.
1542
	show_admin_bar( false );
1543
}
1544