Completed
Push — add/gdpr-ads-compliance ( 381a03...f41c9e )
by
unknown
25:56 queued 13:26
created

infinity.php ➔ the_neverending_home_page_theme_support()   C

Complexity

Conditions 7
Paths 3

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Please refer to the PHP core documentation on constructors.

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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

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

class A {
    var $property;
}

the property is implicitly global.

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

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