Completed
Push — master-stable ( 2f6b36...784ad3 )
by
unknown
21:53 queued 13:33
created

The_Neverending_Home_Page::got_infinity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 12
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 already 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
		$post_type = get_post_type();
304
		$entries = wp_count_posts( empty( $post_type ) ? 'post' : $post_type )->publish;
305
		if ( self::wp_query()->get( 'paged' ) && self::wp_query()->get( 'paged' ) > 1 ) {
306
			$entries -= self::get_settings()->posts_per_page * self::wp_query()->get( 'paged' );
307
		}
308
		return $entries <= self::get_settings()->posts_per_page;
309
	}
310
311
	/**
312
	 * The more tag will be ignored by default if the blog page isn't our homepage.
313
	 * Let's force the $more global to false.
314
	 */
315
	function preserve_more_tag( $array ) {
316
		global $more;
317
318
		if ( self::got_infinity() )
319
			$more = 0; //0 = show content up to the more tag. Add more link.
320
321
		return $array;
322
	}
323
324
	/**
325
	 * Add a checkbox field to Settings > Reading
326
	 * for enabling infinite scroll.
327
	 *
328
	 * Only show if the current theme supports infinity.
329
	 *
330
	 * @uses current_theme_supports, add_settings_field, __, register_setting
331
	 * @action admin_init
332
	 * @return null
333
	 */
334
	function settings_api_init() {
335
		if ( ! current_theme_supports( 'infinite-scroll' ) )
336
			return;
337
338
		// Add the setting field [infinite_scroll] and place it in Settings > Reading
339
		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' );
340
		register_setting( 'reading', self::$option_name_enabled, 'esc_attr' );
341
	}
342
343
	/**
344
	 * HTML code to display a checkbox true/false option
345
	 * for the infinite_scroll setting.
346
	 */
347
	function infinite_setting_html() {
348
		$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>';
349
350
		// If the blog has footer widgets, show a notice instead of the checkbox
351
		if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) {
352
			echo '<label>' . $notice . '</label>';
353
		} else {
354
			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>';
355
			echo '<p class="description">' . esc_html( sprintf( _n( 'Shows %s post on each load.', 'Shows %s posts on each load.', self::get_settings()->posts_per_page, 'jetpack' ), number_format_i18n( self::get_settings()->posts_per_page ) ) ) . '</p>';
356
		}
357
	}
358
359
	/**
360
	 * Does the legwork to determine whether the feature is enabled.
361
	 *
362
	 * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action
363
	 * @action template_redirect
364
	 * @return null
365
	 */
366
	function action_template_redirect() {
367
		// Check that we support infinite scroll, and are on the home page.
368
		if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
369
			return;
370
371
		$id = self::get_settings()->container;
372
373
		// Check that we have an id.
374
		if ( empty( $id ) )
375
			return;
376
377
		// Add our scripts.
378
		wp_register_script( 'the-neverending-homepage', plugins_url( 'infinity.js', __FILE__ ), array( 'jquery' ), '4.0.0', true );
379
380
		// Add our default styles.
381
		wp_register_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20140422' );
382
383
		// Make sure there are enough posts for IS
384
		if ( self::is_last_batch() ) {
385
			return;
386
		}
387
388
		// Add a class to the body.
389
		add_filter( 'body_class', array( $this, 'body_class' ) );
390
391
		// Add our scripts.
392
		wp_enqueue_script( 'the-neverending-homepage' );
393
394
		// Add our default styles.
395
		wp_enqueue_style( 'the-neverending-homepage' );
396
397
		add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_spinner_scripts' ) );
398
399
		add_action( 'wp_footer', array( $this, 'action_wp_footer_settings' ), 2 );
400
401
		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
402
403
		add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ), 10, 3 );
404
	}
405
406
	/**
407
	 * Enqueue spinner scripts.
408
	 */
409
	function enqueue_spinner_scripts() {
410
		wp_enqueue_script( 'jquery.spin' );
411
	}
412
413
	/**
414
	 * Adds an 'infinite-scroll' class to the body.
415
	 */
416
	function body_class( $classes ) {
417
		// Do not add infinity-scroll class if disabled through the Reading page
418
		$disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
419
		if ( ! $disabled || 'click' == self::get_settings()->type ) {
420
			$classes[] = 'infinite-scroll';
421
422
			if ( 'scroll' == self::get_settings()->type )
423
				$classes[] = 'neverending';
424
		}
425
426
		return $classes;
427
	}
428
429
	/**
430
	 * 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
431
	 *
432
	 * @uses self::wp_query
433
	 * @uses self::get_last_post_date
434
	 * @uses self::has_only_title_matching_posts
435
	 * @return array
436
	 */
