Bagaimana cara mendapatkan nilai unik untuk setiap permintaan Ajax?

11

Saya telah melihat beberapa diskusi tentang cara membuat Wordpress untuk membuat kembali sebuah layanan unik untuk permintaan Ajax berikutnya, tetapi untuk kehidupan saya, saya tidak bisa benar-benar membuat Wordpress melakukannya - setiap kali saya meminta apa yang saya pikir harus menjadi yang baru Nonce, saya mendapatkan Nonce yang sama kembali dari Wordpress. Saya mengerti konsep WP's nonce_life dan bahkan mengaturnya untuk sesuatu yang lain, tetapi itu tidak membantu saya.

Saya tidak menghasilkan nonce di objek JS di header melalui lokalisasi - saya melakukannya di halaman tampilan saya. Saya bisa mendapatkan halaman saya untuk memproses permintaan Ajax, tetapi ketika saya meminta nonce baru dari WP di callback, saya mendapatkan nonce yang sama kembali, dan saya tidak tahu apa yang saya lakukan salah ... Pada akhirnya saya ingin memperpanjang ini sehingga mungkin ada beberapa item pada halaman, masing-masing dengan kemampuan untuk menambah / menghapus - jadi saya membutuhkan solusi yang akan memungkinkan beberapa permintaan Ajax berikutnya dari satu halaman.

(Dan saya harus mengatakan saya telah meletakkan semua fungsi ini ke dalam sebuah plugin, jadi "halaman tampilan" front-end sebenarnya adalah fungsi yang disertakan dengan plugin ...)

functions.php: melokalkan, tapi saya tidak membuat nonce di sini

wp_localize_script('myjs', 'ajaxVars', array('ajaxurl' => 'admin-ajax.php')));

Memanggil JS:

$("#myelement").click(function(e) {
    e.preventDefault();
    post_id = $(this).data("data-post-id");
    user_id = $(this).data("data-user-id");
    nonce = $(this).data("data-nonce");
    $.ajax({
      type: "POST",
      dataType: "json",
      url: ajaxVars.ajaxurl,
      data: {
         action: "myfaves",
         post_id: post_id,
         user_id: user_id,
         nonce: nonce
      },
      success: function(response) {
         if(response.type == "success") {
            nonce = response.newNonce;
            ... other stuff
         }
      }
  });
});

Menerima PHP:

function myFaves() {
   $ajaxNonce = 'myplugin_myaction_nonce_' . $postID;
   if (!wp_verify_nonce($_POST['nonce'], $ajaxNonce))
      exit('Sorry!');

   // Get various POST vars and do some other stuff...

   // Prep JSON response & generate new, unique nonce
   $newNonce = wp_create_nonce('myplugin_myaction_nonce_' . $postID . '_' 
       . str_replace('.', '', gettimeofday(true)));
   $response['newNonce'] = $newNonce;

   // Also let the page process itself if there is no JS/Ajax capability
   } else {
      header("Location: " . $_SERVER["HTTP_REFERER"];
   }
   die();
}

Fungsi tampilan PHP frontend, di antaranya adalah:

$nonce = wp_create_nonce('myplugin_myaction_nonce_' . $post->ID);
$link = admin_url('admin-ajax.php?action=myfaves&post_id=' . $post->ID
   . '&user_id=' . $user_ID
   . '&nonce=' . $nonce);

echo '<a id="myelement" data-post-id="' . $post->ID
   . '" data-user-id="' . $user_ID
   . '" data-nonce="' . $nonce
   . '" href="' . $link . '">My Link</a>';

Pada titik ini saya akan sangat berterima kasih atas setiap petunjuk atau petunjuk dalam mendapatkan WP untuk membuat ulang sebuah layanan unik untuk setiap permintaan Ajax baru ...


UPDATE: Saya telah memecahkan masalah saya. Cuplikan kode di atas valid, namun saya mengubah kreasi $ newNonce di PHP callback untuk menambahkan string mikrodetik untuk memastikan bahwa itu unik pada permintaan Ajax berikutnya.

