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