Completed
Push — add/masterbar-module ( 0660ea...2b5322 )
by
unknown
12:15 queued 02:52
created

The_Neverending_Home_Page::is_last_batch()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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