Tim
sumber
Dari tampilan yang sangat singkat: Anda membuat notce setelah Anda menerimanya (dipajang)? Mengapa Anda tidak membuatnya selama panggilan pelokalan?
kaiser
JQuery menggunakan nonce awal dari atribut "data-nonce" di tautan # myelement, dan idenya adalah bahwa halaman tersebut dapat diproses oleh Ajax atau dengan sendirinya. Sepertinya saya yang membuat nonce sekali melalui panggilan lokal akan mengecualikannya dari pemrosesan non-JS, tapi saya bisa salah tentang itu. Either way Wordpress memberikan saya kembali sama ...
Tim
Juga: Tidak akan menempatkan nilai pada panggilan lokal mencegah seseorang dari memiliki beberapa item pada halaman di mana setiap item dapat memiliki nilai unik untuk permintaan Ajax?
Tim
Membuat nonce di dalam pelokalan akan membuat dan membuatnya tersedia untuk skrip yang satu itu. Tetapi Anda juga bisa menambahkan jumlah tak terbatas lainnya (bernama kunci) nilai pelokalan dengan nonces terpisah.
kaiser
Jika Anda telah menyelesaikannya, Anda disarankan untuk mengirim jawaban Anda dan menandainya "diterima". Ini akan membantu menjaga situs tetap terorganisir. Saya hanya mengacaukan kode Anda dan beberapa hal tidak berfungsi untuk saya, jadi gandakan permintaan Anda untuk memposting solusi Anda.
s_ha_dum

Jawaban:

6

Inilah jawaban yang sangat panjang dari pertanyaan saya sendiri yang lebih dari sekedar menjawab pertanyaan tentang menghasilkan nonces unik untuk permintaan Ajax berikutnya. Ini adalah fitur "tambahkan ke favorit" yang dibuat generik untuk keperluan jawaban (fitur saya memungkinkan pengguna menambahkan ID posting lampiran foto ke daftar favorit, tetapi ini dapat berlaku untuk berbagai fitur lain yang bergantung pada Ajax). Saya mengkodekan ini sebagai plugin mandiri, dan ada beberapa item yang hilang - tetapi ini harus cukup detail untuk memberikan intisari jika Anda ingin mereplikasi fitur. Ini akan bekerja pada masing-masing posting / halaman, tetapi juga akan berfungsi dalam daftar posting (misalnya Anda dapat menambah / menghapus item ke inline favorit melalui Ajax dan setiap posting akan memiliki tingkat unik tersendiri untuk setiap permintaan Ajax). Perlu diingat bahwa ada '

scripts.php

/**
* Enqueue front-end jQuery
*/
function enqueueFavoritesJS()
{
    // Only show Favorites Ajax JS if user is logged in
    if (is_user_logged_in()) {
        wp_enqueue_script('favorites-js', MYPLUGIN_BASE_URL . 'js/favorites.js', array('jquery'));
        wp_localize_script('favorites-js', 'ajaxVars', array('ajaxurl' => admin_url('admin-ajax.php')));
    }
}
add_action('wp_enqueue_scripts', 'enqueueFavoritesJS');

Favorites.js (Banyak hal debug yang dapat dihapus)

