2

I am implementing a front end AJAX comment moderation system for a site I manage but I am having a hard time getting it to work. Right now clicking on the link that approves the comment runs the proper php script but it reloads the entire page. So it works but I'd like to approve the comment using ajax without reloading the entire page, but my lack of javascript knowledge is preventing me from getting this to work. Here is the code (located in a plugin):

add_filter( 'comment_text', 'p3_comment_moderation_buttons' );
function p3_comment_moderation_buttons ( ) {
    if ( is_author() || current_user_can( 'moderate_comment' ) ) {
        // Adds moderation buttons under every comment
        $comment_id = get_comment_ID();
        $text = get_comment_text();
        $nonce = wp_create_nonce( 'p3_comment_moderation' );

        $p3_approve_link = admin_url('admin-ajax.php?action=p3_comment_approve&comment_id='. $comment_id.'&nonce='.$nonce);

        $p3_edit_links = '<div class="p3-edit-links"><a class="p3-comment-moderation" href="' . $p3_approve_link . '" data-comment_id="' . $comment_id . '" data-nonce="' . $nonce . '">Approve</a></div>';
        return $text . $p3_edit_links;
    }
    else{
        return get_comment_text();
    }
}


add_action("wp_ajax_p3_comment_approve", "p3_comment_approve");
function p3_comment_approve() {
    if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'p3_comment_moderation' ) ) {
        exit("Go away!"); //If nonce check fails stop everything
    }
    $comment_id = $_REQUEST["comment_id"];
    $success = wp_set_comment_status( $comment_id, 'approve' );
    $success = update_comment_meta( $comment_id, 'p3_comment_status', '' );

    if ( $success = true ) {
        $result['type'] = 'success';
        $result['comment_id'] = $_REQUEST["comment_id"];
    }
    else {
        $result['type'] = 'error';
        $result['comment_id'] = $_REQUEST["comment_id"];
        echo "Something didn't work";
    }

    if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
        $result = json_encode($result);
        echo $result;
    }
    else {
        header("Location: ".$_SERVER["HTTP_REFERER"]);
    }

    die(); // this is required to return a proper result
}

add_action( 'init', 'p3_comment_meta_script_enqueuer' );
function p3_comment_meta_script_enqueuer() {
    wp_register_script( "p3_comment_meta", plugins_url().'/p3wp-comments/js/p3_comment_meta.js', array('jquery') );
    wp_localize_script( "p3_comment_meta", 'p3cmetaAjax', array( 'ajaxurl' => admin_url( 'admin-ajax.php' )));

    wp_enqueue_script( 'jquery' );
    wp_enqueue_script( 'p3_comment_meta' );
}

And the corresponding javascript:

jQuery(document).ready( function() {

    jQuery(".p3-comment-moderation").click( function() {
        comment_id = jQuery(this).attr("data-comment_id")
        nonce = jQuery(this).attr("data-nonce")

        jQuery.ajax({
            type : "post",
            dataType : "json",
            url : p3cmetaAjax.ajaxurl,
            data : {action: "p3_comment_meta", comment_id : comment_id, nonce: nonce},
            success: function(response) {
                if(response.type == "success") {
                    alert("Success!")
                }
                else {
                    alert("Something went wrong.")
                }
            }
        })

    })

})

Can anyone spot the problem?

BTW I have already modified the comment template to display unapproved comments so they can be approved on the front end. I also plan to expand this to to work with custom comment meta, and expand the javascript so it does something more useful than displaying an annoying alert

Thanks

1 Answer 1

4

Seems to me that the only problem is that you put the full approve url in the link, in this ways when you click the link you trigger the ajax and open the page url in the link.

To be more clear your link is something like:

<a class="p3-comment-moderation" href="admin-ajax.php?action=p3_comment_approve&comment_id=123&nonce=xxxxx" data-comment_id="123" data-nonce="xxxxx">Approve</a>

