1

I have multiple wp_query's on the same page. Each one of those queries has an AJAX "read more" button at the bottom. The problem I'm finding is that I can only get one to work at a time. Whichever function is added first in the functions.php, that one works - the other one gets a 403 error for admin-ajax.php.

I'm pretty new to AJAX, so I've probably made a total hash of it, and I'm sure there's a way to combine this into a single function (preferred!) but if I can even just find a way for them all to work independently, that work be fine.

Here's my code:

The 2 WP_Queries in a custom page-work.php template:

First one:

<?php
    //Find out how many posts 
    $total_posts = wp_count_posts('smart_maps');
    $total_posts = $total_posts->publish;
    $number_shown = 0;


     $the_query = new WP_Query( $args ); ?>
    <?php if ( $the_query->have_posts() ) : ?>
    <div id="smartmaps" class="smartmaps col xs-col-14 xs-offset-1 md-col-12 md-offset-2" style="display:none;">
        <div class="in-here-smartmaps">
            <?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
            <a href="<?php echo get_permalink(get_the_ID());?>">
                <div class="single-smartmap col xs-col-16 md-col-8">
                    <div class="col xs-col-8 xs-offset-4 md-col-4 md-offset-0 center">
                        <div class="image-element">
                            <img src="<?php the_field('thumbnail_image');?>" class="circle">
                            <div class="image-overlay circle"></div>
                        </div>
                    </div>
                    <div class="col xs-col-14 xs-offset-1 md-col-10 md-offset-1">
                        <h5 class="smart-map-title"><?php the_title();?></h5>
                        <?php if(get_field('icons')) :?>
                            <div class="block-icons">
                                <?php if(in_array('Heart', get_field('icons'))) :?>
                                    <span class="icon-heart"></span>
                                <?php endif;?>
                                <?php if(in_array('Plant', get_field('icons'))) :?>
                                    <span class="icon-plant"></span>
                                <?php endif; ?>
                                <?php if(in_array('Cup', get_field('icons'))) :?>
                                    <span class="icon-cup"></span>
                                <?php endif;?>
                                <?php if(in_array('Book', get_field('icons'))) :?>
                                    <span class="icon-book"></span>
                                <?php endif;?>
                            </div>
                        <?php endif;?>
                    </div>
                </div>
            </a>
                <?php $number_shown++; 
            endwhile; ?>
        <?php endif;?>
        </div>
    <?php if($number_shown != $total_posts) :?>
        <div class="loadmore smarties col xs-col-14 xs-offset-1 md-col-8 md-offset-4 center">
            <h3><a href="#">LOAD MORE</a></h3>
        </div>
    <?php endif;?>

    </div>
<div clas="clearfix"></div>

The second 1:

<div id="strategic-events" class="strategicevents col xs-col-14 xs-offset-1 md-col-12 md-offset-2" style="display:none;">
    <div class="in-here-strats">
    <?php
    //Find out how many posts 
    $total_posts = wp_count_posts('strategic_events');
    $total_posts = $total_posts->publish;
    $number_shown = 0;

    $args = array( 'post_type' => 'strategic_events', 'posts_per_page' => 10, 'offset' => $the_offset );


     $the_query2 = new WP_Query( $args ); ?>
    <?php if ( $the_query2->have_posts() ) : 
        while ( $the_query2->have_posts() ) : $the_query2->the_post(); ?>
        <a href="<?php echo get_permalink(get_the_ID());?>">    
            <div class="single-strategicevent col xs-col-16 md-col-8">
                <div class="col xs-col-8 xs-offset-4 md-col-4 md-offset-0 center">
                    <div class="image-element">
                        <img src="<?php the_field('strategic_event_image');?>" class="circle">
                        <div class="image-overlay circle"></div>
                    </div>
                </div>
                <div class="col xs-col-14 xs-offset-1 md-col-10 md-offset-1">
                    <h5 class="strategic-event-title"><?php the_title();?></h5>
                    <?php if(get_field('subtitle')) :?>
                        <p><?php the_field('subtitle');?></p>   
                        <small><?php the_field('location_text');?></small>          
                    <?php endif;?>
                </div>
            </div>
        </a>
            <?php $number_shown++; 
        endwhile; 
    endif; ?>
    </div>
    <?php if($number_shown != $total_posts) :?>
        <div class="loadmore strats col xs-col-14 xs-offset-1 md-col-8 md-offset-4 center">
            <h3><a href="#">LOAD MORE</a></h3>
        </div>
    <?php endif;?>

