Как получить уникальный одноразовый номер для каждого запроса Ajax?

11

Я видел пару дискуссий о том, как заставить Wordpress создать уникальный одноразовый номер для последующих запросов Ajax, но на самом деле я не могу заставить Wordpress сделать это - каждый раз, когда я запрашиваю то, что я считаю новым nonce, я получаю тот же nonce обратно из Wordpress. Я понимаю концепцию nonce_life WP и даже настраиваю ее на что-то другое, но это мне не помогло.

Я не генерирую одноразовый номер в объекте JS в заголовке посредством локализации - я делаю это на своей странице отображения. Я могу заставить свою страницу обрабатывать Ajax-запрос, но когда я запрашиваю новый одноразовый номер из WP в обратном вызове, я получаю тот же одноразовый номер обратно и не знаю, что делаю неправильно ... В конечном итоге я хочу Расширьте это так, чтобы на странице могло быть несколько элементов, каждый с возможностью добавления / удаления - поэтому мне нужно решение, которое позволит несколько последующих запросов Ajax с одной страницы.

(И я должен сказать, что я поместил всю эту функциональность в плагин, поэтому интерфейсная «страница отображения» на самом деле является функцией, включенной в плагин ...)

functions.php: локализуйте, но я не создаю здесь одноразовый номер

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

Вызов 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
         }
      }
  });
});

Получение 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();
}

Функция отображения внешнего интерфейса PHP, среди которых:

$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>';

На данный момент я был бы очень благодарен за любые подсказки или указатели в получении WP для регенерации уникального одноразового номера для каждого нового запроса Ajax ...


ОБНОВЛЕНИЕ: я решил свою проблему. Приведенные выше фрагменты кода верны, однако я изменил создание $ newNonce в обратном вызове PHP, добавив строку микросекунд, чтобы обеспечить ее уникальность при последующих запросах Ajax.

Тим
источник
С самого беглого взгляда: Вы создаете нонса после того как вы получили его (на дисплее)? Почему вы не создаете его во время вызова локализации?
Кайзер
JQuery использует начальный nonce из атрибута «data-nonce» в ссылке a # myelement, и идея заключается в том, что страница может обрабатываться либо Ajax, либо самостоятельно. Мне казалось, что однократное создание одноразового номера с помощью вызова локализации исключит его из обработки не-JS, но я могу ошибаться. В любом случае Wordpress возвращает мне тот же самый одноразовый номер ...
Тим
Также: не помешает ли включение одноразового номера в вызов localize иметь несколько элементов на странице, где каждый элемент может иметь уникальный одноразовый номер для запроса Ajax?
Тим
Создание одноразового номера внутри локализации создаст и сделает его доступным для этого сценария. Но вы также можете добавить неограниченное количество других (именованных ключей) локализованных значений с отдельными одноразовыми значениями.
Кайзер
Если вы решили его, вам рекомендуется опубликовать свой ответ и пометить его как «принятый». Это поможет сохранить сайт организованным. Я просто возился с вашим кодом, и некоторые вещи не работают для меня, поэтому удвоите эту просьбу, чтобы вы опубликовали свое решение.
s_ha_dum

Ответы:

6

Вот очень длинный ответ на мой собственный вопрос, который выходит за рамки простого решения вопроса о создании уникальных одноразовых номеров для последующих запросов Ajax. Это функция «Добавить в избранное», которая была сделана общей для целей ответа (моя функция позволяет пользователям добавлять идентификаторы сообщений с вложениями фотографий в список избранных, но это может применяться к множеству других функций, которые зависят от Ajax). Я закодировал это как отдельный плагин, и некоторые элементы отсутствуют, но этого должно быть достаточно, чтобы представить суть, если вы хотите воспроизвести эту функцию. Он будет работать с отдельным сообщением / страницей, но он также будет работать в списках сообщений (например, вы можете добавлять / удалять элементы в избранном, встроенном через Ajax, и каждое сообщение будет иметь свой уникальный одноразовый номер для каждого запроса Ajax). Имейте в виду, что там

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 (много отладочных материалов, которые можно удалить)

$(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
});

Функции (внешний интерфейс и действие Ajax)

Чтобы вывести ссылку «Добавить / Удалить избранное», просто вызовите ее на странице / в сообщении через:

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

Функция внешнего интерфейса:

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";
    }

}

Функция действия 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');
Тим
источник
3

Я действительно должен подвергнуть сомнению причины получения нового одноразового номера для каждого запроса AJAX. Срок действия исходного одноразового номера истекает, но его можно использовать несколько раз, пока он не истечет. Получение javascript через ajax отрицательно сказывается на цели, особенно в случае ошибки. (Цель одноразовых сообщений - небольшая безопасность для связи действия с пользователем в течение определенного периода времени.)

Я не должен упоминать другие ответы, но я новичок и не могу комментировать выше, поэтому в отношении опубликованного «решения» вы получаете новый nonce каждый раз, но не используете его в запросе. Конечно, было бы сложно получить одинаковые микросекунды каждый раз, чтобы соответствовать каждому новому одноразовому номеру, созданному таким образом. Код PHP проверяет исходный одноразовый номер, а javascript предоставляет исходный одноразовый номер ... так что он работает (потому что срок его действия еще не истек).

Джой Рейнольдс
источник
1
Проблема в том, что nonce истекает после его использования и будет возвращать -1 в функции ajax после каждого раза. Это проблема, если вы проверяете части формы в PHP и возвращаете ошибки для распечатки. Была использована форма nonce, но на самом деле произошла ошибка при проверке php полей, и когда форма снова отправляется, на этот раз она не может быть проверена и check_ajax_refererвозвращает -1, а это не то, что нам нужно!
Соломон Клоссон