thanks to the javascript, when you click on the link you launch an ajax request but, there is nothing that prevent the default link behaviour to be triggered, and so the admin-ajax.php is required to the browser, and thanks to the header statement in your code, then the page comes back again to current page.

So, actually, this is what happen:

  1. the ajax is triggered
  2. admin-ajax.php is open by the url in the a tag
  3. the page is sended again to previous page because of the header("Location: ".$_SERVER["HTTP_REFERER"]) statement in your code

I know that putting the full url in the link is the right way to make it works even if the user has javascript disabled, but when javascript is enabled you must prevent the default link behaviour, this can be done via the preventDefault() js function.

So, in your javascript replace

jQuery(".p3-comment-moderation").click( function() {

with

jQuery(".p3-comment-moderation").click( function(e) {
    e.preventDefault();

After that everything should work as expected.

If, like I guess, this is the only problem, this question is a pure javascript one, so off-topic here. However, to make my answer more WordPress relevant I want add some tips (nothing that make the script work or not, just best practice and improvements).

Tip 1: Escape attributes and links###

When in WordPress you output something in the html output as tag property, best practice is escape it with esc_attr, in general use all esc_* function when applyable

$p3_edit_links = '<a class="p3-comment-moderation" href="' . $p3_approve_link . '" data-comment_id="' . $comment_id . '" data-nonce="' . $nonce . '">Approve</a>';

is better wrote:

$p3_edit_links_f = '<a class="p3-comment-moderation" href="%s" data-comment_id="%d" data-nonce="%s">';
$p3_edit_links = sprintf($p3_edit_links_f, esc_url($p3_approve_link), esc_attr($comment_id), esc_attr($nonce) );

I know that you getting urls and attributes from WP core functions, however once almost all output from core can be filtered is good to apply data validation (although data validation functions can filtered too...)

###Tip 2: use core constant to check ajax requests###

In your code there's

if( ! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {

to check if the current request is from ajax. In WordPress when you properly use admin-ajax.php (like you do) you can check ajax request looking for DOING_AJAX constant, so the previous conditional statement becomes:

if ( defined('DOING_AJAX') && DOING_AJAX ) {

###Tip 3: use core function to output json and exit script###

Your code

$result = json_encode($result);
echo $result;
...
die();

can be wrote in one line, using the core wp_send_json function

wp_send_json( $result );

Tip 4: avoid direct superglobals access###

when you have to handle superglobals ($_REQUEST, $_GET, $_POST...) to avoid notices you should always check that the key you are looking for is setted, so your

if ( ! wp_verify_nonce( $_REQUEST['nonce'], 'p3_comment_moderation' ) ) {

is better wrote:

if ( ! isset($_REQUEST['nonce']) || ! wp_verify_nonce( $_REQUEST['nonce'], 'p3_comment_moderation' ) ) {

However, is even better to use filter_var instead of accessing superglobals directly

$nonce = filter_var( INPUT_GET, 'nonce', FILTER_VALIDATE_STRING);
if ( empty($nonce) ) $nonce = filter_var( INPUT_POST, 'nonce', FILTER_VALIDATE_STRING);
if ( ! wp_verify_nonce( $nonce, 'p3_comment_moderation' ) ) {
2
  • Thanks! I just had time to to a quick test, but once I add jQuery(".p3-comment-moderation").click( function(e) { e.preventDefault(); The something went wrong alert is triggered. Not sure why, though because when I force the server to send back json regardless of whether or not it was an ajax request the json looks like this {"type":"success","comment_id":"45081"} But I'll do more testing tonight when I have more time. Thanks again
    – ScruffyDan
    Commented Jan 3, 2014 at 16:09
  • I got it to work. I had a few other errors on my code. I'll post the working code tomorrow so others can see where I screwed up and hopefully avoid my mistakes. Thanks for everything, especially the pointers in best practices. Once Everything is working (the live code will do a few other things) I'll implement them.
    – ScruffyDan
    Commented Jan 4, 2014 at 7:12

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.