</div>
<div class="clearfix"></div>

My JS code (both also in the page-work.php for now)...

<script>
var ajaxurl = "<?php echo admin_url( 'admin-ajax.php' ); ?>";
var page = 2;
jQuery(function($) {
    $('body').on('click', '.loadmore.strats', function(e) {
        e.preventDefault();
        var data = {
            'action': 'load_posts_by_ajax',
            'page': page,
            'security': '<?php echo wp_create_nonce("load_strats_posts"); ?>',
            'max_page': '<?php global $wp_query; echo $wp_query->max_num_pages;?>'
        };

        $.post(ajaxurl, data, function(response) {
            $('.in-here-strats').append(response);
            page++;
        });
    });
});

<script>
var ajaxurl = "<?php echo admin_url( 'admin-ajax.php' ); ?>";
var page = 2;
jQuery(function($) {
    $('body').on('click', '.loadmore.smarties', function(e) {
        e.preventDefault();
        var data = {
            'action': 'load_posts_by_ajax',
            'page': page,
            'security2': '<?php echo wp_create_nonce("load_smartmaps_posts"); ?>',
            'max_page': '<?php global $wp_query; echo $wp_query->max_num_pages;?>'
        };

        $.post(ajaxurl, data, function(response) {
            $('.in-here-smartmaps').append(response);
            page++;
        });
    });
});
</script>

And the AJAX functions in my function.php:

//Load More Smart Maps 
add_action('wp_ajax_load_posts_by_ajax', 'load_smart_maps_by_ajax_callback');
add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_smart_maps_by_ajax_callback');
function load_smart_maps_by_ajax_callback() {
check_ajax_referer('load_smartmaps_posts', 'security2');
$paged = $_POST['page'];
$args = array(
    'post_type' => 'smart_maps',
    'post_status' => 'publish',
    'posts_per_page' => '10',
    'paged' => $paged,
);
$my_posts = new WP_Query( $args );
if ( $my_posts->have_posts() ) :
    ?>
    <?php 
        $total_posts = wp_count_posts('smart_maps');
        $total_posts = $total_posts->publish;
        $number_shown = $paged * 10 - 10;?>
    <?php while ( $my_posts->have_posts() ) : $my_posts->the_post() ?>

            <a href="<?php echo get_permalink(get_the_ID());?>">
                <div class="single-smartmap col xs-col-16 md-col-8">
                    <div class="col xs-col-8 xs-offset-4 md-col-4 md-offset-0 center">
                        <div class="image-element">
                            <img src="<?php the_field('thumbnail_image');?>" class="circle">
                            <div class="image-overlay circle"></div>
                        </div>
                    </div>
                    <div class="col xs-col-14 xs-offset-1 md-col-10 md-offset-1">
                        <h5 class="smart-map-title"><?php the_title();?></h5>
                        <?php if(get_field('icons')) :?>
                            <div class="block-icons">
                                <?php if(in_array('Heart', get_field('icons'))) :?>
                                    <span class="icon-heart"></span>
                                <?php endif;?>
                                <?php if(in_array('Plant', get_field('icons'))) :?>
                                    <span class="icon-plant"></span>
                                <?php endif; ?>
                                <?php if(in_array('Cup', get_field('icons'))) :?>
                                    <span class="icon-cup"></span>
                                <?php endif;?>
                                <?php if(in_array('Book', get_field('icons'))) :?>
                                    <span class="icon-book"></span>
                                <?php endif;?>
                            </div>
                        <?php endif;?>
                    </div>
                </div>
            </a>
            <?php $number_shown++;
                if ( $number_shown == $total_posts ) {
                    echo '<style>.loadmore.smarties {display:none;}</style>';
                }
            endwhile ?>
    <?php endif;

wp_die();
}


