Completed
Push — master-stable ( 1165b2...b1216e )
by
unknown
29:17 queued 19:15
created

enqueue_spinner_scripts()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 3
rs 10
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
752
		// If it's a taxonomy, try to change the button text.
753
		if ( is_tax() ) {
754
			// Get current taxonomy slug.
755
			$taxonomy_slug = self::wp_query()->get( 'taxonomy' );
756
757
			// Get taxonomy settings.
758
			$taxonomy = get_taxonomy( $taxonomy_slug );
759
760
			// Check if the taxonomy is attached to one post type only and use its plural name.
761
			// If not, use "Posts" without confusing the users.
762
			if ( count( $taxonomy->object_type ) < 2 ) {
763
				$post_type = $taxonomy->object_type[0];
764
			}
765
		}
766
767
		if ( is_string( $post_type ) && ! empty( $post_type ) ) {
768
			$post_type = get_post_type_object( $post_type );
769
770
			if ( is_object( $post_type ) && ! is_wp_error( $post_type ) ) {
771
				if ( isset( $post_type->labels->name ) ) {
772
					$cpt_text = $post_type->labels->name;
773
				} elseif ( isset( $post_type->label ) ) {
774
					$cpt_text = $post_type->label;
775
				}
776
777
				if ( isset( $cpt_text ) ) {
778
					/* translators: %s is the name of a custom post type */
779
					$click_handle_text = sprintf( __( 'Older %s', 'jetpack' ), $cpt_text );
780
					unset( $cpt_text );
781
				}
782
			}
783
		}
784
785
		unset( $post_type );
786
787
		// Base JS settings
788
		$js_settings = array(
789
			'id'               => self::get_settings()->container,
790
			'ajaxurl'          => esc_url_raw( self::ajax_url() ),
791
			'type'             => esc_js( self::get_settings()->type ),
792
			'wrapper'          => self::has_wrapper(),
793
			'wrapper_class'    => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
794
			'footer'           => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
795
			'click_handle'     => esc_js( self::get_settings()->click_handle ),
796
			'text'             => esc_js( $click_handle_text ),
797
			'totop'            => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
798
			'currentday'       => $currentday,
799
			'order'            => 'DESC',
800
			'scripts'          => array(),
801
			'styles'           => array(),
802
			'google_analytics' => false,
803
			'offset'           => self::wp_query()->get( 'paged' ),
804
			'history'          => array(
805
				'host'                 => preg_replace( '#^http(s)?://#i', '', untrailingslashit( esc_url( get_home_url() ) ) ),
806
				'path'                 => self::get_request_path(),
807
				'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes,
808
				'parameters'           => self::get_request_parameters(),
809
			),
810
			'query_args'      => self::get_query_vars(),
811
			'last_post_date'  => self::get_last_post_date(),
812
			'body_class'	  => self::body_class(),
813
		);
814
815
		// Optional order param
816
		if ( isset( $_REQUEST['order'] ) ) {
817
			$order = strtoupper( $_REQUEST['order'] );
818
819
			if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
820
				$js_settings['order'] = $order;
821
		}
822
823
		/**
824
		 * Filter the Infinite Scroll JS settings outputted in the head.
825
		 *
826
		 * @module infinite-scroll
827
		 *
828
		 * @since 2.0.0
829
		 *
830
		 * @param array $js_settings Infinite Scroll JS settings.
831
		 */
832
		$js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
833
834
		/**
835
		 * Fires before Infinite Scroll outputs inline JavaScript in the head.
836
		 *
837
		 * @module infinite-scroll
838
		 *
839
		 * @since 2.0.0
840
		 */
841
		do_action( 'infinite_scroll_wp_head' );
842
843
		?>
844
		<script type="text/javascript">
845
		//<![CDATA[
846
		var infiniteScroll = <?php echo json_encode( array( 'settings' => $js_settings ) ); ?>;
847
		//]]>
848
		</script>
849
		<?php
850
	}
851
852
	/**
853
	 * Build path data for current request.
854
	 * Used for Google Analytics and pushState history tracking.
855
	 *
856
	 * @global $wp_rewrite
857
	 * @global $wp
858
	 * @uses user_trailingslashit, sanitize_text_field, add_query_arg
859
	 * @return string|bool
860
	 */
