PageRenderTime 51ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/files/jetpack/2.1.1/infinite-scroll/infinity.php

https://gitlab.com/Mirros/jsdelivr
PHP | 913 lines | 534 code | 153 blank | 226 comment | 91 complexity | d57ea6458db60af63dc4243e19526b9c MD5 | raw file
  1. <?php
  2. /*
  3. Plugin Name: The Neverending Home Page.
  4. Plugin URI: http://automattic.com/
  5. 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.
  6. Version: 1.1
  7. Author: Automattic
  8. Author URI: http://automattic.com/
  9. License: GNU General Public License v2 or later
  10. License URI: http://www.gnu.org/licenses/gpl-2.0.html
  11. */
  12. /**
  13. * Class: The_Neverending_Home_Page relies on add_theme_support, expects specific
  14. * styling from each theme; including fixed footer.
  15. */
  16. class The_Neverending_Home_Page {
  17. /**
  18. *
  19. */
  20. function __construct() {
  21. add_filter( 'pre_get_posts', array( $this, 'posts_per_page_query' ) );
  22. add_action( 'admin_init', array( $this, 'settings_api_init' ) );
  23. add_action( 'template_redirect', array( $this, 'action_template_redirect' ) );
  24. add_action( 'template_redirect', array( $this, 'ajax_response' ) );
  25. add_action( 'custom_ajax_infinite_scroll', array( $this, 'query' ) );
  26. add_action( 'the_post', array( $this, 'preserve_more_tag' ) );
  27. add_action( 'get_footer', array( $this, 'footer' ) );
  28. // Plugin compatibility
  29. add_filter( 'grunion_contact_form_redirect_url', array( $this, 'filter_grunion_redirect_url' ) );
  30. // Parse IS settings from theme
  31. self::get_settings();
  32. }
  33. /**
  34. * Initialize our static variables
  35. */
  36. static $the_time = null;
  37. static $settings = null; // Don't access directly, instead use self::get_settings().
  38. static $option_name_enabled = 'infinite_scroll';
  39. /**
  40. * Parse IS settings provided by theme
  41. *
  42. * @uses get_theme_support, infinite_scroll_has_footer_widgets, sanitize_title, add_action, get_option, wp_parse_args, is_active_sidebar
  43. * @return object
  44. */
  45. static function get_settings() {
  46. if ( is_null( self::$settings ) ) {
  47. $css_pattern = '#[^A-Z\d\-_]#i';
  48. $settings = $defaults = array(
  49. 'type' => 'scroll', // scroll | click
  50. 'requested_type' => 'scroll', // store the original type for use when logic overrides it
  51. 'footer_widgets' => false, // true | false | sidebar_id | array of sidebar_ids -- last two are checked with is_active_sidebar
  52. 'container' => 'content', // container html id
  53. 'wrapper' => true, // true | false | html class
  54. 'render' => false, // optional function, otherwise the `content` template part will be used
  55. 'footer' => true, // boolean to enable or disable the infinite footer | string to provide an html id to derive footer width from
  56. 'posts_per_page' => false // int | false to set based on IS type
  57. );
  58. // Validate settings passed through add_theme_support()
  59. $_settings = get_theme_support( 'infinite-scroll' );
  60. if ( is_array( $_settings ) ) {
  61. // Preferred implementation, where theme provides an array of options
  62. if ( isset( $_settings[0] ) && is_array( $_settings[0] ) ) {
  63. foreach ( $_settings[0] as $key => $value ) {
  64. switch ( $key ) {
  65. case 'type' :
  66. if ( in_array( $value, array( 'scroll', 'click' ) ) )
  67. $settings[ $key ] = $settings['requested_type'] = $value;
  68. break;
  69. case 'footer_widgets' :
  70. if ( is_string( $value ) )
  71. $settings[ $key ] = sanitize_title( $value );
  72. elseif ( is_array( $value ) )
  73. $settings[ $key ] = array_map( 'sanitize_title', $value );
  74. elseif ( is_bool( $value ) )
  75. $settings[ $key ] = $value;
  76. break;
  77. case 'container' :
  78. case 'wrapper' :
  79. if ( 'wrapper' == $key && is_bool( $value ) ) {
  80. $settings[ $key ] = $value;
  81. }
  82. else {
  83. $value = preg_replace( $css_pattern, '', $value );
  84. if ( ! empty( $value ) )
  85. $settings[ $key ] = $value;
  86. }
  87. break;
  88. case 'render' :
  89. if ( false !== $value && is_callable( $value ) ) {
  90. $settings[ $key ] = $value;
  91. add_action( 'infinite_scroll_render', $value );
  92. }
  93. break;
  94. case 'footer' :
  95. if ( is_bool( $value ) ) {
  96. $settings[ $key ] = $value;
  97. }
  98. elseif ( is_string( $value ) ) {
  99. $value = preg_replace( $css_pattern, '', $value );
  100. if ( ! empty( $value ) )
  101. $settings[ $key ] = $value;
  102. }
  103. break;
  104. case 'posts_per_page' :
  105. if ( is_numeric( $value ) )
  106. $settings[ $key ] = (int) $value;
  107. break;
  108. default:
  109. continue;
  110. break;
  111. }
  112. }
  113. }
  114. // Checks below are for backwards compatibility
  115. elseif ( is_string( $_settings[0] ) ) {
  116. // Container to append new posts to
  117. $settings['container'] = preg_replace( $css_pattern, '', $_settings[0] );
  118. // Wrap IS elements?
  119. if ( isset( $_settings[1] ) )
  120. $settings['wrapper'] = (bool) $_settings[1];
  121. }
  122. }
  123. // Always ensure all values are present in the final array
  124. $settings = wp_parse_args( $settings, $defaults );
  125. // Check if a legacy `infinite_scroll_has_footer_widgets()` function is defined and override the footer_widgets parameter's value.
  126. // Otherwise, if a widget area ID or array of IDs was provided in the footer_widgets parameter, check if any contains any widgets.
  127. // 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.
  128. if ( function_exists( 'infinite_scroll_has_footer_widgets' ) ) {
  129. $settings['footer_widgets'] = (bool) infinite_scroll_has_footer_widgets();
  130. }
  131. elseif ( is_array( $settings['footer_widgets'] ) ) {
  132. $sidebar_ids = $settings['footer_widgets'];
  133. $settings['footer_widgets'] = false;
  134. foreach ( $sidebar_ids as $sidebar_id ) {
  135. if ( is_active_sidebar( $sidebar_id ) ) {
  136. $settings['footer_widgets'] = true;
  137. break;
  138. }
  139. }
  140. unset( $sidebar_ids );
  141. unset( $sidebar_id );
  142. }
  143. elseif ( is_string( $settings['footer_widgets'] ) ) {
  144. $settings['footer_widgets'] = (bool) is_active_sidebar( $settings['footer_widgets'] );
  145. }
  146. // For complex logic, let themes filter the `footer_widgets` parameter.
  147. $settings['footer_widgets'] = apply_filters( 'infinite_scroll_has_footer_widgets', $settings['footer_widgets'] );
  148. // Finally, after all of the sidebar checks and filtering, ensure that a boolean value is present, otherwise set to default of `false`.
  149. if ( ! is_bool( $settings['footer_widgets'] ) )
  150. $settings['footer_widgets'] = false;
  151. // Ensure that IS is enabled and no footer widgets exist if the IS type isn't already "click".
  152. if ( 'click' != $settings['type'] ) {
  153. // Check the setting status
  154. $disabled = '' === get_option( self::$option_name_enabled ) ? true : false;
  155. // Footer content or Reading option check
  156. if ( $settings['footer_widgets'] || $disabled )
  157. $settings['type'] = 'click';
  158. }
  159. // Backwards compatibility for posts_per_page setting
  160. if ( false === $settings['posts_per_page'] )
  161. $settings['posts_per_page'] = 'click' == $settings['type'] ? (int) get_option( 'posts_per_page' ) : 7;
  162. // Store final settings in a class static to avoid reparsing
  163. self::$settings = apply_filters( 'infinite_scroll_settings', $settings );
  164. }
  165. return (object) self::$settings;
  166. }
  167. /**
  168. * Has infinite scroll been triggered?
  169. */
  170. static function got_infinity() {
  171. return isset( $_GET[ 'infinity' ] );
  172. }
  173. /**
  174. * The more tag will be ignored by default if the blog page isn't our homepage.
  175. * Let's force the $more global to false.
  176. */
  177. function preserve_more_tag( $array ) {
  178. global $more;
  179. if ( self::got_infinity() )
  180. $more = 0; //0 = show content up to the more tag. Add more link.
  181. return $array;
  182. }
  183. /**
  184. * Add a checkbox field to Settings > Reading
  185. * for enabling infinite scroll.
  186. *
  187. * Only show if the current theme supports infinity.
  188. *
  189. * @uses current_theme_supports, add_settings_field, __, register_setting
  190. * @action admin_init
  191. * @return null
  192. */
  193. function settings_api_init() {
  194. if ( ! current_theme_supports( 'infinite-scroll' ) )
  195. return;
  196. // Add the setting field [infinite_scroll] and place it in Settings > Reading
  197. add_settings_field( self::$option_name_enabled, '<span id="infinite-scroll-options">' . __( 'To infinity and beyond', 'jetpack' ) . '</span>', array( $this, 'infinite_setting_html' ), 'reading' );
  198. register_setting( 'reading', self::$option_name_enabled, 'esc_attr' );
  199. }
  200. /**
  201. * HTML code to display a checkbox true/false option
  202. * for the infinite_scroll setting.
  203. */
  204. function infinite_setting_html() {
  205. $notice = '<em>' . __( "We've disabled this option for you since you have footer widgets in Appearance &rarr; Widgets, or because your theme does not support infinite scroll.", 'jetpack' ) . '</em>';
  206. // If the blog has footer widgets, show a notice instead of the checkbox
  207. if ( self::get_settings()->footer_widgets || 'click' == self::get_settings()->requested_type ) {
  208. echo '<label>' . $notice . '</label>';
  209. } else {
  210. 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>';
  211. }
  212. }
  213. /**
  214. * Does the legwork to determine whether the feature is enabled.
  215. *
  216. * @uses current_theme_supports, self::archive_supports_infinity, self::get_settings, self::set_last_post_time, add_filter, wp_enqueue_script, plugins_url, wp_enqueue_style, add_action
  217. * @action template_redirect
  218. * @return null
  219. */
  220. function action_template_redirect() {
  221. // Check that we support infinite scroll, and are on the home page.
  222. if ( ! current_theme_supports( 'infinite-scroll' ) || ! self::archive_supports_infinity() )
  223. return;
  224. $id = self::get_settings()->container;
  225. // Check that we have an id.
  226. if ( empty( $id ) )
  227. return;
  228. // Bail if there are not enough posts for infinity.
  229. if ( ! self::set_last_post_time() )
  230. return;
  231. // Add a class to the body.
  232. add_filter( 'body_class', array( $this, 'body_class' ) );
  233. // Add our scripts.
  234. wp_enqueue_script( 'the-neverending-homepage', plugins_url( 'infinity.js', __FILE__ ), array( 'jquery' ), '20130101' );
  235. // Add our default styles.
  236. wp_enqueue_style( 'the-neverending-homepage', plugins_url( 'infinity.css', __FILE__ ), array(), '20120612' );
  237. add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_spinner_scripts' ) );
  238. add_action( 'wp_head', array( $this, 'action_wp_head' ), 2 );
  239. add_action( 'wp_footer', array( $this, 'action_wp_footer' ), 99999999 );
  240. add_filter( 'infinite_scroll_results', array( $this, 'filter_infinite_scroll_results' ) );
  241. }
  242. /**
  243. * Enqueue spinner scripts.
  244. */
  245. function enqueue_spinner_scripts() {
  246. wp_enqueue_script( 'jquery.spin' );
  247. }
  248. /**
  249. * Adds an 'infinite-scroll' class to the body.
  250. */
  251. function body_class( $classes ) {
  252. $classes[] = 'infinite-scroll';
  253. if ( 'scroll' == self::get_settings()->type )
  254. $classes[] = 'neverending';
  255. return $classes;
  256. }
  257. /**
  258. * Grab the timestamp for the last post.
  259. * @return string 'Y-m-d H:i:s' or null
  260. */
  261. function set_last_post_time( $date = false ) {
  262. global $posts;
  263. $count = count( $posts );
  264. if ( ! empty( $date ) && preg_match( '|\d{4}\-\d{2}\-\d{2}|', $_GET['date'] ) ) {
  265. self::$the_time = "$date 00:00:00";
  266. return self::$the_time;
  267. }
  268. // If we don't have enough posts for infinity, return early
  269. if ( ! $count || $count < self::get_settings()->posts_per_page )
  270. return self::$the_time;
  271. $last_post = end( $posts );
  272. // If the function is called again but we already have a value, return it
  273. if ( null != self::$the_time ) {
  274. return self::$the_time;
  275. }
  276. else if ( isset( $last_post->post_date_gmt ) ) {
  277. // Grab the latest post time in Y-m-d H:i:s gmt format
  278. self::$the_time = $last_post->post_date_gmt;
  279. }
  280. return self::$the_time;
  281. }
  282. /**
  283. * Create a where clause that will make sure post queries
  284. * will always return results prior to (descending sort)
  285. * or before (ascending sort) the last post date.
  286. *
  287. * @param string $where
  288. * @param object $query
  289. * @filter posts_where
  290. * @return string
  291. */
  292. function query_time_filter( $where, $query ) {
  293. global $wpdb;
  294. $operator = 'ASC' == $query->get( 'order' ) ? '>' : '<';
  295. // Construct the date query using our timestamp
  296. $where .= $wpdb->prepare( " AND post_date_gmt {$operator} %s", self::set_last_post_time() );
  297. return $where;
  298. }
  299. /**
  300. * Let's overwrite the default post_per_page setting to always display a fixed amount.
  301. *
  302. * @global $wp_the_query Used to provide compatibility back to WP 3.2
  303. * @param object $query
  304. * @uses self::archive_supports_infinity, self::get_settings
  305. * @return null
  306. */
  307. function posts_per_page_query( $query ) {
  308. global $wp_the_query;
  309. if ( self::archive_supports_infinity() && $query === $wp_the_query ) // After 3.3, this line would be: if ( self::archive_supports_infinity() && $query->is_main_query() )
  310. $query->set( 'posts_per_page', self::get_settings()->posts_per_page );
  311. }
  312. /**
  313. * Check if the IS output should be wrapped in a div.
  314. * Setting value can be a boolean or a string specifying the class applied to the div.
  315. *
  316. * @uses self::get_settings
  317. * @return bool
  318. */
  319. function has_wrapper() {
  320. return (bool) self::get_settings()->wrapper;
  321. }
  322. /**
  323. * Returns the Ajax url
  324. */
  325. function ajax_url() {
  326. global $wp;
  327. $base_url = home_url( trailingslashit( $wp->request ), is_ssl() ? 'https' : 'http' );
  328. $ajaxurl = add_query_arg( array( 'infinity' => 'scrolling' ), $base_url );
  329. return apply_filters( 'infinite_scroll_ajax_url', $ajaxurl );
  330. }
  331. /**
  332. * Our own Ajax response, avoiding calling admin-ajax
  333. */
  334. function ajax_response() {
  335. // Only proceed if the url query has a key of "Infinity"
  336. if ( ! self::got_infinity() )
  337. return false;
  338. define( 'DOING_AJAX', true );
  339. @header( 'Content-Type: text/html; charset=' . get_option( 'blog_charset' ) );
  340. send_nosniff_header();
  341. do_action( 'custom_ajax_infinite_scroll' );
  342. die( '0' );
  343. }
  344. /**
  345. * Prints the relevant infinite scroll settings in JS.
  346. *
  347. * @uses self::get_settings, esc_js, esc_url_raw, self::has_wrapper, __, apply_filters, do_action
  348. * @action wp_head
  349. * @return string
  350. */
  351. function action_wp_head() {
  352. global $wp_query, $wp_rewrite;
  353. // Base JS settings
  354. $js_settings = array(
  355. 'id' => self::get_settings()->container,
  356. 'ajaxurl' => esc_js( esc_url_raw( self::ajax_url() ) ),
  357. 'type' => esc_js( self::get_settings()->type ),
  358. 'wrapper' => self::has_wrapper(),
  359. 'wrapper_class' => is_string( self::get_settings()->wrapper ) ? esc_js( self::get_settings()->wrapper ) : 'infinite-wrap',
  360. 'footer' => is_string( self::get_settings()->footer ) ? esc_js( self::get_settings()->footer ) : self::get_settings()->footer,
  361. 'text' => esc_js( __( 'Load more posts', 'jetpack' ) ),
  362. 'totop' => esc_js( __( 'Scroll back to top', 'jetpack' ) ),
  363. 'order' => 'DESC',
  364. 'scripts' => array(),
  365. 'styles' => array(),
  366. 'google_analytics' => false,
  367. 'offset' => $wp_query->get( 'paged' ),
  368. 'history' => array(
  369. 'host' => preg_replace( '#^http(s)?://#i', '', untrailingslashit( get_option( 'home' ) ) ),
  370. 'path' => self::get_request_path(),
  371. 'use_trailing_slashes' => $wp_rewrite->use_trailing_slashes
  372. )
  373. );
  374. // Optional order param
  375. if ( isset( $_GET['order'] ) ) {
  376. $order = strtoupper( $_GET['order'] );
  377. if ( in_array( $order, array( 'ASC', 'DESC' ) ) )
  378. $js_settings['order'] = $order;
  379. }
  380. $js_settings = apply_filters( 'infinite_scroll_js_settings', $js_settings );
  381. do_action( 'infinite_scroll_wp_head' );
  382. ?>
  383. <script type="text/javascript">
  384. //<![CDATA[
  385. var infiniteScroll = <?php echo json_encode( array( 'settings' => $js_settings ) ); ?>;
  386. //]]>
  387. </script>
  388. <?php
  389. }
  390. /**
  391. * Build path data for current request.
  392. * Used for Google Analytics and pushState history tracking.
  393. *
  394. * @global $wp_rewrite
  395. * @global $wp
  396. * @uses user_trailingslashit, sanitize_text_field, add_query_arg
  397. * @return string|bool
  398. */
  399. private function get_request_path() {
  400. global $wp_rewrite;
  401. if ( $wp_rewrite->using_permalinks() ) {
  402. global $wp;
  403. // If called too early, bail
  404. if ( ! isset( $wp->request ) )
  405. return false;
  406. // Determine path for paginated version of current request
  407. if ( false != preg_match( '#' . $wp_rewrite->pagination_base . '/\d+/?$#i', $wp->request ) )
  408. $path = preg_replace( '#' . $wp_rewrite->pagination_base . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
  409. else
  410. $path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
  411. // Slashes everywhere we need them
  412. if ( 0 !== strpos( $path, '/' ) )
  413. $path = '/' . $path;
  414. $path = user_trailingslashit( $path );
  415. }
  416. else {
  417. // Clean up raw $_GET input
  418. $path = array_map( 'sanitize_text_field', $_GET );
  419. $path = array_filter( $path );
  420. $path['paged'] = '%d';
  421. $path = add_query_arg( $path, '/' );
  422. }
  423. return empty( $path ) ? false : $path;
  424. }
  425. /**
  426. * Provide IS with a list of the scripts and stylesheets already present on the page.
  427. * Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
  428. *
  429. * @global $wp_scripts, $wp_styles
  430. * @action wp_footer
  431. * @return string
  432. */
  433. function action_wp_footer() {
  434. global $wp_scripts, $wp_styles;
  435. $scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
  436. $styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
  437. ?><script type="text/javascript">
  438. jQuery.extend( infiniteScroll.settings.scripts, <?php echo json_encode( $scripts ); ?> );
  439. jQuery.extend( infiniteScroll.settings.styles, <?php echo json_encode( $styles ); ?> );
  440. </script><?php
  441. }
  442. /**
  443. * Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
  444. *
  445. * @global $wp_scripts
  446. * @uses sanitize_text_field, add_query_arg
  447. * @filter infinite_scroll_results
  448. * @return array
  449. */
  450. function filter_infinite_scroll_results( $results ) {
  451. // Don't bother unless there are posts to display
  452. if ( 'success' != $results['type'] )
  453. return $results;
  454. // Parse and sanitize the script handles already output
  455. $initial_scripts = isset( $_GET['scripts'] ) && is_array( $_GET['scripts'] ) ? array_map( 'sanitize_text_field', $_GET['scripts'] ) : false;
  456. if ( is_array( $initial_scripts ) ) {
  457. global $wp_scripts;
  458. // Identify new scripts needed by the latest set of IS posts
  459. $new_scripts = array_diff( $wp_scripts->done, $initial_scripts );
  460. // If new scripts are needed, extract relevant data from $wp_scripts
  461. if ( ! empty( $new_scripts ) ) {
  462. $results['scripts'] = array();
  463. foreach ( $new_scripts as $handle ) {
  464. // Abort if somehow the handle doesn't correspond to a registered script
  465. if ( ! isset( $wp_scripts->registered[ $handle ] ) )
  466. continue;
  467. // Provide basic script data
  468. $script_data = array(
  469. 'handle' => $handle,
  470. 'footer' => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer ) ),
  471. 'extra_data' => $wp_scripts->print_extra_script( $handle, false )
  472. );
  473. // Base source
  474. $src = $wp_scripts->registered[ $handle ]->src;
  475. // Take base_url into account
  476. if ( strpos( $src, 'http' ) !== 0 )
  477. $src = $wp_scripts->base_url . $src;
  478. // Version and additional arguments
  479. if ( null === $wp_scripts->registered[ $handle ]->ver )
  480. $ver = '';
  481. else
  482. $ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
  483. if ( isset($wp_scripts->args[ $handle ] ) )
  484. $ver = $ver ? $ver . '&amp;' . $wp_scripts->args[$handle] : $wp_scripts->args[$handle];
  485. // Full script source with version info
  486. $script_data['src'] = add_query_arg( 'ver', $ver, $src );
  487. // Add script to data that will be returned to IS JS
  488. array_push( $results['scripts'], $script_data );
  489. }
  490. }
  491. }
  492. // Parse and sanitize the style handles already output
  493. $initial_styles = isset( $_GET['styles'] ) && is_array( $_GET['styles'] ) ? array_map( 'sanitize_text_field', $_GET['styles'] ) : false;
  494. if ( is_array( $initial_styles ) ) {
  495. global $wp_styles;
  496. // Identify new styles needed by the latest set of IS posts
  497. $new_styles = array_diff( $wp_styles->done, $initial_styles );
  498. // If new styles are needed, extract relevant data from $wp_styles
  499. if ( ! empty( $new_styles ) ) {
  500. $results['styles'] = array();
  501. foreach ( $new_styles as $handle ) {
  502. // Abort if somehow the handle doesn't correspond to a registered stylesheet
  503. if ( ! isset( $wp_styles->registered[ $handle ] ) )
  504. continue;
  505. // Provide basic style data
  506. $style_data = array(
  507. 'handle' => $handle,
  508. 'media' => 'all'
  509. );
  510. // Base source
  511. $src = $wp_styles->registered[ $handle ]->src;
  512. // Take base_url into account
  513. if ( strpos( $src, 'http' ) !== 0 )
  514. $src = $wp_styles->base_url . $src;
  515. // Version and additional arguments
  516. if ( null === $wp_styles->registered[ $handle ]->ver )
  517. $ver = '';
  518. else
  519. $ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
  520. if ( isset($wp_styles->args[ $handle ] ) )
  521. $ver = $ver ? $ver . '&amp;' . $wp_styles->args[$handle] : $wp_styles->args[$handle];
  522. // Full stylesheet source with version info
  523. $style_data['src'] = add_query_arg( 'ver', $ver, $src );
  524. // Parse stylesheet's conditional comments if present, converting to logic executable in JS
  525. if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
  526. // First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
  527. $style_data['conditional'] = str_replace( array(
  528. 'lte',
  529. 'lt',
  530. 'gte',
  531. 'gt'
  532. ), array(
  533. '%ver <=',
  534. '%ver <',
  535. '%ver >=',
  536. '%ver >',
  537. ), $wp_styles->registered[ $handle ]->extra['conditional'] );
  538. // 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().
  539. $style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
  540. // Lastly, remove the IE strings
  541. $style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
  542. }
  543. // Parse requested media context for stylesheet
  544. if ( isset( $wp_styles->registered[ $handle ]->args ) )
  545. $style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
  546. // Add stylesheet to data that will be returned to IS JS
  547. array_push( $results['styles'], $style_data );
  548. }
  549. }
  550. }
  551. // Lastly, return the IS results array
  552. return $results;
  553. }
  554. /**
  555. * Runs the query and returns the results via JSON.
  556. * Triggered by an AJAX request.
  557. *
  558. * @global $wp_query
  559. * @global $wp_the_query
  560. * @uses current_user_can, get_option, self::set_last_post_time, 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
  561. * @return string or null
  562. */
  563. function query() {
  564. global $wp_query, $wp_the_query;
  565. if ( ! isset( $_GET['page'] ) || ! current_theme_supports( 'infinite-scroll' ) )
  566. die;
  567. $page = (int) $_GET['page'];
  568. $sticky = get_option( 'sticky_posts' );
  569. if ( ! empty( $_GET['date'] ) )
  570. self::set_last_post_time( $_GET['date'] );
  571. $post_status = array( 'publish' );
  572. if ( current_user_can( 'read_private_posts' ) )
  573. array_push( $post_status, 'private' );
  574. $order = in_array( $_GET['order'], array( 'ASC', 'DESC' ) ) ? $_GET['order'] : 'DESC';
  575. $query_args = array_merge( $wp_the_query->query_vars, array(
  576. 'paged' => $page,
  577. 'post_status' => $post_status,
  578. 'posts_per_page' => self::get_settings()->posts_per_page,
  579. 'post__not_in' => ( array ) $sticky,
  580. 'order' => $order
  581. ) );
  582. // By default, don't query for a specific page of a paged post object.
  583. // This argument comes from merging $wp_the_query.
  584. // Since IS is only used on archives, we should always display the first page of any paged content.
  585. unset( $query_args['page'] );
  586. $query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
  587. // Add query filter that checks for posts below the date
  588. add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
  589. $wp_query = new WP_Query( $query_args );
  590. remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
  591. $results = array();
  592. if ( have_posts() ) {
  593. // Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
  594. ob_start();
  595. wp_head();
  596. ob_end_clean();
  597. $results['type'] = 'success';
  598. // First, try theme's specified rendering handler, either specified via `add_theme_support` or by hooking to this action directly.
  599. ob_start();
  600. do_action( 'infinite_scroll_render' );
  601. $results['html'] = ob_get_clean();
  602. // 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.
  603. if ( empty( $results['html'] ) ) {
  604. add_action( 'infinite_scroll_render', array( $this, 'render' ) );
  605. rewind_posts();
  606. ob_start();
  607. do_action( 'infinite_scroll_render' );
  608. $results['html'] = ob_get_clean();
  609. }
  610. // If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
  611. if ( empty( $results['html'] ) ) {
  612. unset( $results['html'] );
  613. do_action( 'infinite_scroll_empty' );
  614. $results['type'] = 'empty';
  615. }
  616. elseif ( $this->has_wrapper() ) {
  617. $wrapper_classes = is_string( self::get_settings()->wrapper ) ? self::get_settings()->wrapper : 'infinite-wrap';
  618. $wrapper_classes .= ' infinite-view-' . $page;
  619. $wrapper_classes = trim( $wrapper_classes );
  620. $results['html'] = '<div class="' . esc_attr( $wrapper_classes ) . '" id="infinite-view-' . $page . '" data-page-num="' . $page . '">' . $results['html'] . '</div>';
  621. }
  622. // Fire wp_footer to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
  623. ob_start();
  624. wp_footer();
  625. ob_end_clean();
  626. // Loop through posts to capture sharing data for new posts loaded via Infinite Scroll
  627. if ( 'success' == $results['type'] && function_exists( 'sharing_register_post_for_share_counts' ) ) {
  628. global $jetpack_sharing_counts;
  629. while( have_posts() ) {
  630. the_post();
  631. sharing_register_post_for_share_counts( get_the_ID() );
  632. }
  633. $results['postflair'] = array_flip( $jetpack_sharing_counts );
  634. }
  635. } else {
  636. do_action( 'infinite_scroll_empty' );
  637. $results['type'] = 'empty';
  638. }
  639. echo json_encode( apply_filters( 'infinite_scroll_results', $results ) );
  640. die;
  641. }
  642. /**
  643. * Rendering fallback used when themes don't specify their own handler.
  644. *
  645. * @uses have_posts, the_post, get_template_part, get_post_format
  646. * @action infinite_scroll_render
  647. * @return string
  648. */
  649. function render() {
  650. while ( have_posts() ) {
  651. the_post();
  652. get_template_part( 'content', get_post_format() );
  653. }
  654. }
  655. /**
  656. * Allow plugins to filter what archives Infinite Scroll supports
  657. *
  658. * @uses apply_filters, current_theme_supports, is_home, is_archive, self::get_settings
  659. * @return bool
  660. */
  661. public function archive_supports_infinity() {
  662. return (bool) apply_filters( 'infinite_scroll_archive_supported', current_theme_supports( 'infinite-scroll' ) && ( is_home() || is_archive() ), self::get_settings() );
  663. }
  664. /**
  665. * The Infinite Blog Footer
  666. *
  667. * @uses self::get_settings, self::set_last_post_time, self::archive_supports_infinity, __, wp_get_theme, get_current_theme, apply_filters, home_url, esc_attr, get_bloginfo, bloginfo
  668. * @return string or null
  669. */
  670. function footer() {
  671. // Bail if theme requested footer not show
  672. if ( false == self::get_settings()->footer )
  673. return;
  674. // Bail if there are not enough posts for infinity.
  675. if ( ! self::set_last_post_time() )
  676. return;
  677. // We only need the new footer for the 'scroll' type
  678. if ( 'scroll' != self::get_settings()->type || ! self::archive_supports_infinity() )
  679. return;
  680. $credits = '<a href="http://wordpress.org/" rel="generator">Proudly powered by WordPress</a> ';
  681. $credits .= sprintf( __( 'Theme: %1$s.', 'jetpack' ), function_exists( 'wp_get_theme' ) ? wp_get_theme()->Name : get_current_theme() );
  682. $credits = apply_filters( 'infinite_scroll_credit', $credits );
  683. ?>
  684. <div id="infinite-footer">
  685. <div class="container">
  686. <div class="blog-info">
  687. <a id="infinity-blog-title" href="<?php echo home_url( '/' ); ?>" title="<?php echo esc_attr( get_bloginfo( 'name', 'display' ) ); ?>" rel="home">
  688. <?php bloginfo( 'name' ); ?>
  689. </a>
  690. </div>
  691. <div class="blog-credits">
  692. <?php echo $credits; ?>
  693. </div>
  694. </div>
  695. </div><!-- #infinite-footer -->
  696. <?php
  697. }
  698. /**
  699. * Ensure that IS doesn't interfere with Grunion by stripping IS query arguments from the Grunion redirect URL.
  700. * When arguments are present, Grunion redirects to the IS AJAX endpoint.
  701. *
  702. * @param string $url
  703. * @uses remove_query_arg
  704. * @filter grunion_contact_form_redirect_url
  705. * @return string
  706. */
  707. public function filter_grunion_redirect_url( $url ) {
  708. // Remove IS query args, if present
  709. if ( false !== strpos( $url, 'infinity=scrolling' ) ) {
  710. $url = remove_query_arg( array(
  711. 'infinity',
  712. 'action',
  713. 'page',
  714. 'order',
  715. 'scripts',
  716. 'styles'
  717. ), $url );
  718. }
  719. return $url;
  720. }
  721. };
  722. /**
  723. * Initialize The_Neverending_Home_Page
  724. */
  725. function the_neverending_home_page_init() {
  726. if ( ! current_theme_supports( 'infinite-scroll' ) )
  727. return;
  728. new The_Neverending_Home_Page;
  729. }
  730. add_action( 'init', 'the_neverending_home_page_init', 20 );
  731. /**
  732. * Check whether the current theme is infinite-scroll aware.
  733. * If so, include the files which add theme support.
  734. */
  735. function the_neverending_home_page_theme_support() {
  736. $theme_name = get_stylesheet();
  737. $customization_file = apply_filters( 'infinite_scroll_customization_file', dirname( __FILE__ ) . "/themes/{$theme_name}.php", $theme_name );
  738. if ( is_readable( $customization_file ) )
  739. require_once( $customization_file );
  740. }
  741. add_action( 'after_setup_theme', 'the_neverending_home_page_theme_support', 5 );