Completed
Pull Request — add/telegram-sharebuttons (#3775)
by
unknown
30:50 queued 21:07
created

The_Neverending_Home_Page::footer()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

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