861
	private function get_request_path() {
862
		global $wp_rewrite;
863
864
		if ( $wp_rewrite->using_permalinks() ) {
865
			global $wp;
866
867
			// If called too early, bail
868
			if ( ! isset( $wp->request ) )
869
				return false;
870
871
			// Determine path for paginated version of current request
872
			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...
873
				$path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
874
			else
875
				$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
876
877
			// Slashes everywhere we need them
878
			if ( 0 !== strpos( $path, '/' ) )
879
				$path = '/' . $path;
880
881
			$path = user_trailingslashit( $path );
882
		} else {
883
			// Clean up raw $_REQUEST input
884
			$path = array_map( 'sanitize_text_field', $_REQUEST );
885
			$path = array_filter( $path );
886
887
			$path['paged'] = '%d';
888
889
			$path = add_query_arg( $path, '/' );
890
		}
891
892
		return empty( $path ) ? false : $path;
893
	}
894
895
	/**
896
	 * Return query string for current request, prefixed with '?'.
897
	 *
898
	 * @return string
899
	 */
900
	private function get_request_parameters() {
901
		$uri = $_SERVER[ 'REQUEST_URI' ];
902
		$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
903
		if ( $count != 1 )
904
			return '';
905
		return $uri;
906
	}
907
908
	/**
909
	 * Provide IS with a list of the scripts and stylesheets already present on the page.
910
	 * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
911
	 *
912
	 * @global $wp_scripts, $wp_styles
913
	 * @action wp_footer
914
	 * @return string
915
	 */
916
	function action_wp_footer() {
917
		global $wp_scripts, $wp_styles;
918
919
		$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
920
		/**
921
		 * Filter the list of scripts already present on the page.
922
		 *
923
		 * @module infinite-scroll
924
		 *
925
		 * @since 2.1.2
926
		 *
927
		 * @param array $scripts Array of scripts present on the page.
928
		 */
929
		$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
930
931
		$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
932
		/**
933
		 * Filter the list of styles already present on the page.
934
		 *
935
		 * @module infinite-scroll
936
		 *
937
		 * @since 2.1.2
938
		 *
939
		 * @param array $styles Array of styles present on the page.
940
		 */
941
		$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
942
943
		?><script type="text/javascript">
944
			jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> );
945
			jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> );
946
		</script><?php
947
	}
948
949
	/**
950
	 * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
951
	 *
952
	 * @global $wp_scripts
953
	 * @uses sanitize_text_field, add_query_arg
954
	 * @filter infinite_scroll_results
955
	 * @return array
956
	 */
957
	function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
958
		// Don't bother unless there are posts to display
959
		if ( 'success' != $results['type'] )
960
			return $results;
961
962
		// Parse and sanitize the script handles already output
963
		$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', $_REQUEST['scripts'] ) : false;
964
965
		if ( is_array( $initial_scripts ) ) {
966
			global $wp_scripts;
967
968
			// Identify new scripts needed by the latest set of IS posts
969
			$new_scripts = array_diff( $wp_scripts->done, $initial_scripts );
970
971
			// If new scripts are needed, extract relevant data from $wp_scripts
972
			if ( ! empty( $new_scripts ) ) {
973
				$results['scripts'] = array();
974
975
				foreach ( $new_scripts as $handle ) {
976
					// Abort if somehow the handle doesn't correspond to a registered script
977
					if ( ! isset( $wp_scripts->registered[ $handle ] ) )
978
						continue;
979
980
					// Provide basic script data
981
					$script_data = array(
982
						'handle'     => $handle,
983
						'footer'     => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ),
984
						'extra_data' => $wp_scripts->print_extra_script( $handle, false )
985
					);
986
987
					// Base source
988
					$src = $wp_scripts->registered[ $handle ]->src;
989
990
					// Take base_url into account
991
					if ( strpos( $src, 'http' ) !== 0 )
992
						$src = $wp_scripts->base_url . $src;
993
994
					// Version and additional arguments
995 View Code Duplication
					if ( null === $wp_scripts->registered[ $handle ]->ver )
996
						$ver = '';
997
					else
998
						$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
999
1000 View Code Duplication
					if ( isset( $wp_scripts->args[ $handle ] ) )
1001
						$ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
1002
1003
					// Full script source with version info
1004
					$script_data['src'] = add_query_arg( 'ver', $ver, $src );
1005
1006
					// Add script to data that will be returned to IS JS
1007
					array_push( $results['scripts'], $script_data );
1008
				}
1009
			}
