Completed
Push — update/infinite-scroll-woocomm... ( 0ef90e...e8e6b7 )
by Tiago
10:30
created

The_Neverending_Home_Page   D

Complexity

Total Complexity 233

Size/Duplication

Total Lines 1576
Duplicated Lines 3.43 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 54
loc 1576
rs 4.4102
c 0
b 0
f 0
wmc 233
lcom 1
cbo 1

37 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 18 1
F get_settings() 22 183 43
A posts_per_page() 0 19 3
A wp_query() 0 13 1
A got_infinity() 0 12 1
B is_last_batch() 0 39 6
A preserve_more_tag() 0 8 2
A settings_api_init() 0 8 2
A infinite_setting_html() 0 11 3
B action_template_redirect() 0 45 5
A enqueue_spinner_scripts() 0 3 1
B body_class() 0 13 5
B get_excluded_posts() 0 20 8
A get_query_vars() 0 16 3
C has_only_title_matching_posts() 0 29 7
D get_last_post_date() 9 27 9
B get_query_sort_field() 9 16 6
B query_time_filter() 0 35 6
A posts_per_page_query() 0 4 4
A has_wrapper() 0 3 1
A ajax_url() 0 16 1
A ajax_response() 0 23 3
A action_wp_head() 0 3 1
F action_wp_footer_settings() 0 111 14
B get_request_path() 0 33 6
A get_request_parameters() 0 7 2
B action_wp_footer() 0 32 3
F filter_infinite_scroll_results() 14 191 32
F query() 0 190 26
A allowed_query_vars() 0 13 2
A get_taxonomy_vars() 0 14 3
B inject_query_args() 0 26 5
A render() 0 7 2
B archive_supports_infinity() 0 20 6
B footer() 0 19 7
B default_footer() 0 36 1
A filter_grunion_redirect_url() 0 15 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like The_Neverending_Home_Page often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use The_Neverending_Home_Page, and based on these observations, apply Extract Interface, too.

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