Completed
Push — branch-4.8-built ( f5da56 )
by
unknown
221:21 queued 208:14
created

modules/infinite-scroll/infinity.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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