1010
		}
1011
1012
		// Expose additional script data to filters, but only include in final `$results` array if needed.
1013
		if ( ! isset( $results['scripts'] ) )
1014
			$results['scripts'] = array();
1015
1016
		/**
1017
		 * Filter the additional scripts required by the latest set of IS posts.
1018
		 *
1019
		 * @module infinite-scroll
1020
		 *
1021
		 * @since 2.1.2
1022
		 *
1023
		 * @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
1024
		 * @param array|bool $initial_scripts Set of scripts loaded on each page.
1025
		 * @param array $results Array of Infinite Scroll results.
1026
		 * @param array $query_args Array of Query arguments.
1027
		 * @param WP_Query $wp_query WP Query.
1028
		 */
1029
		$results['scripts'] = apply_filters(
1030
			'infinite_scroll_additional_scripts',
1031
			$results['scripts'],
1032
			$initial_scripts,
1033
			$results,
1034
			$query_args,
1035
			$wp_query
1036
		);
1037
1038
		if ( empty( $results['scripts'] ) )
1039
			unset( $results['scripts' ] );
1040
1041
		// Parse and sanitize the style handles already output
1042
		$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', $_REQUEST['styles'] ) : false;
1043
1044
		if ( is_array( $initial_styles ) ) {
1045
			global $wp_styles;
1046
1047
			// Identify new styles needed by the latest set of IS posts
1048
			$new_styles = array_diff( $wp_styles->done, $initial_styles );
1049
1050
			// If new styles are needed, extract relevant data from $wp_styles
1051
			if ( ! empty( $new_styles ) ) {
1052
				$results['styles'] = array();
1053
1054
				foreach ( $new_styles as $handle ) {
1055
					// Abort if somehow the handle doesn't correspond to a registered stylesheet
1056
					if ( ! isset( $wp_styles->registered[ $handle ] ) )
1057
						continue;
1058
1059
					// Provide basic style data
1060
					$style_data = array(
1061
						'handle' => $handle,
1062
						'media'  => 'all'
1063
					);
1064
1065
					// Base source
1066
					$src = $wp_styles->registered[ $handle ]->src;
1067
1068
					// Take base_url into account
1069
					if ( strpos( $src, 'http' ) !== 0 )
1070
						$src = $wp_styles->base_url . $src;
1071
1072
					// Version and additional arguments
1073 View Code Duplication
					if ( null === $wp_styles->registered[ $handle ]->ver )
1074
						$ver = '';
1075
					else
1076
						$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
1077
1078 View Code Duplication
					if ( isset($wp_styles->args[ $handle ] ) )
1079
						$ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
1080
1081
					// Full stylesheet source with version info
1082
					$style_data['src'] = add_query_arg( 'ver', $ver, $src );
1083
1084
					// Parse stylesheet's conditional comments if present, converting to logic executable in JS
1085
					if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
1086
						// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
1087
						$style_data['conditional'] = str_replace( array(
1088
							'lte',
1089
							'lt',
1090
							'gte',
1091
							'gt'
1092
						), array(
1093
							'%ver <=',
1094
							'%ver <',
1095
							'%ver >=',
1096
							'%ver >',
1097
						), $wp_styles->registered[ $handle ]->extra['conditional'] );
1098
1099
						// 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().
1100
						$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
1101
1102
						// Lastly, remove the IE strings
1103
						$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
1104
					}
1105
1106
					// Parse requested media context for stylesheet
1107 View Code Duplication
					if ( isset( $wp_styles->registered[ $handle ]->args ) )
1108
						$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
1109
1110
					// Add stylesheet to data that will be returned to IS JS
1111
					array_push( $results['styles'], $style_data );
1112
				}
1113
			}
1114
		}
1115
1116
		// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
1117
		if ( ! isset( $results['styles'] ) )
1118
			$results['styles'] = array();