437
	function get_excluded_posts() {
438
439
		$excluded_posts = array();
440
		//loop through posts returned by wp_query call
441
		foreach( self::wp_query()->get_posts() as $post ) {
442
443
			$orderby = isset( self::wp_query()->query_vars['orderby'] ) ? self::wp_query()->query_vars['orderby'] : '';
444
			$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
445
			if ( 'modified' === $orderby || false === $post_date ) {
446
				$post_date = $post->post_modified;
447
			}
448
449
			//in case all posts initially displayed match the keyword by title we add em all to excluded posts array
450
			//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
451
			if ( self::has_only_title_matching_posts() || $post_date <= self::get_last_post_date() ) {
452
				array_push( $excluded_posts, $post->ID );
453
			}
454
		}
455
		return $excluded_posts;
456
	}
457
458
	/**
459
	 * 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
460
	 *
461
	 * @uses self::wp_query
462
	 * @uses self::get_excluded_posts
463
	 * @return array
464
	 */
465
	function get_query_vars() {
466
467
		$query_vars = self::wp_query()->query_vars;
468
		//applies to search page only
469
		if ( true === self::wp_query()->is_search() ) {
470
			//set post__not_in array in query_vars in case it does not exists
471
			if ( false === isset( $query_vars['post__not_in'] ) ) {
472
				$query_vars['post__not_in'] = array();
473
			}
474
			//get excluded posts
475
			$excluded = self::get_excluded_posts();
476
			//merge them with other post__not_in posts (eg.: sticky posts)
477
			$query_vars['post__not_in'] = array_merge( $query_vars['post__not_in'], $excluded );
478
		}
479
		return $query_vars;
480
	}
481
482
	/**
483
	 * This function checks whether all posts returned by initial wp_query match the keyword by title
484
	 * The code used in this function is borrowed from WP_Query class where it is used to construct like conditions for keywords
485
	 *
486
	 * @uses self::wp_query
487
	 * @return bool
488
	 */
489
	function has_only_title_matching_posts() {
490
491
		//apply following logic for search page results only
492
		if ( false === self::wp_query()->is_search() ) {
493
			return false;
494
		}
495
496
		//grab the last posts in the stack as if the last one is title-matching the rest is title-matching as well
497
		$post = end( self::wp_query()->posts );
498
499
		//code inspired by WP_Query class
500
		if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', self::wp_query()->get( 's' ), $matches ) ) {
501
			$search_terms = self::wp_query()->query_vars['search_terms'];
502
			// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
503
			if ( empty( $search_terms ) || count( $search_terms ) > 9 ) {
504
				$search_terms = array( self::wp_query()->get( 's' ) );
505
			}
506
		} else {
507
			$search_terms = array( self::wp_query()->get( 's' ) );
508
		}
509
510
		//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
511
		$term = current( $search_terms );
512
		if ( ! empty( $term ) && false !== strpos( $post->post_title, $term ) ) {
513
			return true;
514
		}
515
516
		return false;
517
	}
518
519
	/**
520
	 * Grab the timestamp for the initial query's last post.
521
	 *
522
	 * This takes into account the query's 'orderby' parameter and returns
523
	 * false if the posts are not ordered by date.
524
	 *
525
	 * @uses self::got_infinity
526
	 * @uses self::has_only_title_matching_posts
527
	 * @uses self::wp_query
528
	 * @return string 'Y-m-d H:i:s' or false
529
	 */
530
	function get_last_post_date() {
531
		if ( self::got_infinity() )
532
			return;
533
534
		if ( ! self::wp_query()->have_posts() ) {
535
			return null;
536
		}
537
538
		//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
539
		if ( true === self::has_only_title_matching_posts() ) {
540
			return false;
541
		}
542
543
		$post = end( self::wp_query()->posts );
544
		$orderby = isset( self::wp_query()->query_vars['orderby'] ) ?
545
			self::wp_query()->query_vars['orderby'] : '';
546
		$post_date = ( ! empty( $post->post_date ) ? $post->post_date : false );
547 View Code Duplication
		switch ( $orderby ) {
548
			case 'modified':
549
				return $post->post_modified;
550
			case 'date':
551
			case '':
552
				return $post_date;
553
			default:
554
				return false;
555
		}
556
	}
557
558
	/**
559
	 * Returns the appropriate `wp_posts` table field for a given query's
560
	 * 'orderby' parameter, if applicable.
561
	 *
562
	 * @param optional object $query
563
	 * @uses self::wp_query
564
	 * @return string or false
565
	 */
566
	function get_query_sort_field( $query = null ) {
567
		if ( empty( $query ) )
568
			$query = self::wp_query();
569
570
		$orderby = isset( $query->query_vars['orderby'] ) ? $query->query_vars['orderby'] : '';
571
572 View Code Duplication
		switch ( $orderby ) {
573
			case 'modified':
574
				return 'post_modified';
575
			case 'date':
576
			case '':
577
				return 'post_date';
578
			default:
579
				return false;
580
		}
581
	}
