Completed
Push — fix/typo-pro-promo ( e5832e...fc698e )
by
unknown
29:20
created

infinity.php ➔ the_neverending_home_page_theme_support()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
Plugin Name: The Neverending Home Page.
5
Plugin URI: http://automattic.com/
6
Description: Adds infinite scrolling support to the front-end blog post view for themes, pulling the next set of posts automatically into view when the reader approaches the bottom of the page.
7
Version: 1.1
8
Author: Automattic
9
Author URI: http://automattic.com/
10
License: GNU General Public License v2 or later
11
License URI: http://www.gnu.org/licenses/gpl-2.0.html
12
*/
13
14
/**
15
 * Class: The_Neverending_Home_Page relies on add_theme_support, expects specific
16
 * styling from each theme; including fixed footer.
17
 */
18
class The_Neverending_Home_Page {
19
	/**
20
	 * Register actions and filters, plus parse IS settings
21
	 *
22
	 * @uses add_action, add_filter, self::get_settings
23
	 * @return null
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
24
	 */
25
	function __construct() {
26
		add_action( 'pre_get_posts',                  array( $this, 'posts_per_page_query' ) );
27
28
		add_action( 'admin_init',                     array( $this, 'settings_api_init' ) );
29
		add_action( 'template_redirect',              array( $this, 'action_template_redirect' ) );
30
		add_action( 'template_redirect',              array( $this, 'ajax_response' ) );
31
		add_action( 'custom_ajax_infinite_scroll',    array( $this, 'query' ) );
32
		add_filter( 'infinite_scroll_query_args',     array( $this, 'inject_query_args' ) );
33
		add_filter( 'infinite_scroll_allowed_vars',   array( $this, 'allowed_query_vars' ) );
34
		add_action( 'the_post',                       array( $this, 'preserve_more_tag' ) );
35
		add_action( 'wp_footer',                      array( $this, 'footer' ) );
36
37
		// Plugin compatibility
38
		add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
39
40
		// Parse IS settings from theme
41
		self::get_settings();
42
	}
43
44
	/**
45
	 * Initialize our static variables
46
	 */
47
	static $the_time = null;
0 ignored issues
show
Coding Style introduced by
The visibility should be declared for property $the_time.

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

class A {
    var $property;
}

the property is implicitly global.

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

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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

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

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

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

    return false;
}

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

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