1119
1120
		/**
1121
		 * Filter the additional styles required by the latest set of IS posts.
1122
		 *
1123
		 * @module infinite-scroll
1124
		 *
1125
		 * @since 2.1.2
1126
		 *
1127
		 * @param array $results['styles'] Additional styles required by the latest set of IS posts.
1128
		 * @param array|bool $initial_styles Set of styles loaded on each page.
1129
		 * @param array $results Array of Infinite Scroll results.
1130
		 * @param array $query_args Array of Query arguments.
1131
		 * @param WP_Query $wp_query WP Query.
1132
		 */
1133
		$results['styles'] = apply_filters(
1134
			'infinite_scroll_additional_stylesheets',
1135
			$results['styles'],
1136
			$initial_styles,
1137
			$results,
1138
			$query_args,
1139
			$wp_query
1140
		);
1141
1142
		if ( empty( $results['styles'] ) )
1143
			unset( $results['styles' ] );
1144
1145
		// Lastly, return the IS results array
1146
		return $results;
1147
	}
1148
1149
	/**
1150
	 * Runs the query and returns the results via JSON.
1151
	 * Triggered by an AJAX request.
1152
	 *
1153
	 * @global $wp_query
1154
	 * @global $wp_the_query
1155
	 * @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
1156
	 * @return string or null
1157
	 */
1158
	function query() {
1159
		global $wp_customize;
1160
		global $wp_version;
1161
		if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
1162
			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...
1163
1164
		$page = (int) $_REQUEST['page'];
1165
1166
		// Sanitize and set $previousday. Expected format: dd.mm.yy
1167
		if ( preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) {
1168
			global $previousday;
1169
			$previousday = $_REQUEST['currentday'];
1170
		}
1171
1172
		$sticky = get_option( 'sticky_posts' );
1173
		$post__not_in = self::wp_query()->get( 'post__not_in' );
1174
1175
		//we have to take post__not_in args into consideration here not only sticky posts
1176
		if ( true === isset( $_REQUEST['query_args']['post__not_in'] ) ) {
1177
			$post__not_in = array_merge( $post__not_in, array_map( 'intval', (array) $_REQUEST['query_args']['post__not_in'] ) );
1178
		}
1179
1180
		if ( ! empty( $post__not_in ) )
1181
			$sticky = array_unique( array_merge( $sticky, $post__not_in ) );
1182
1183
		$post_status = array( 'publish' );
1184
		if ( current_user_can( 'read_private_posts' ) )
1185
			array_push( $post_status, 'private' );
1186
1187
		$order = in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ) ) ? $_REQUEST['order'] : 'DESC';
1188
1189
		$query_args = array_merge( self::wp_query()->query_vars, array(
1190
			'paged'          => $page,
1191
			'post_status'    => $post_status,
1192
			'posts_per_page' => self::get_settings()->posts_per_page,
1193
			'post__not_in'   => ( array ) $sticky,
1194
			'order'          => $order
1195
		) );
1196
1197
		// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
1198
		if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
1199
			unset( $query_args['s'] );
1200
		}
1201
1202
		// By default, don't query for a specific page of a paged post object.
1203
		// This argument can come from merging self::wp_query() into $query_args above.
1204
		// Since IS is only used on archives, we should always display the first page of any paged content.
1205
		unset( $query_args['page'] );
1206
1207
		/**
1208
		 * Filter the array of main query arguments.
1209
		 *
1210
		 * @module infinite-scroll
1211
		 *
1212
		 * @since 2.0.1
1213
		 *
1214
		 * @param array $query_args Array of Query arguments.
1215
		 */
1216
		$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
1217
1218
		// Add query filter that checks for posts below the date
1219
		add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1220
1221
		$GLOBALS['wp_the_query'] = $GLOBALS['wp_query'] = new WP_Query( $query_args );
1222
1223
		remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
1224
1225
		$results = array();