582
583
	/**
584
	 * Create a where clause that will make sure post queries
585
	 * will always return results prior to (descending sort)
586
	 * or before (ascending sort) the last post date.
587
	 *
588
	 * @global $wpdb
589
	 * @param string $where
590
	 * @param object $query
591
	 * @uses apply_filters
592
	 * @filter posts_where
593
	 * @return string
594
	 */
595
	function query_time_filter( $where, $query ) {
596
		if ( self::got_infinity() ) {
597
			global $wpdb;
598
599
			$sort_field = self::get_query_sort_field( $query );
600
			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...
601
				return $where;
602
603
			$last_post_date = $_REQUEST['last_post_date'];
604
			// Sanitize timestamp
605
			if ( empty( $last_post_date ) || !preg_match( '|\d{4}\-\d{2}\-\d{2}|', $last_post_date ) )
606
				return $where;
607
608
			$operator = 'ASC' == $_REQUEST['query_args']['order'] ? '>' : '<';
609
610
			// Construct the date query using our timestamp
611
			$clause = $wpdb->prepare( " AND {$wpdb->posts}.{$sort_field} {$operator} %s", $last_post_date );
612
613
			/**
614
			 * Filter Infinite Scroll's SQL date query making sure post queries
615
			 * will always return results prior to (descending sort)
616
			 * or before (ascending sort) the last post date.
617
			 *
618
			 * @module infinite-scroll
619
			 *
620
			 * @param string $clause SQL Date query.
621
			 * @param object $query Query.
622
			 * @param string $operator Query operator.
623
			 * @param string $last_post_date Last Post Date timestamp.
624
			 */
625
			$where .= apply_filters( 'infinite_scroll_posts_where', $clause, $query, $operator, $last_post_date );
626
		}
627
628
		return $where;
629
	}
630
631
	/**
632
	 * Let's overwrite the default post_per_page setting to always display a fixed amount.
633
	 *
634
	 * @param object $query
635
	 * @uses is_admin, self::archive_supports_infinity, self::get_settings
636
	 * @return null
637
	 */
638
	function posts_per_page_query( $query ) {
639
		if ( ! is_admin() && self::archive_supports_infinity() && $query->is_main_query() )
640
			$query->set( 'posts_per_page', self::get_settings()->posts_per_page );
641
	}
642
643
	/**
644
	 * Check if the IS output should be wrapped in a div.
645
	 * Setting value can be a boolean or a string specifying the class applied to the div.
646
	 *
647
	 * @uses self::get_settings
648
	 * @return bool
649
	 */
650
	function has_wrapper() {
651
		return (bool) self::get_settings()->wrapper;
652
	}
653
654
	/**
655
	 * Returns the Ajax url
656
	 *
657
	 * @global $wp
658
	 * @uses home_url, add_query_arg, apply_filters
659
	 * @return string
660
	 */
661
	function ajax_url() {
662
		$base_url = set_url_scheme( home_url( '/' ) );
663
664
		$ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url );
665
666
		/**
667
		 * Filter the Infinite Scroll Ajax URL.
668
		 *
669
		 * @module infinite-scroll
670
		 *
671
		 * @since 2.0.0
672
		 *
673
		 * @param string $ajaxurl Infinite Scroll Ajax URL.
674
		 */
675
		return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl );
676
	}
677
678
	/**
679
	 * Our own Ajax response, avoiding calling admin-ajax
680
	 */
681
	function ajax_response() {
682
		// Only proceed if the url query has a key of "Infinity"
683
		if ( ! self::got_infinity() )
684
			return false;
685
686
		// This should already be defined below, but make sure.
687
		if ( ! defined( 'DOING_AJAX' ) ) {
688
			define( 'DOING_AJAX', true );
689
		}
690
691
		@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...
692
		send_nosniff_header();
693
694
		/**
695
		 * Fires at the end of the Infinite Scroll Ajax response.
696
		 *
697
		 * @module infinite-scroll
698
		 *
699
		 * @since 2.0.0
700
		 */
701
		do_action( 'custom_ajax_infinite_scroll' );
702
		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...
703
	}
704
705
	/**
706
	 * Alias for renamed class method.
707
	 *
708
	 * Previously, JS settings object was unnecessarily output in the document head.
709
	 * When the hook was changed, the method name no longer made sense.
710
	 */
711
	function action_wp_head() {
712
		$this->action_wp_footer_settings();
713
	}
714
715
	/**
716
	 * Prints the relevant infinite scroll settings in JS.
717
	 *
718
	 * @global $wp_rewrite
719
	 * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action, self::get_query_vars
720
	 * @action wp_footer
721
	 * @return string
722
	 */
