/modules/infinite-scroll/infinity.php

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