//Load More Strategic Events
add_action('wp_ajax_load_posts_by_ajax', 'load_strats_by_ajax_callback');
add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_strats_by_ajax_callback');
function load_strats_by_ajax_callback() {
check_ajax_referer('load_strats_posts', 'security');
$paged = $_POST['page'];
$args = array(
    'post_type' => 'strategic_events',
    'post_status' => 'publish',
    'posts_per_page' => '10',
    'paged' => $paged,
);
$my_posts = new WP_Query( $args );
if ( $my_posts->have_posts() ) :
    ?>
    <?php 
        $total_posts = wp_count_posts('strategic_events');
        $total_posts = $total_posts->publish;
        $number_shown = $paged * 10 - 10;?>
    <?php while ( $my_posts->have_posts() ) : $my_posts->the_post() ?>

            <a href="<?php echo get_permalink(get_the_ID());?>">    
            <div class="single-strategicevent col xs-col-16 md-col-8">
                <div class="col xs-col-8 xs-offset-4 md-col-4 md-offset-0 center">
                    <div class="image-element">
                        <img src="<?php the_field('strategic_event_image');?>" class="circle">
                        <div class="image-overlay circle"></div>
                    </div>
                </div>
                <div class="col xs-col-14 xs-offset-1 md-col-10 md-offset-1">
                    <h5 class="strategic-event-title"><?php the_title();?></h5>
                    <?php if(get_field('subtitle')) :?>
                        <p><?php the_field('subtitle');?></p>   
                        <small><?php the_field('location_text');?></small>          
                    <?php endif;?>
                </div>
            </div>
        </a>
            <?php $number_shown++; 
                if ( $number_shown == $total_posts ) {
                    echo '<style>.loadmore.strats {display:none;}</style>';
                }
            endwhile ?>
    <?php endif;

wp_die();
}

I'm all up for simplifying this into a single function if possible, or if not, at least getting it to work independently. I'm guessing that it's only viewing 1 wp_nonce's and checking it against both AJAX calls, so the first one it reads is fine, the second is coming up with a 403?

4
  • 1
    You can do this entirely in JS by eliminating your AJAX and using the existing REST API endpoints at /wp-json/wp/v2/posts to both render the initial posts, and pull in additional pages. Nonces etc shouldn't be necessary
    – Tom J Nowell
    Commented Dec 13, 2018 at 17:21
  • Thanks, I have no idea how to do that - feel free to post an answer showing me how with my code if you want to =) Commented Dec 14, 2018 at 10:28
  • Note that doing this entirely in JS would require no WP knowledge, and would be a pure Javascript thing, so it would be better asked on Stack Overflow. Fundamentally, the part you need to know is how to make a request in javascript, and decode JSON, both of which are rather easy, and which I hope you already know if you're using AJAX. Also, just visit the page I mentioned on your site and it should be super obvious how to get the data ( aka it just gives you it). Just know that the code you're written is recreating things that already exist, but with some additional load on your server.
    – Tom J Nowell
    Commented Dec 14, 2018 at 14:26
  • Also, if you're writing custom things with the ancient WP Admin AJAX API, perhaps a custom REST API endpoint would be easier instead?
    – Tom J Nowell
    Commented Dec 14, 2018 at 14:29

2 Answers 2

1

The problem I'm finding is that I can only get one to work at a time. Whichever function is added first in the functions.php, that one works - the other one gets a 403 error for admin-ajax.php.

Yes, because both your PHP functions (or AJAX callbacks) there are hooked to the same AJAX action, which is load_posts_by_ajax:

// #1 AJAX action = load_posts_by_ajax
add_action('wp_ajax_load_posts_by_ajax', 'load_smart_maps_by_ajax_callback');
add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_smart_maps_by_ajax_callback');

// #2 AJAX action = load_posts_by_ajax
add_action('wp_ajax_load_posts_by_ajax', 'load_strats_by_ajax_callback');
add_action('wp_ajax_nopriv_load_posts_by_ajax', 'load_strats_by_ajax_callback');

And check_ajax_referer() by default exits the page with a 403 ("forbidden") status, when for example the nonce is not specified or has already expired:

// #1 $_REQUEST['security2'] will be checked for the load_smartmaps_posts nonce action.
check_ajax_referer('load_smartmaps_posts', 'security2');

// #2 $_REQUEST['security'] will be checked for the load_strats_posts nonce action.
check_ajax_referer('load_strats_posts', 'security');

You can send both nonces (although nobody would do that), or you can use the same nonce and same $_REQUEST key (e.g. security), but you'd still get an unexpected results/response.