723
	function action_wp_footer_settings() {
724
		global $wp_rewrite;
725
		global $currentday;
726
727
		// Default click handle text
728
		$click_handle_text = __( 'Older posts', 'jetpack' );
729
730
		// If a single CPT is displayed, use its plural name instead of "posts"
731
		// Could be empty (posts) or an array of multiple post types.
732
		// In the latter two cases cases, the default text is used, leaving the `infinite_scroll_js_settings` filter for further customization.
733
		$post_type = self::wp_query()->get( 'post_type' );
734
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
735
			$post_type = get_post_type_object( $post_type );
736
737
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
738
				if ( isset( $post_type->labels->name ) ) {
739
					$cpt_text = $post_type->labels->name;
740
				} elseif ( isset( $post_type->label ) ) {
741
					$cpt_text = $post_type->label;
742
				}
743
744
				if ( isset( $cpt_text ) ) {
745
					/* translators: %s is the name of a custom post type */
746
					$click_handle_text = sprintf( __( 'Older %s', 'jetpack' ), $cpt_text );
747
					unset( $cpt_text );
748
				}
749
			}
750
		}
751
752
		unset( $post_type );
753
754
		// Base JS settings
755
		$js_settings = array(
756
			'id'               => self::get_settings()->container,
757
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
758
			'type'             => esc_js( self::get_settings()->type ),
759
			'wrapper'          => self::has_wrapper(),
760
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
761
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
762
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
763
			'text'             => esc_js( $click_handle_text ),
764
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
765
			'currentday'       => $currentday,
766
			'order'            => 'DESC',
767
			'scripts'          => array(),
768
			'styles'           => array(),
769
			'google_analytics' => false,
770
			'offset'           => self::wp_query()->get( 'paged' ),
771
			'history'          => array(
772
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( esc_url( get_home_url() ) ) ),
773
				'path'                 => self::get_request_path(),
774
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
775
				'parameters'           => self::get_request_parameters(),
776
			),
777
			'query_args'      => self::get_query_vars(),
778
			'last_post_date'  => self::get_last_post_date(),
779
		);
780
781
		// Optional order param
782
		if ( isset( $_REQUEST['order'] ) ) {
783
			$order = strtoupper( $_REQUEST['order'] );
784
785
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
786
				$js_settings['order'] = $order;
787
		}
788
789
		/**
790
		 * Filter the Infinite Scroll JS settings outputted in the head.
791
		 *
792
		 * @module infinite-scroll
793
		 *
794
		 * @since 2.0.0
795
		 *
796
		 * @param array $js_settings Infinite Scroll JS settings.
797
		 */
798
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
799
800
		/**
801
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
802
		 *
803
		 * @module infinite-scroll
804
		 *
805
		 * @since 2.0.0
806
		 */
807
		do_action( 'infinite_scroll_wp_head' );
808
809
		?>
810
		<script type="text/javascript">
811
		//<![CDATA[
812
		var infiniteScroll = <?php echo json_encode( array( 'settings' => $js_settings ) ); ?>;
813
		//]]>
814
		</script>
815
		<?php
816
	}
817
818
	/**
819
	 * Build path data for current request.
820
	 * Used for Google Analytics and pushState history tracking.
821
	 *
822
	 * @global $wp_rewrite
823
	 * @global $wp
824
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
825
	 * @return string|bool
826
	 */
827
	private function get_request_path() {
828
		global $wp_rewrite;
829
830
		if ( $wp_rewrite->using_permalinks() ) {
831
			global $wp;
832
833
			// If called too early, bail
834
			if ( ! isset( $wp->request ) )
835
				return false;
836
837
			// Determine path for paginated version of current request
838
			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...
839
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
840
			else
841
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
842
843
			// Slashes everywhere we need them
844
			if ( 0 !== strpos( $path, '/' ) )
845
				$path = '/' . $path;
846
847
			$path = user_trailingslashit( $path );
848
		} else {
849
			// Clean up raw $_REQUEST input
850
			$path = array_map( 'sanitize_text_field', $_REQUEST );
851
			$path = array_filter( $path );
852
853
			$path['paged'] = '%d';
854
855
			$path = add_query_arg( $path, '/' );
856
		}
857
858
		return empty( $path ) ? false : $path;
859
	}
860
861
	/**
862
	 * Return query string for current request, prefixed with '?'.
863
	 *
864
	 * @return string
865
	 */
866
	private function get_request_parameters() {
867
		$uri = $_SERVER[ 'REQUEST_URI' ];
868
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
869
		if ( $count != 1 )
870
			return '';
871
		return $uri;
872
	}
873
874
	/**
875
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
876
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
877
	 *
878
	 * @global $wp_scripts, $wp_styles
879
	 * @action wp_footer
880
	 * @return string
881
	 */
882
	function action_wp_footer() {
883
		global $wp_scripts, $wp_styles;
884
885
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
886
		/**
887
		 * Filter the list of scripts already present on the page.
888
		 *
889
		 * @module infinite-scroll
890
		 *
891
		 * @since 2.1.2
892
		 *
893
		 * @param array $scripts Array of scripts present on the page.
894
		 */
895
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
896
897
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
898
		/**
899
		 * Filter the list of styles already present on the page.
900
		 *
901
		 * @module infinite-scroll
902
		 *
903
		 * @since 2.1.2
904
		 *
905
		 * @param array $styles Array of styles present on the page.
906
		 */
907
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
908
909
		?><script type="text/javascript">
910
			jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> );