$(document).ready(function()
{
    // Toggle item in Favorites
    $(".faves-link").click(function(e) {
        // Prevent self eval of requests and use Ajax instead
        e.preventDefault();
        var $this = $(this);
        console.log("Starting click event...");

        // Fetch initial variables from the page
        post_id = $this.attr("data-post-id");
        user_id = $this.attr("data-user-id");
        the_toggle = $this.attr("data-toggle");
        ajax_nonce = $this.attr("data-nonce");

        console.log("data-post-id: " + post_id);
        console.log("data-user-id: " + user_id);
        console.log("data-toggle: " + the_toggle);
        console.log("data-nonce: " + ajax_nonce);
        console.log("Starting Ajax...");

        $.ajax({
            type: "POST",
            dataType: "json",
            url: ajaxVars.ajaxurl,
            data: {
                // Send JSON back to PHP for eval
                action : "myFavorites",
                post_id: post_id,
                user_id: user_id,
                _ajax_nonce: ajax_nonce,
                the_toggle: the_toggle
            },
            beforeSend: function() {
                if (the_toggle == "y") {
                    $this.text("Removing from Favorites...");
                    console.log("Removing...");
                } else {
                    $this.text("Adding to Favorites...");
                    console.log("Adding...");
                }
            },
            success: function(response) {
                // Process JSON sent from PHP
                if(response.type == "success") {
                    console.log("Success!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("New toggle: " + response.theToggle);
                    console.log("Message from PHP: " + response.message);
                    $this.text(response.message);
                    $this.attr("data-toggle", response.theToggle);
                    // Set new nonce
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                } else {
                    console.log("Failed!");
                    console.log("New nonce: " + response.newNonce);
                    console.log("Message from PHP: " + response.message);
                    $this.parent().html("<p>" + response.message + "</p>");
                    _ajax_nonce = response.newNonce;
                    console.log("_ajax_nonce is now: " + _ajax_nonce);
                }
            },
            error: function(e, x, settings, exception) {
                // Generic debugging
                var errorMessage;
                var statusErrorMap = {
                    '400' : "Server understood request but request content was invalid.",
                    '401' : "Unauthorized access.",
                    '403' : "Forbidden resource can't be accessed.",
                    '500' : "Internal Server Error",
                    '503' : "Service Unavailable"
                };
                if (x.status) {
                    errorMessage = statusErrorMap[x.status];
                    if (!errorMessage) {
                        errorMessage = "Unknown Error.";
                    } else if (exception == 'parsererror') {
                        errorMessage = "Error. Parsing JSON request failed.";
                    } else if (exception == 'timeout') {
                        errorMessage = "Request timed out.";
                    } else if (exception == 'abort') {
                        errorMessage = "Request was aborted by server.";
                    } else {
                        errorMessage = "Unknown Error.";
                    }
                    $this.parent().html(errorMessage);
                    console.log("Error message is: " + errorMessage);
                } else {
                    console.log("ERROR!!");
                    console.log(e);
                }
            }
        }); // Close $.ajax
    }); // End click event
});

Fungsi (tampilan ujung depan & aksi Ajax)

Untuk menampilkan tautan Tambah / Hapus Favorit, cukup panggil di halaman / posting Anda melalui:

if (function_exists('myFavoritesLink') {
    myFavoritesLink($user_ID, $post->ID);
}

Fungsi tampilan front-end:

function myFavoritesLink($user_ID, $postID)
{
    global $user_ID;
    if (is_user_logged_in()) {
        // Set initial element toggle value & link text - udpated by callback
        $myUserMeta = get_user_meta($user_ID, 'myMetadata', true);
        if (is_array($myUserMeta['metadata']) && in_array($postID, $myUserMeta['metadata'])) {
            $toggle = "y";
            $linkText = "Remove from Favorites";
        } else {
            $toggle = "n";
            $linkText = "Add to Favorites";
        }

        // Create Ajax-only nonce for initial request only
        // New nonce returned in callback
        $ajaxNonce = wp_create_nonce('myplugin_myaction_' . $postID);
        echo '<p class="faves-action"><a class="faves-link"' 
            . ' data-post-id="' . $postID 
            . '" data-user-id="' . $user_ID  
            . '" data-toggle="' . $toggle 
            . '" data-nonce="' . $ajaxNonce 
            . '" href="#">' . $linkText . '</a></p>' . "\n";

    } else {
        // User not logged in
        echo '<p>Sign in to use the Favorites feature.</p>' . "\n";
    }

}

Fungsi aksi Ajax:

/**
* Toggle add/remove for Favorites
*/
function toggleFavorites()
{
    if (is_user_logged_in()) {
        // Verify nonce
        $ajaxNonce = 'myplugin_myaction' . $_POST['post_id'];
        if (! wp_verify_nonce($_POST['_ajax_nonce'], $ajaxNonce)) {
            exit('Sorry!');
        }
        // Process POST vars
        if (isset($_POST['post_id']) && is_numeric($_POST['post_id'])) {
            $postID = $_POST['post_id'];
        } else {
            return;
        }
        if (isset($_POST['user_id']) && is_numeric($_POST['user_id'])) {
            $userID = $_POST['user_id'];
        } else {
            return;
        }
        if (isset($_POST['the_toggle']) && ($_POST['the_toggle'] === "y" || $_POST['the_toggle'] === "n")) {
            $toggle = $_POST['the_toggle'];
        } else {
            return;
        }

        $myUserMeta = get_user_meta($userID, 'myMetadata', true);

        // Init myUserMeta array if it doesn't exist
        if ($myUserMeta['myMetadata'] === '' || ! is_array($myUserMeta['myMetadata'])) {
            $myUserMeta['myMetadata'] = array();
        }

        // Toggle the item in the Favorites list
        if ($toggle === "y" && in_array($postID, $myUserMeta['myMetadata'])) {
            // Remove item from Favorites list
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            unset($myUserMeta['myMetadata'][$postID]);
            $myUserMeta['myMetadata'] = array_flip($myUserMeta['myMetadata']);
            $myUserMeta['myMetadata'] = array_values($myUserMeta['myMetadata']);
            $newToggle = "n";
            $message = "Add to Favorites";
        } else {
            // Add item to Favorites list
            $myUserMeta['myMetadata'][] = $postID;
            $newToggle = "y";
            $message = "Remove from Favorites";
        }

        // Prep for the response
        // Nonce for next request - unique with microtime string appended
        $newNonce = wp_create_nonce('myplugin_myaction_' . $postID . '_' 
            . str_replace('.', '', gettimeofday(true)));
        $updateUserMeta = update_user_meta($userID, 'myMetadata', $myUserMeta);

        // Response to jQuery
        if($updateUserMeta === false) {
            $response['type'] = "error";
            $response['theToggle'] = $toggle;
            $response['message'] = "Your Favorites could not be updated.";
            $response['newNonce'] = $newNonce;
        } else {
            $response['type'] = "success";
            $response['theToggle'] = $newToggle;
            $response['message'] = $message;
            $response['newNonce'] = $newNonce;
        }

        // Process with Ajax, otherwise process with self
        if (! empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
            strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
                $response = json_encode($response);
                echo $response;
        } else {
            header("Location: " . $_SERVER["HTTP_REFERER"]);
        }
        exit();
    } // End is_user_logged_in()
}
add_action('wp_ajax_myFavorites', 'toggleFavorites');
Tim
sumber
3

Saya benar-benar harus mempertanyakan alasan di balik mendapatkan nilai baru untuk setiap permintaan ajax. Angka awal akan kedaluwarsa, tetapi dapat digunakan lebih dari satu kali hingga selesai. Memiliki javascript menerimanya melalui ajax mengalahkan tujuan, terutama menyediakannya pada kasus kesalahan. (Tujuan nonces adalah sedikit keamanan untuk mengaitkan tindakan dengan pengguna dalam jangka waktu tertentu.)

Saya tidak seharusnya menyebutkan jawaban lain, tapi saya baru dan tidak bisa berkomentar di atas, jadi sehubungan dengan "solusi" yang diposting, Anda mendapatkan angka baru setiap kali tetapi tidak menggunakannya dalam permintaan. Tentu akan sulit untuk mendapatkan mikrodetik yang sama setiap waktu untuk mencocokkan setiap elemen baru yang dibuat dengan cara itu. Kode PHP memverifikasi terhadap nonce asli, dan javascript memasok nonce asli ... sehingga berfungsi (karena belum kedaluwarsa).

Joy Reynolds
sumber
1
Masalahnya adalah, Nonce berakhir setelah digunakan dan akan mengembalikan -1 dalam fungsi ajax setelah setiap kali. Ini masalah jika Anda memvalidasi bagian formulir di PHP dan mengembalikan kesalahan untuk dicetak. Formulir nonce digunakan, tetapi kesalahan sebenarnya terjadi dalam validasi php bidang, dan ketika formulir dikirimkan lagi, kali ini, tidak dapat diverifikasi dan check_ajax_referermengembalikan -1, yang bukan yang kita inginkan!
Solomon Closson