1226
1227
		if ( have_posts() ) {
1228
			// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1229
			ob_start();
1230
			wp_head();
1231
			while ( ob_get_length() ) {
1232
				ob_end_clean();
1233
			}
1234
1235
			$results['type'] = 'success';
1236
1237
			// First, try theme's specified rendering handler, either specified via `add_theme_support` or by hooking to this action directly.
1238
			ob_start();
1239
			/**
1240
			 * Fires when rendering Infinite Scroll posts.
1241
			 *
1242
			 * @module infinite-scroll
1243
			 *
1244
			 * @since 2.0.0
1245
			 */
1246
			do_action( 'infinite_scroll_render' );
1247
			$results['html'] = ob_get_clean();
1248
1249
			// 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.
1250
			if ( empty( $results['html'] ) ) {
1251
				add_action( 'infinite_scroll_render', array( $this, 'render' ) );
1252
				rewind_posts();
1253
1254
				ob_start();
1255
				/** This action is already documented in modules/infinite-scroll/infinity.php */
1256
				do_action( 'infinite_scroll_render' );
1257
				$results['html'] = ob_get_clean();
1258
			}
1259
1260
			// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
1261
			if ( empty( $results['html'] ) ) {
1262
				unset( $results['html'] );
1263
				/**
1264
				 * Fires when Infinite Scoll doesn't render any posts.
1265
				 *
1266
				 * @module infinite-scroll
1267
				 *
1268
				 * @since 2.0.0
1269
				 */
1270
				do_action( 'infinite_scroll_empty' );
1271
				$results['type'] = 'empty';
1272
			} elseif ( $this->has_wrapper() ) {
1273
				$wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
1274
				$wrapper_classes .= ' infinite-view-' . $page;
1275
				$wrapper_classes = trim( $wrapper_classes );
1276
1277
				$results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
1278
			}
1279
1280
			// Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
1281
			ob_start();
1282
			wp_footer();
1283
			while ( ob_get_length() ) {
1284
				ob_end_clean();
1285
			}
1286
1287
			if ( 'success' == $results['type'] ) {
1288
				global $currentday;
1289
				$results['lastbatch'] = self::is_last_batch();
1290
				$results['currentday'] = $currentday;
1291
			}
1292
1293
			// Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
1294
			if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
1295
				global $jetpack_sharing_counts;
1296
1297
				while( have_posts() ) {
1298
					the_post();
1299
1300
					sharing_register_post_for_share_counts( get_the_ID() );
1301
				}
1302
1303
				$results['postflair'] = array_flip( $jetpack_sharing_counts );
1304
			}
1305
		} else {
1306
			/** This action is already documented in modules/infinite-scroll/infinity.php */
1307
			do_action( 'infinite_scroll_empty' );
1308
			$results['type'] = 'empty';
1309
		}
1310
1311
		// This should be removed when WordPress 4.8 is released.
1312
		if ( version_compare( $wp_version, '4.7', '<' ) && is_customize_preview() ) {
1313
			$wp_customize->remove_preview_signature();
1314
		}
1315
1316
		wp_send_json(
1317
			/**
1318
			 * Filter the Infinite Scroll results.
1319
			 *
1320
			 * @module infinite-scroll
1321
			 *
1322
			 * @since 2.0.0
1323
			 *
1324
			 * @param array $results Array of Infinite Scroll results.
1325
			 * @param array $query_args Array of main query arguments.
1326
			 * @param WP_Query $wp_query WP Query.
1327
			 */
1328
			apply_filters( 'infinite_scroll_results', $results, $query_args, self::wp_query() )
1329
		);
1330
	}
1331
1332
	/**
1333
	 * Update the $allowed_vars array with the standard WP public and private
1334
	 * query vars, as well as taxonomy vars
1335
	 *
1336
	 * @global $wp
1337
	 * @param array $allowed_vars
1338
	 * @filter infinite_scroll_allowed_vars
1339
	 * @return array
1340
	 */
1341
	function allowed_query_vars( $allowed_vars ) {
1342
		global $wp;
1343
1344
		$allowed_vars += $wp->public_query_vars;
1345
		$allowed_vars += $wp->private_query_vars;
1346
		$allowed_vars += $this->get_taxonomy_vars();
1347
1348
		foreach ( array_keys( $allowed_vars, 'paged' ) as $key ) {
1349
			unset( $allowed_vars[ $key ] );
1350
		}
1351
1352
		return array_unique( $allowed_vars );
1353
	}
1354
1355
	/**
1356
	 * Returns an array of stock and custom taxonomy query vars
1357
	 *
1358
	 * @global $wp_taxonomies
1359
	 * @return array
1360
	 */