911
			jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> );
912
		</script><?php
913
	}
914
915
	/**
916
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
917
	 *
918
	 * @global $wp_scripts
919
	 * @uses sanitize_text_field, add_query_arg
920
	 * @filter infinite_scroll_results
921
	 * @return array
922
	 */
923
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
924
		// Don't bother unless there are posts to display
925
		if ( 'success' != $results['type'] )
926
			return $results;
927
928
		// Parse and sanitize the script handles already output
929
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
930
931
		if ( is_array( $initial_scripts ) ) {
932
			global $wp_scripts;
933
934
			// Identify new scripts needed by the latest set of IS posts
935
			$new_scripts = array_diff( $wp_scripts->done, $initial_scripts );
936
937
			// If new scripts are needed, extract relevant data from $wp_scripts
938
			if ( ! empty( $new_scripts ) ) {
939
				$results['scripts'] = array();
940
941
				foreach ( $new_scripts as $handle ) {
942
					// Abort if somehow the handle doesn't correspond to a registered script
943
					if ( ! isset( $wp_scripts->registered[ $handle ] ) )
944
						continue;
945
946
					// Provide basic script data
947
					$script_data = array(
948
						'handle'     => $handle,
949
						'footer'     => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ),
950
						'extra_data' => $wp_scripts->print_extra_script( $handle, false )
951
					);
952
953
					// Base source
954
					$src = $wp_scripts->registered[ $handle ]->src;
955
956
					// Take base_url into account
957
					if ( strpos( $src, 'http' ) !== 0 )
958
						$src = $wp_scripts->base_url . $src;
959
960
					// Version and additional arguments
961 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
962
						$ver = '';
963
					else
964
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
965
966 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
967
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
968
969
					// Full script source with version info
970
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
971
972
					// Add script to data that will be returned to IS JS
973
					array_push( $results['scripts'], $script_data );
974
				}
975
			}
976
		}
977
978
		// Expose additional script data to filters, but only include in final `$results` array if needed.
979
		if ( ! isset( $results['scripts'] ) )
980
			$results['scripts'] = array();
981
982
		/**
983
		 * Filter the additional scripts required by the latest set of IS posts.
984
		 *
985
		 * @module infinite-scroll
986
		 *
987
		 * @since 2.1.2
988
		 *
989
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
990
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
991
		 * @param array $results Array of Infinite Scroll results.
992
		 * @param array $query_args Array of Query arguments.
993
		 * @param WP_Query $wp_query WP Query.
994
		 */
995
		$results['scripts'] = apply_filters(
996
			'infinite_scroll_additional_scripts',
997
			$results['scripts'],
998
			$initial_scripts,
999
			$results,
1000
			$query_args,
1001
			$wp_query
1002
		);
1003
1004
		if ( empty( $results['scripts'] ) )
1005
			unset( $results['scripts' ] );
1006
1007
		// Parse and sanitize the style handles already output
1008
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
1009
1010
		if ( is_array( $initial_styles ) ) {
1011
			global $wp_styles;
1012
1013
			// Identify new styles needed by the latest set of IS posts
1014
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1015
1016
			// If new styles are needed, extract relevant data from $wp_styles
1017
			if ( ! empty( $new_styles ) ) {
1018
				$results['styles'] = array();
1019
1020
				foreach ( $new_styles as $handle ) {
1021
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1022
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1023
						continue;
1024
1025
					// Provide basic style data
1026
					$style_data = array(
1027
						'handle' => $handle,
1028
						'media'  => 'all'
1029
					);
1030
1031
					// Base source
1032
					$src = $wp_styles->registered[ $handle ]->src;
1033
1034
					// Take base_url into account
1035
					if ( strpos( $src, 'http' ) !== 0 )
1036
						$src = $wp_styles->base_url . $src;
1037
1038
					// Version and additional arguments
1039 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1040
						$ver = '';
1041
					else
1042
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1043
1044 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1045
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1046
1047
					// Full stylesheet source with version info
1048
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1049
1050
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1051
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1052
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1053
						$style_data['conditional'] = str_replace( array(
1054
							'lte',
1055
							'lt',
1056
							'gte',
1057
							'gt'
1058
						), array(
1059
							'%ver <=',
1060
							'%ver <',
1061
							'%ver >=',
1062
							'%ver >',
1063
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1064
1065
						// 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().
1066
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1067
1068
						// Lastly, remove the IE strings
1069
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1070
					}
1071
1072
					// Parse requested media context for stylesheet
1073 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1074
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1075
1076
					// Add stylesheet to data that will be returned to IS JS
1077
					array_push( $results['styles'], $style_data );
1078
				}
1079
			}
1080
		}
1081
1082
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1083
		if ( ! isset( $results['styles'] ) )
1084
			$results['styles'] = array();
1085
1086
		/**
1087
		 * Filter the additional styles required by the latest set of IS posts.
1088
		 *
1089
		 * @module infinite-scroll
1090
		 *
1091
		 * @since 2.1.2
1092
		 *
1093
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1094
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1095
		 * @param array $results Array of Infinite Scroll results.
1096
		 * @param array $query_args Array of Query arguments.
1097
		 * @param WP_Query $wp_query WP Query.
1098
		 */
1099
		$results['styles'] = apply_filters(
1100
			'infinite_scroll_additional_stylesheets',
1101
			$results['styles'],
1102
			$initial_styles,
1103
			$results,
1104
			$query_args,
1105
			$wp_query
1106
		);
1107
1108
		if ( empty( $results['styles'] ) )
1109
			unset( $results['styles' ] );
1110
1111
		// Lastly, return the IS results array
1112
		return $results;
1113
	}
