* @uses user_trailingslashit, sanitize_text_field, add_query_arg
private function get_request_path() {
if ( $wp_rewrite->using_permalinks() ) {
// If called too early, bail
if ( ! isset( $wp->request ) ) {
// Determine path for paginated version of current request
if ( preg_match( '#' . preg_quote( $wp_rewrite->pagination_base, '#' ) . '/\d+/?$#i', $wp->request ) ) {
$path = preg_replace( '#' . preg_quote( $wp_rewrite->pagination_base, '#' ) . '/\d+$#i', $wp_rewrite->pagination_base . '/%d', $wp->request );
$path = $wp->request . '/' . $wp_rewrite->pagination_base . '/%d';
// Slashes everywhere we need them
if ( ! str_starts_with( $path, '/' ) ) {
$path = user_trailingslashit( $path );
// Clean up raw $_REQUEST input
$path = array_map( 'sanitize_text_field', wp_unslash( $_REQUEST ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- seems this is used for Google Analytics and browser history tracking.
$path = array_filter( $path );
$path = add_query_arg( $path, '/' );
return empty( $path ) ? false : $path;
* Return query string for current request, prefixed with '?'.
private function get_request_parameters() {
$uri = isset( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
$uri = preg_replace( '/^[^?]*(\?.*$)/', '$1', $uri, 1, $count );
* Provide IS with a list of the scripts and stylesheets already present on the page.
* Since posts may contain require additional assets that haven't been loaded, this data will be used to track the additional assets.
* @global $wp_scripts, $wp_styles
public function action_wp_footer() {
global $wp_scripts, $wp_styles;
$scripts = is_a( $wp_scripts, 'WP_Scripts' ) ? $wp_scripts->done : array();
* Filter the list of scripts already present on the page.
* @module infinite-scroll
* @param array $scripts Array of scripts present on the page.
$scripts = apply_filters( 'infinite_scroll_existing_scripts', $scripts );
$styles = is_a( $wp_styles, 'WP_Styles' ) ? $wp_styles->done : array();
* Filter the list of styles already present on the page.
* @module infinite-scroll
* @param array $styles Array of styles present on the page.
$styles = apply_filters( 'infinite_scroll_existing_stylesheets', $styles );
<script type="text/javascript">
var extend = function(out) {
for (var i = 1; i < arguments.length; i++) {
for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key))
out[key] = arguments[i][key];
extend( window.infiniteScroll.settings.scripts, <?php echo wp_json_encode( $scripts, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
extend( window.infiniteScroll.settings.styles, <?php echo wp_json_encode( $styles, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ); ?> );
$aria_live = 'assertive';
if ( 'scroll' === self::get_settings()->type ) {
<span id="infinite-aria" aria-live="<?php echo esc_attr( $aria_live ); ?>"></span>
* Identify additional scripts required by the latest set of IS posts and provide the necessary data to the IS response handler.
* @param array $results - the results.
* @param array $query_args - Array of Query arguments.
* @param array $wp_query - the WP query.
* @uses sanitize_text_field, add_query_arg
* @filter infinite_scroll_results
public function filter_infinite_scroll_results( $results, $query_args, $wp_query ) {
// Don't bother unless there are posts to display
if ( 'success' !== $results['type'] ) {
// Parse and sanitize the script handles already output
$initial_scripts = isset( $_REQUEST['scripts'] ) && is_array( $_REQUEST['scripts'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['scripts'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no site changes made.
if ( is_array( $initial_scripts ) ) {
// Identify new scripts needed by the latest set of IS posts
$new_scripts = array_filter(
function ( $script_name ) use ( $initial_scripts ) {
// Jetpack block scripts should always be sent, even if they've been
// sent before. These scripts only run once on when loaded, they don't
// watch for new blocks being added.
if ( str_starts_with( $script_name, 'jetpack-block-' ) ) {
return ! in_array( $script_name, $initial_scripts, true );
// If new scripts are needed, extract relevant data from $wp_scripts
if ( ! empty( $new_scripts ) ) {
$results['scripts'] = array();
foreach ( $new_scripts as $handle ) {
// Abort if somehow the handle doesn't correspond to a registered script
// or if the script doesn't have `src` set.
$script_not_registered = ! isset( $wp_scripts->registered[ $handle ] );
$empty_src = empty( $wp_scripts->registered[ $handle ]->src );
if ( $script_not_registered || $empty_src ) {
$before_handle = $wp_scripts->get_inline_script_data( $handle, 'before' );
$after_handle = $wp_scripts->get_inline_script_data( $handle, 'after' );
// Provide basic script data
'footer' => ( is_array( $wp_scripts->in_footer ) && in_array( $handle, $wp_scripts->in_footer, true ) ),
'extra_data' => $wp_scripts->print_extra_script( $handle, false ),
'before_handle' => $before_handle,
'after_handle' => $after_handle,
$src = $wp_scripts->registered[ $handle ]->src;
// Take base_url into account
if ( strpos( $src, 'http' ) !== 0 ) {
$src = $wp_scripts->base_url . $src;
// Version and additional arguments
if ( null === $wp_scripts->registered[ $handle ]->ver ) {
$ver = $wp_scripts->registered[ $handle ]->ver ? $wp_scripts->registered[ $handle ]->ver : $wp_scripts->default_version;
if ( isset( $wp_scripts->args[ $handle ] ) ) {
$ver = $ver ? $ver . '&' . $wp_scripts->args[ $handle ] : $wp_scripts->args[ $handle ];
// Full script source with version info
$script_data['src'] = add_query_arg( 'ver', $ver, $src );
// Add script to data that will be returned to IS JS
array_push( $results['scripts'], $script_data );
// Expose additional script data to filters, but only include in final `$results` array if needed.
if ( ! isset( $results['scripts'] ) ) {
$results['scripts'] = array();
* Filter the additional scripts required by the latest set of IS posts.
* @module infinite-scroll
* @param array $results['scripts'] Additional scripts required by the latest set of IS posts.
* @param array|bool $initial_scripts Set of scripts loaded on each page.
* @param array $results Array of Infinite Scroll results.
* @param array $query_args Array of Query arguments.
* @param WP_Query $wp_query WP Query.
$results['scripts'] = apply_filters(
'infinite_scroll_additional_scripts',
if ( empty( $results['scripts'] ) ) {
unset( $results['scripts'] );
// Parse and sanitize the style handles already output
$initial_styles = isset( $_REQUEST['styles'] ) && is_array( $_REQUEST['styles'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['styles'] ) ) : false; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( is_array( $initial_styles ) ) {
// Identify new styles needed by the latest set of IS posts
$new_styles = array_diff( $wp_styles->done, $initial_styles );
// If new styles are needed, extract relevant data from $wp_styles
if ( ! empty( $new_styles ) ) {
$results['styles'] = array();
foreach ( $new_styles as $handle ) {
// Abort if somehow the handle doesn't correspond to a registered stylesheet
if ( ! isset( $wp_styles->registered[ $handle ] ) ) {
// Provide basic style data
$src = $wp_styles->registered[ $handle ]->src;
// Take base_url into account
if ( strpos( $src, 'http' ) !== 0 ) {
$src = $wp_styles->base_url . $src;
// Version and additional arguments
if ( null === $wp_styles->registered[ $handle ]->ver ) {
$ver = $wp_styles->registered[ $handle ]->ver ? $wp_styles->registered[ $handle ]->ver : $wp_styles->default_version;
if ( isset( $wp_styles->args[ $handle ] ) ) {
$ver = $ver ? $ver . '&' . $wp_styles->args[ $handle ] : $wp_styles->args[ $handle ];
// Full stylesheet source with version info
$style_data['src'] = add_query_arg( 'ver', $ver, $src );
// Parse stylesheet's conditional comments if present, converting to logic executable in JS
if ( isset( $wp_styles->registered[ $handle ]->extra['conditional'] ) && $wp_styles->registered[ $handle ]->extra['conditional'] ) {
// First, convert conditional comment operators to standard logical operators. %ver is replaced in JS with the IE version
$style_data['conditional'] = str_replace(
$wp_styles->registered[ $handle ]->extra['conditional']
// 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().
$style_data['conditional'] = preg_replace( '#!\s*IE(\s*\d+){0}#i', '1==2', $style_data['conditional'] );
// Lastly, remove the IE strings
$style_data['conditional'] = str_replace( 'IE', '', $style_data['conditional'] );
// Parse requested media context for stylesheet
if ( isset( $wp_styles->registered[ $handle ]->args ) ) {
$style_data['media'] = esc_attr( $wp_styles->registered[ $handle ]->args );
// Add stylesheet to data that will be returned to IS JS
array_push( $results['styles'], $style_data );
// Expose additional stylesheet data to filters, but only include in final `$results` array if needed.
if ( ! isset( $results['styles'] ) ) {
$results['styles'] = array();
* Filter the additional styles required by the latest set of IS posts.
* @module infinite-scroll
* @param array $results['styles'] Additional styles required by the latest set of IS posts.
* @param array|bool $initial_styles Set of styles loaded on each page.
* @param array $results Array of Infinite Scroll results.
* @param array $query_args Array of Query arguments.
* @param WP_Query $wp_query WP Query.
$results['styles'] = apply_filters(
'infinite_scroll_additional_stylesheets',
if ( empty( $results['styles'] ) ) {
unset( $results['styles'] );
// Lastly, return the IS results array
* Runs the query and returns the results via JSON.
* Triggered by an AJAX request.
* @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
public function query() {
if ( ! isset( $_REQUEST['page'] ) || ! current_theme_supports( 'infinite-scroll' ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no changes to the site.
// @todo see if we should validate this nonce since we use it to form a query.
$page = (int) $_REQUEST['page']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- we're casting this to an int and not making changes to the site.
// Sanitize and set $previousday. Expected format: dd.mm.yy
if ( isset( $_REQUEST['currentday'] ) && is_string( $_REQUEST['currentday'] ) && preg_match( '/^\d{2}\.\d{2}\.\d{2}$/', $_REQUEST['currentday'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput, WordPress.Security.NonceVerification.Recommended -- manually validating, no changes to site
$previousday = $_REQUEST['currentday']; // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput
$post_status = array( 'publish' );
if ( current_user_can( 'read_private_posts' ) ) {
array_push( $post_status, 'private' );
$order = isset( $_REQUEST['order'] ) && in_array( $_REQUEST['order'], array( 'ASC', 'DESC' ), true ) ? sanitize_text_field( wp_unslash( $_REQUEST['order'] ) ) : 'DESC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- no changes made to the site.
$query_args = array_merge(
self::wp_query()->query_vars,
'post_status' => $post_status,
'posts_per_page' => self::posts_per_page(), // phpcs:ignore WordPress.WP.PostsPerPage.posts_per_page_posts_per_page
// 4.0 ?s= compatibility, see https://core.trac.wordpress.org/ticket/11330#comment:50
if ( empty( $query_args['s'] ) && ! isset( self::wp_query()->query['s'] ) ) {
unset( $query_args['s'] );
// By default, don't query for a specific page of a paged post object.
// This argument can come from merging self::wp_query() into $query_args above.
// Since IS is only used on archives, we should always display the first page of any paged content.
unset( $query_args['page'] );
* Filter the array of main query arguments.
* @module infinite-scroll
* @param array $query_args Array of Query arguments.
$query_args = apply_filters( 'infinite_scroll_query_args', $query_args );
add_filter( 'posts_where', array( $this, 'query_time_filter' ), 10, 2 );
$infinite_scroll_query = new WP_Query();
$GLOBALS['wp_the_query'] = $infinite_scroll_query;
$GLOBALS['wp_query'] = $infinite_scroll_query;
$infinite_scroll_query->query( $query_args );
remove_filter( 'posts_where', array( $this, 'query_time_filter' ), 10 );
// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
while ( ob_get_length() ) {
$results['type'] = 'success';
* Fires when rendering Infinite Scroll posts.
* @module infinite-scroll
do_action( 'infinite_scroll_render' );
$results['html'] = ob_get_clean();
if ( empty( $results['html'] ) ) {
* Gather renderer callbacks. These will be called in order and allow multiple callbacks to be queued. Once content is found, no futher callbacks will run.
* @module infinite-scroll
$callbacks = apply_filters(
'infinite_scroll_render_callbacks',
array( self::get_settings()->render ) // This is the setting callback e.g. from add theme support.
// Append fallback callback. That rhymes.
$callbacks[] = array( $this, 'render' );
foreach ( $callbacks as $callback ) {
if ( false !== $callback && is_callable( $callback ) ) {
add_action( 'infinite_scroll_render', $callback );
* This action is already documented above.
* See https://github.com/Automattic/jetpack/pull/16317/
* for more details as to why it was introduced.
do_action( 'infinite_scroll_render' );
// Fire wp_head to ensure that all necessary scripts are enqueued. Output isn't used, but scripts are extracted in self::action_wp_footer.
$results['html'] = ob_get_clean();
remove_action( 'infinite_scroll_render', $callback );
if ( ! empty( $results['html'] ) ) {
// If primary and fallback rendering methods fail, prevent further IS rendering attempts. Otherwise, wrap the output if requested.
if ( empty( $results['html'] ) ) {
unset( $results['html'] );
* Fires when Infinite Scoll doesn't render any posts.