1361
	function get_taxonomy_vars() {
1362
		global $wp_taxonomies;
1363
1364
		$taxonomy_vars = array();
1365
		foreach ( $wp_taxonomies as $taxonomy => $t ) {
1366
			if ( $t->query_var )
1367
				$taxonomy_vars[] = $t->query_var;
1368
		}
1369
1370
		// still needed?
1371
		$taxonomy_vars[] = 'tag_id';
1372
1373
		return $taxonomy_vars;
1374
	}
1375
1376
	/**
1377
	 * Update the $query_args array with the parameters provided via AJAX/GET.
1378
	 *
1379
	 * @param array $query_args
1380
	 * @filter infinite_scroll_query_args
1381
	 * @return array
1382
	 */
1383
	function inject_query_args( $query_args ) {
1384
		/**
1385
		 * Filter the array of allowed Infinite Scroll query arguments.
1386
		 *
1387
		 * @module infinite-scroll
1388
		 *
1389
		 * @since 2.6.0
1390
		 *
1391
		 * @param array $args Array of allowed Infinite Scroll query arguments.
1392
		 * @param array $query_args Array of query arguments.
1393
		 */
1394
		$allowed_vars = apply_filters( 'infinite_scroll_allowed_vars', array(), $query_args );
1395
1396
		$query_args = array_merge( $query_args, array(
1397
			'suppress_filters' => false,
1398
		) );
1399
1400
		if ( is_array( $_REQUEST[ 'query_args' ] ) ) {
1401
			foreach ( $_REQUEST[ 'query_args' ] as $var => $value ) {
1402
				if ( in_array( $var, $allowed_vars ) && ! empty( $value ) )
1403
					$query_args[ $var ] = $value;
1404
			}
1405
		}
1406
1407
		return $query_args;
1408
	}
1409
1410
	/**
1411
	 * Rendering fallback used when themes don't specify their own handler.
1412
	 *
1413
	 * @uses have_posts, the_post, get_template_part, get_post_format
1414
	 * @action infinite_scroll_render
1415
	 * @return string
1416
	 */
1417
	function render() {
1418
		while ( have_posts() ) {
1419
			the_post();
1420
1421
			get_template_part( 'content', get_post_format() );
1422
		}
1423
	}
1424
1425
	/**
1426
	 * Allow plugins to filter what archives Infinite Scroll supports
1427
	 *
1428
	 * @uses current_theme_supports, is_home, is_archive, apply_filters, self::get_settings
1429
	 * @return bool
1430
	 */
1431
	public static function archive_supports_infinity() {
1432
		$supported = current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() || is_search() );
1433
1434
		// Disable when previewing a non-active theme in the customizer
1435
		if ( is_customize_preview() && ! $GLOBALS['wp_customize']->is_theme_active() ) {
1436
			return false;
1437
		}
1438
1439
		/**
1440
		 * Allow plugins to filter what archives Infinite Scroll supports.
1441
		 *
1442
		 * @module infinite-scroll
1443
		 *
1444
		 * @since 2.0.0
1445
		 *
1446
		 * @param bool $supported Does the Archive page support Infinite Scroll.
1447
		 * @param object self::get_settings() IS settings provided by theme.
1448
		 */
1449
		return (bool) apply_filters( 'infinite_scroll_archive_supported', $supported, self::get_settings() );
1450
	}
1451
1452
	/**
1453
	 * The Infinite Blog Footer
1454
	 *
1455
	 * @uses self::get_settings, self::archive_supports_infinity, self::default_footer
1456
	 * @return string or null
1457
	 */
1458
	function footer() {
1459
		// Bail if theme requested footer not show
1460
		if ( false == self::get_settings()->footer )
1461
			return;
1462
1463
		// We only need the new footer for the 'scroll' type
1464
		if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
1465
			return;
1466
1467
		if ( self::is_last_batch() ) {
1468
			return;
1469
		}
1470
1471
		// Display a footer, either user-specified or a default
1472
		if ( false !== self::get_settings()->footer_callback && is_callable( self::get_settings()->footer_callback ) )
1473
			call_user_func( self::get_settings()->footer_callback, self::get_settings() );
1474
		else
1475
			self::default_footer();
1476
	}