1114
1115
	/**
1116
	 * Runs the query and returns the results via JSON.
1117
	 * Triggered by an AJAX request.
1118
	 *
1119
	 * @global $wp_query
1120
	 * @global $wp_the_query
1121
	 * @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
1122
	 * @return string or null
1123
	 */
1124
	function query() {
1125
		global $wp_customize;
1126
		global $wp_version;
1127
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1128
			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...
1129
1130
		$page = (int) $_REQUEST['page'];
1131
1132
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1133
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1134
			global $previousday;
1135
			$previousday = $_REQUEST['currentday'];
1136
		}
1137
1138
		$sticky = get_option( 'sticky_posts' );
1139
		$post__not_in = self::wp_query()->get( 'post__not_in' );
1140
1141
		//we have to take post__not_in args into consideration here not only sticky posts
1142
		if ( true === isset( $_REQUEST['query_args']['post__not_in'] ) ) {
1143
			$post__not_in = array_merge( $post__not_in, array_map( 'intval', (array) $_REQUEST['query_args']['post__not_in'] ) );
1144
		}
1145
1146
		if ( ! empty( $post__not_in ) )
1147
			$sticky = array_unique( array_merge( $sticky, $post__not_in ) );
1148
1149
		$post_status = array( 'publish' );
1150
		if ( current_user_can( 'read_private_posts' ) )
1151
			array_push( $post_status, 'private' );
1152
1153
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1154
1155
		$query_args = array_merge( self::wp_query()->query_vars, array(
1156
			'paged'          => $page,
1157
			'post_status'    => $post_status,
1158
			'posts_per_page' => self::get_settings()->posts_per_page,
1159
			'post__not_in'   => ( array ) $sticky,
1160
			'order'          => $order
1161
		) );
1162
1163
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1164
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1165
			unset( $query_args['s'] );
1166
		}
1167
1168
		// By default, don't query for a specific page of a paged post object.
1169
		// This argument can come from merging self::wp_query() into $query_args above.
1170
		// Since IS is only used on archives, we should always display the first page of any paged content.
1171
		unset( $query_args['page'] );
1172
1173
		/**
1174
		 * Filter the array of main query arguments.
1175
		 *
1176
		 * @module infinite-scroll
1177
		 *
1178
		 * @since 2.0.1
1179
		 *
1180
		 * @param array $query_args Array of Query arguments.
1181
		 */
1182
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1183
1184
		// Add query filter that checks for posts below the date
1185
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1186
1187
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = new WP_Query( $query_args );
1188
1189
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1190
1191
		$results = array();
1192
1193
		if ( have_posts() ) {
1194
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1195
			ob_start();
1196
			wp_head();
1197
			while ( ob_get_length() ) {
1198
				ob_end_clean();
1199
			}
1200
1201
			$results['type'] = 'success';
1202
1203
			// First, try theme's specified rendering handler, either specified via `add_theme_support` or by hooking to this action directly.
1204
			ob_start();
1205
			/**
1206
			 * Fires when rendering Infinite Scroll posts.
1207
			 *
1208
			 * @module infinite-scroll
1209
			 *
1210
			 * @since 2.0.0
1211
			 */
1212
			do_action( 'infinite_scroll_render' );
1213
			$results['html'] = ob_get_clean();
1214
1215
			// 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.
1216
			if ( empty( $results['html'] ) ) {
1217
				add_action( 'infinite_scroll_render', array( $this, 'render' ) );
1218
				rewind_posts();
1219
1220
				ob_start();
1221
				/** This action is already documented in modules/infinite-scroll/infinity.php */
1222
				do_action( 'infinite_scroll_render' );
1223
				$results['html'] = ob_get_clean();
1224
			}
1225
1226
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1227
			if ( empty( $results['html'] ) ) {
1228
				unset( $results['html'] );
1229
				/**
1230
				 * Fires when Infinite Scoll doesn't render any posts.
1231
				 *
1232
				 * @module infinite-scroll
1233
				 *
1234
				 * @since 2.0.0
1235
				 */
1236
				do_action( 'infinite_scroll_empty' );
1237
				$results['type'] = 'empty';
1238
			} elseif ( $this->has_wrapper() ) {
1239
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1240
				$wrapper_classes .= ' infinite-view-' . $page;
1241
				$wrapper_classes = trim( $wrapper_classes );
1242
1243
				$results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
1244
			}
1245
1246
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1247
			ob_start();
1248
			wp_footer();
1249
			while ( ob_get_length() ) {
1250
				ob_end_clean();
1251
			}
1252
1253
			if ( 'success' == $results['type'] ) {
1254
				global $currentday;
1255
				$results['lastbatch'] = self::is_last_batch();
1256
				$results['currentday'] = $currentday;
1257
			}
1258
1259
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1260
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1261
				global $jetpack_sharing_counts;
1262
1263
				while( have_posts() ) {
1264
					the_post();
1265
1266
					sharing_register_post_for_share_counts( get_the_ID() );
1267
				}
1268
1269
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1270
			}