So if you want a "single function", you can use a secondary "action":

  1. PHP part in functions.php:

    // #1 Load More Smart Maps
    // No add_action( 'wp_ajax_...' ) calls here.
    function load_smart_maps_by_ajax_callback() {
        // No need to call check_ajax_referer()
        ...your code here...
    }
    
    
    // #2 Load More Strategic Events
    // No add_action( 'wp_ajax_...' ) calls here.
    function load_strats_by_ajax_callback() {
        // No need to call check_ajax_referer()
        ...your code here...
    }
    
    add_action( 'wp_ajax_load_posts_by_ajax', 'load_posts_by_ajax' );
    add_action( 'wp_ajax_nopriv_load_posts_by_ajax', 'load_posts_by_ajax' );
    function load_posts_by_ajax() {
        check_ajax_referer( 'load_posts_by_ajax', 'security' );
    
        switch ( filter_input( INPUT_POST, 'action2' ) ) {
            case 'load_smartmaps_posts':
                load_smart_maps_by_ajax_callback();
                break;
    
            case 'load_strats_posts':
                load_strats_by_ajax_callback();
                break;
        }
    
        wp_die();
    }
    
  2. JS part — the data object:

    // #1 On click of `.loadmore.smarties`
    var data = {
        'action': 'load_posts_by_ajax',
        'action2': 'load_smartmaps_posts',
        'security': '<?php echo wp_create_nonce( "load_posts_by_ajax" ); ?>', // same nonce
        ...other properties...
    };
    
    // #2 On click of `.loadmore.strats`
    var data = {
        'action': 'load_posts_by_ajax',
        'action2': 'load_strats_posts',
        'security': '<?php echo wp_create_nonce( "load_posts_by_ajax" ); ?>', // same nonce
        ...other properties...
    };
    

But why not hook the callbacks to their own specific AJAX action:

// #1 AJAX action = load_smart_maps_posts_by_ajax
add_action('wp_ajax_load_smart_maps_posts_by_ajax', 'load_smart_maps_by_ajax_callback');
add_action('wp_ajax_nopriv_load_smart_maps_posts_by_ajax', 'load_smart_maps_by_ajax_callback');

// #2 AJAX action = load_strats_posts_by_ajax
add_action('wp_ajax_load_strats_posts_by_ajax', 'load_strats_by_ajax_callback');
add_action('wp_ajax_nopriv_load_strats_posts_by_ajax', 'load_strats_by_ajax_callback');
1
  • Thanks! The last bit about changing the hook callbacks was the solution. Commented Dec 14, 2018 at 10:18
2

WP ajax is a bit strange. You enqueue the script to get it to show up on the page. You also LOCALIZE your script in order to stick variables into the page the script will need, variables such as nonces, ajax addresses, or even other weird things you may find useful.

Register and localize the script like this:

    wp_enqueue_script( 'name_of_function', get_stylesheet_directory_uri() . '/js/my_special_script.js', array( 'jquery' ), '1.0', true );
    wp_localize_script( 'name_of_function',
                       'name_the_script_will_see',
                       array(
                           'ajax_url' => admin_url('admin-ajax.php'),
                           'ajax_nonce' => wp_create_nonce('your_nonce'),
                       ));

And then you have to add the ajax action TWICE, once for public and again for admin pages.

add_action('wp_ajax_this_is_the_ajax_name', 'function_name_in_php' );
add_action('wp_ajax_nopriv_this_is_the_ajax_name', 'function_name_in_php' );

Then in your script you pick up the values something like this:

var data = {
                'action': 'this_is_the_ajax_name',
                'post_nonce': name_the_script_will_see.ajax_nonce,
                'other_value_needed': value_generated_by_script,
            };
$.post( name_the_script_will_see.ajax_url, data, function( response_data ) { 

            if ( response_data.success ) {
                alert ("Success");
            } else {
                alert ("Error");
            }

Your php script will get the data as post data:

function function_name_in_php(){
     $nonce = $_POST['post_nonce'];
     $other_data = $_POST['other_value_needed'];
     if ( ! wp_verify_nonce( $nonce, 'your_nonce' ) ) {
    wp_send_json_error(array(
        'message'=>'Security Token Failure',
    )); // sends json_encoded success=false
}

I will use one nonce in many different scripts. All I'm really doing is making sure my requester is actually coming from my site. There's no point in generating 17 different nonces for that. Often I stick the nonce into a submission button as a data-attribute, then pull the nonce from that rather than from the wordpress localization scheme. It's more convenient when there are many scripts on a single page and they are accessing some of the same back end functions.

var postData = {};
 var nonce = $('#register_button').attr('data-nonce');
 postData.nonce = nonce;
 postData.action = 'this_is_the_ajax_name';
$.post( name_the_script_will_see.ajax_url, postData, function( response_data ) { 

        if ( response_data.success ) {
            alert ("success");
        } else {
            alert ("Error");
        }

Hope this helps!

1
  • Thanks for the explaination, however I have marked the other answer complete as it was more useful for me personally (It addressed my actual code). Commented Dec 14, 2018 at 10:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.