1477
1478
	/**
1479
	 * Render default IS footer
1480
	 *
1481
	 * @uses __, wp_get_theme, get_current_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
1482
	 * @return string
1483
	 */
1484
	private function default_footer() {
1485
		$credits = sprintf(
1486
			'<a href="https://wordpress.org/" target="_blank" rel="generator">%1$s</a> ',
1487
			__( 'Proudly powered by WordPress', 'jetpack' )
1488
		);
1489
		$credits .= sprintf(
1490
			/* translators: %1$s is the name of a theme */
1491
			__( 'Theme: %1$s.', 'jetpack' ),
1492
			function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme()
1493
		);
1494
		/**
1495
		 * Filter Infinite Scroll's credit text.
1496
		 *
1497
		 * @module infinite-scroll
1498
		 *
1499
		 * @since 2.0.0
1500
		 *
1501
		 * @param string $credits Infinite Scroll credits.
1502
		 */
1503
		$credits = apply_filters( 'infinite_scroll_credit', $credits );
1504
1505
		?>
1506
		<div id="infinite-footer">
1507
			<div class="container">
1508
				<div class="blog-info">
1509
					<a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" rel="home">
1510
						<?php bloginfo( 'name' ); ?>
1511
					</a>
1512
				</div>
1513
				<div class="blog-credits">
1514
					<?php echo $credits; ?>
1515
				</div>
1516
			</div>
1517
		</div><!-- #infinite-footer -->
1518
		<?php
1519
	}
1520
1521
	/**
1522
	 * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
1523
	 * When arguments are present, Grunion redirects to the IS AJAX endpoint.
1524
	 *
1525
	 * @param string $url
1526
	 * @uses remove_query_arg
1527
	 * @filter grunion_contact_form_redirect_url
1528
	 * @return string
1529
	 */
1530
	public function filter_grunion_redirect_url( $url ) {
1531
		// Remove IS query args, if present
1532
		if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
1533
			$url = remove_query_arg( array(
1534
				'infinity',
1535
				'action',
1536
				'page',
1537
				'order',
1538
				'scripts',
1539
				'styles'
1540
			), $url );
1541
		}
1542
1543
		return $url;
1544
	}
1545
};
1546
1547
/**
1548
 * Initialize The_Neverending_Home_Page
1549
 */
1550
function the_neverending_home_page_init() {
1551
	if ( ! current_theme_supports( 'infinite-scroll' ) )
1552
		return;
1553
1554
	new The_Neverending_Home_Page;
1555
}
1556
add_action( 'init', 'the_neverending_home_page_init', 20 );
1557
1558
/**
1559
 * Check whether the current theme is infinite-scroll aware.
1560
 * If so, include the files which add theme support.
1561
 */
1562
function the_neverending_home_page_theme_support() {
1563
	$theme_name = get_stylesheet();
1564
1565
	/**
1566
	 * Filter the path to the Infinite Scroll compatibility file.
1567
	 *
1568
	 * @module infinite-scroll
1569
	 *
1570
	 * @since 2.0.0
1571
	 *
1572
	 * @param string $str IS compatibility file path.
1573
	 * @param string $theme_name Theme name.
1574
	 */
1575
	$customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name );
1576
1577
	if ( is_readable( $customization_file ) )
1578
		require_once( $customization_file );
1579
}
1580
add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );
1581
1582
/**
1583
 * Early accommodation of the Infinite Scroll AJAX request
1584
 */
1585
if ( The_Neverending_Home_Page::got_infinity() ) {
1586
	/**
1587
	 * If we're sure this is an AJAX request (i.e. the HTTP_X_REQUESTED_WITH header says so),
1588
	 * indicate it as early as possible for actions like init
1589
	 */
1590
	if ( ! defined( 'DOING_AJAX' ) &&
1591
		isset( $_SERVER['HTTP_X_REQUESTED_WITH'] ) &&
1592
		strtoupper( $_SERVER['HTTP_X_REQUESTED_WITH'] ) == 'XMLHTTPREQUEST'
1593
	) {
1594
		define( 'DOING_AJAX', true );
1595
	}
1596
1597
	// Don't load the admin bar when doing the AJAX response.
1598
	show_admin_bar( false );
1599
}
1600