1271
		} else {
1272
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1273
			do_action( 'infinite_scroll_empty' );
1274
			$results['type'] = 'empty';
1275
		}
1276
1277
		// This should be removed when WordPress 4.8 is released.
1278
		if ( version_compare( $wp_version, '4.7', '<' ) && is_customize_preview() ) {
1279
			$wp_customize->remove_preview_signature();
1280
		}
1281
1282
		wp_send_json(
1283
			/**
1284
			 * Filter the Infinite Scroll results.
1285
			 *
1286
			 * @module infinite-scroll
1287
			 *
1288
			 * @since 2.0.0
1289
			 *
1290
			 * @param array $results Array of Infinite Scroll results.
1291
			 * @param array $query_args Array of main query arguments.
1292
			 * @param WP_Query $wp_query WP Query.
1293
			 */
1294
			apply_filters( 'infinite_scroll_results', $results, $query_args, self::wp_query() )
1295
		);
1296
	}
1297
1298
	/**
1299
	 * Update the $allowed_vars array with the standard WP public and private
1300
	 * query vars, as well as taxonomy vars
1301
	 *
1302
	 * @global $wp
1303
	 * @param array $allowed_vars
1304
	 * @filter infinite_scroll_allowed_vars
1305
	 * @return array
1306
	 */
1307
	function allowed_query_vars( $allowed_vars ) {
1308
		global $wp;
1309
1310
		$allowed_vars += $wp->public_query_vars;
1311
		$allowed_vars += $wp->private_query_vars;
1312
		$allowed_vars += $this->get_taxonomy_vars();
1313
1314
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1315
			unset( $allowed_vars[ $key ] );
1316
		}
1317
1318
		return array_unique( $allowed_vars );
1319
	}
1320
1321
	/**
1322
	 * Returns an array of stock and custom taxonomy query vars
1323
	 *
1324
	 * @global $wp_taxonomies
1325
	 * @return array
1326
	 */
1327
	function get_taxonomy_vars() {
1328
		global $wp_taxonomies;
1329
1330
		$taxonomy_vars = array();
1331
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1332
			if ( $t->query_var )
1333
				$taxonomy_vars[] = $t->query_var;
1334
		}
1335
1336
		// still needed?
1337
		$taxonomy_vars[] = 'tag_id';
1338
1339
		return $taxonomy_vars;
1340
	}
1341
1342
	/**
1343
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1344
	 *
1345
	 * @param array $query_args
1346
	 * @filter infinite_scroll_query_args
1347
	 * @return array
1348
	 */
1349
	function inject_query_args( $query_args ) {
1350
		/**
1351
		 * Filter the array of allowed Infinite Scroll query arguments.
1352
		 *
1353
		 * @module infinite-scroll
1354
		 *
1355
		 * @since 2.6.0
1356
		 *
1357
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1358
		 * @param array $query_args Array of query arguments.
1359
		 */
1360
		$allowed_vars = apply_filters( 'infinite_scroll_allowed_vars', array(), $query_args );
1361
1362
		$query_args = array_merge( $query_args, array(
1363
			'suppress_filters' => false,
1364
		) );
1365
1366
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1367
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1368
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1369
					$query_args[ $var ] = $value;
1370
			}
1371
		}
1372
1373
		return $query_args;
1374
	}
1375
1376
	/**
1377
	 * Rendering fallback used when themes don't specify their own handler.
1378
	 *
1379
	 * @uses have_posts, the_post, get_template_part, get_post_format
1380
	 * @action infinite_scroll_render
1381
	 * @return string
1382
	 */
1383
	function render() {
1384
		while ( have_posts() ) {
1385
			the_post();
1386
1387
			get_template_part( 'content', get_post_format() );
1388
		}
1389
	}
1390
1391
	/**
1392
	 * Allow plugins to filter what archives Infinite Scroll supports
1393
	 *
1394
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1395
	 * @return bool
1396
	 */
1397
	public static function archive_supports_infinity() {
1398
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1399
1400
		// Disable when previewing a non-active theme in the customizer
1401
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1402
			return false;
1403
		}
1404
1405
		/**
1406
		 * Allow plugins to filter what archives Infinite Scroll supports.
1407
		 *
1408
		 * @module infinite-scroll
1409
		 *
1410
		 * @since 2.0.0
1411
		 *
1412
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1413
		 * @param object self::get_settings() IS settings provided by theme.
1414
		 */
1415
		return (bool) apply_filters( 'infinite_scroll_archive_supported', $supported, self::get_settings() );
1416
	}
1417
1418
	/**
1419
	 * The Infinite Blog Footer
1420
	 *
1421
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1422
	 * @return string or null
1423
	 */
1424
	function footer() {
1425
		// Bail if theme requested footer not show
1426
		if ( false == self::get_settings()->footer )
1427
			return;
1428
1429
		// We only need the new footer for the 'scroll' type
1430
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1431
			return;
1432
1433
		// Display a footer, either user-specified or a default
1434
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1435
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1436
		else
1437
			self::default_footer();
1438
	}
1439
1440
	/**
1441
	 * Render default IS footer
1442
	 *
1443
	 * @uses __, wp_get_theme, get_current_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1444
	 * @return string
1445
	 */
1446
	private function default_footer() {
1447
		$credits = sprintf(
1448
			'<a href="https://wordpress.org/" target="_blank" rel="generator">%1$s</a> ',
1449
			__( 'Proudly powered by WordPress', 'jetpack' )
1450
		);
1451
		$credits .= sprintf(
1452
			/* translators: %1$s is the name of a theme */
1453
			__( 'Theme: %1$s.', 'jetpack' ),
1454
			function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme()
1455
		);
1456
		/**
1457
		 * Filter Infinite Scroll's credit text.
1458
		 *
1459
		 * @module infinite-scroll
1460
		 *
1461
		 * @since 2.0.0
1462
		 *
1463
		 * @param string $credits Infinite Scroll credits.
1464
		 */
1465
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1466
1467
		?>
1468
		<div id="infinite-footer">
1469
			<div class="container">
1470
				<div class="blog-info">
1471
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" rel="home">
1472
						<?php bloginfo( 'name' ); ?>
1473
					</a>
1474
				</div>
1475
				<div class="blog-credits">
1476
					<?php echo $credits; ?>
1477
				</div>
1478
			</div>
1479
		</div><!-- #infinite-footer -->
1480
		<?php
1481
	}
1482
1483
	/**
1484
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1485
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1486
	 *
1487
	 * @param string $url
1488
	 * @uses remove_query_arg
1489
	 * @filter grunion_contact_form_redirect_url
1490
	 * @return string
1491
	 */
1492
	public function filter_grunion_redirect_url( $url ) {
1493
		// Remove IS query args, if present
1494
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1495
			$url = remove_query_arg( array(
1496
				'infinity',
1497
				'action',
1498
				'page',
1499
				'order',
1500
				'scripts',
1501
				'styles'
1502
			), $url );
1503
		}
1504
1505
		return $url;
1506
	}
1507
};
1508
1509
/**
1510
 * Initialize The_Neverending_Home_Page
1511
 */
1512
function the_neverending_home_page_init() {
1513
	if ( ! current_theme_supports( 'infinite-scroll' ) )
1514
		return;
1515
1516
	new The_Neverending_Home_Page;
1517
}
1518
add_action( 'init', 'the_neverending_home_page_init', 20 );
1519
1520
/**
1521
 * Check whether the current theme is infinite-scroll aware.
1522
 * If so, include the files which add theme support.
1523
 */
1524
function the_neverending_home_page_theme_support() {
1525
	$theme_name = get_stylesheet();
1526
1527
	/**
1528
	 * Filter the path to the Infinite Scroll compatibility file.
1529
	 *
1530
	 * @module infinite-scroll
1531
	 *
1532
	 * @since 2.0.0
1533
	 *
1534
	 * @param string $str IS compatibility file path.
1535
	 * @param string $theme_name Theme name.
1536
	 */
1537
	$customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name );
1538
1539
	if ( is_readable( $customization_file ) )
1540
		require_once( $customization_file );
1541
}
1542
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
1543
1544
/**
1545
 * Early accommodation of the Infinite Scroll AJAX request
1546
 */
1547
if ( The_Neverending_Home_Page::got_infinity() ) {
1548
	/**
1549
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
1550
	 * indicate it as early as possible for actions like init
1551
	 */
1552
	if ( ! defined( 'DOING_AJAX' ) &&
1553
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
1554
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
1555
	) {
1556
		define( 'DOING_AJAX', true );
1557
	}
1558
1559
	// Don't load the admin bar when doing the AJAX response.
1560
	show_admin_bar( false );
1561
}
1562