Для тестирования посетителю поочередно показывают вопросы, записывая его ответы в базу. Но есть нюанс – надо сделать так, что бы при обновлении страницы или при клику «Назад» скрипт останавливался и выводилось сообщение об ошибке. Я сделал так. На стартовой странице, где еще нет вопросов, вырабатывается случайное число $randomNumber, которое заносится в базу. Потом человек кликает «Начали» и переходит на страницу с адресом test.php?id=" . $randomNumber. На этой странице извлекается число из динамического адреса и из базы, и сравнивают их. Если числа совпадают, то все хорошо. Вырабатывается другое случайное число и в форме создается action обработчика с динамическим адресом, содержащим это число, которое также заносится в базу вместо прежнего. Посетителю показывается вопрос. Когда посетитель кликает «Готово», происходит переход на обработчика, которого посетитель не видит. В обработчике из адреса берется число и из базы, аналогичным образом они сравниваются. Если совпадают, то результат тестирования заносится в базу. Если не совпадают, то выводится сообщение об ошибке. Генерируется новое случайное число, которое тоже заносится в базу, и оно же добавляется в адрес страницы тестирования, где, после проверки, появляется следующий вопрос. Все это работает очень хорошо, успешно детектируя и обновления, и попытки Назад. Но бывают случаи, когда скрипт останавливается с диагностикой ошибки, которой на самом деле нет. То есть, что-то не успевает синхронизироваться. Может, браузер $_GET долго отдает или что-то в этом роде. Как бы это исправить? (Сессии не стал использовать, поскольку вариант с базой позволяет прерваться и потом продолжить с этого же места).
Добрый день! Правильно ли я понял, что одно случайное число каждый раз сохраняется в БД только для сравнения с введённым числом? Напоминает задачку отгадай число для JS - генерируйте случайное число и сранивайте с ответом. Ну если очень хочется PHP за уши притянуть, отправляйте введённое число из JS через fetch без перезагрузки страницы. Если есть какой-то потаённый смысл, то покажите, пожалуйста, Ваш код. Удачи!
Приведу некоторые коды, относящиеся к проблеме. На странице с тестом. PHP: // Динамический адрес для формы // Случайная часть динамического адреса $randomNumber = random_int(105000, 995000); // Создание подготовленного SQL запроса для обновления столбца $sql = "UPDATE user SET address_dynamic = ? WHERE token = ?"; // Подготовка и выполнение SQL запроса $stmt = mysqli_prepare($db, $sql); mysqli_stmt_bind_param($stmt, "is", $randomNumber, $token); if (mysqli_stmt_execute($stmt)) { //echo "Data updated successfully"; } Форма тестирования HTML: <form action="green-handler.php?d=<?php echo $randomNumber; ?>" method="POST"> <div class="radiobutton-group"> <input type="radio" id="button1" name="ball" value="100"> <label for="button1">0</label> <input type="radio" id="button2" name="ball" value="1"> <label for="button2">1</label> <input type="radio" id="button3" name="ball" value="2"> <label for="button3">2</label> <input type="radio" id="button4" name="ball" value="3"> <label for="button4">3</label> <input type="radio" id="button5" name="ball" value="4"> <label for="button5">4</label> <input type="radio" id="button6" name="ball" value="5"> <label for="button6">5</label> <input type="radio" id="button7" name="ball" value="6"> <label for="button7">6</label> <input type="radio" id="button8" name="ball" value="7"> <label for="button8">7</label> <input type="radio" id="button9" name="ball" value="8"> <label for="button9">8</label> <input type="radio" id="button10" name="ball" value="9"> <label for="button10">9</label> <input type="radio" id="button11" name="ball" value="10"> <label for="button11">10</label> </div> <button class="start" type="submit">Далее</button> </form> В обработчике green-handler.php Вот так, вместе с другими элементами, мы извлекаем из базы динамическую часть адреса (переменная $address_dynamic) PHP: $sql = "SELECT step, new_topics, old_topics, limit_seans, address_dynamic FROM user WHERE token = ?"; // Подготовка и выполнение SQL запроса $stmt = mysqli_prepare($db, $sql); mysqli_stmt_bind_param($stmt, "s", $token); if (mysqli_stmt_execute($stmt)) { // Привязка результатов запроса к переменным mysqli_stmt_bind_result($stmt, $step, $new_topics, $old_topics, $limit_seans, $address_dynamic); // Получение и вывод значений столбцов mysqli_stmt_fetch($stmt); } А здесь производим проверку PHP: // Проверка динамической части адреса для отсечения внешнего вызова if(isset($_GET['d'])) { if(!ctype_digit($_GET['d'])) die('<p class="error">Запрещённая операция. 55 <a href="/">На главную</a></p>'); // В адресе должный быть только цифры if($address_dynamic != $_GET['d']) die('<p class="error">Запрещённая операция. 56 <a href="/">На главную</a></p>'); // Не совпадают адреса } else die('<p class="error">Запрещённая операция. 58 <a href="/">На главную</a></p>'); И почему-то получается, что примерно 1 раз на 200-300 ответов выводится сообщение об ошибке 58.
Эхо в ответ на POST-запрос? Даже обычные ошибки надо выводить по GET (в POST-обработчике можно сделать редирект на что-то вроде /error/55). В ответ на POST выводите только критические ошибки времени выполнения со статусами 5хх. Ну, и 4хх тоже можно.
Чтобы выйти на проблему, я в приведенном в теме коде организовал запись в текстовый файл пары адресов из базы и полученные через GET. Вот результаты: address_dynamic 626890 | $-_GET['d'] 626890 address_dynamic 748117 | $-_GET['d'] 748117 address_dynamic 467814 | $-_GET['d'] 626890 В последней строке запись, после которой диагностирована ошибка. Адрес в базе именно такой (467814), а по GET пришел другой (626890). Видно, что такое число (626890) было на два цикла раньше. Похоже, что браузер, почему то, извлек старый адрес из кеша и решил его передать вместо требуемого. То есть, проблема в том, что браузер из кеша что-то подсовывает. Можно ли от этого избавиться? (На всех страницах стоит header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: post-check=0, pre-check=0", false); header("Pragma: no-cache")
Не надо. Даже при современном качестве Интернета не надо исключать «недогрузы» и т.п. --- Добавлено --- Короче у вас все неправильно. Подобные заголовки кеша не помогают на 100% защититься от использования кнопки «Назад». Нужно просто запоминать, пройден был вопрос или нет. --- Добавлено --- А когда перейдете на вывод по GET, что правильно, ваши случайные числа нужно генерировать при обработке POST-запросов. --- Добавлено --- Вообще нафига при наличии авторизации самому пользователю доп. ключи в адресе и т.п., не понятно. Это только для защиты от CSRF-атак нужно, но не при явной работе пользователя с вашим тестом.
Не понять зачем сохранять в БД address_dynamic перед отправкой формы. Сделал так. 1 Перехватаем submit form и отправляем fetch post request на сервер. 2.На сервере генерируем на РНР $randomNumber , записываем в сессию и возвращаем $randomNumber в fetch response 3.Присваем полученное значени в <input type = "hidden" name ="d" > 4.Отправляем форму на green-handler.php Скрипт с формой PHP: <? if(filter_input(INPUT_POST, 'step')){ session_start(); $randomNumber = random_int(105000, 995000); $_SESSION["address_dynamic"] = $randomNumber; echo $randomNumber; exit; } ?> <html> <head> <script> window.addEventListener("load", () => { var form = document.forms[0]; //------------------------------------------------ form.addEventListener("submit", (e) => { e.preventDefault(); fd = new FormData(form); fetch("?", { method: "POST", body: fd, }) .then((response) => response.text()) .then((text) => { form.d.value = text; form.submit(); }); }); //------------------------------------------------ }); </script> </head> </head> <body> <form action="green-handler.php" method="POST"> <div class="radiobutton-group"> <input type="radio" id="button1" name="ball" value="100"> <label for="button1">0</label> <input type="radio" id="button2" name="ball" value="1"> <label for="button2">1</label> <input type="radio" id="button3" name="ball" value="2"> <label for="button3">2</label> <input type="radio" id="button4" name="ball" value="3"> <label for="button4">3</label> <input type="radio" id="button5" name="ball" value="4"> <label for="button5">4</label> <input type="radio" id="button6" name="ball" value="5"> <label for="button6">5</label> <input type="radio" id="button7" name="ball" value="6"> <label for="button7">6</label> <input type="radio" id="button8" name="ball" value="7"> <label for="button8">7</label> <input type="radio" id="button9" name="ball" value="8"> <label for="button9">8</label> <input type="radio" id="button10" name="ball" value="9"> <label for="button10">9</label> <input type="radio" id="button11" name="ball" value="10"> <label for="button11">10</label> </div> <input type = "hidden" name="step" value=1> <input type = "hidden" name ="d" > <button class="start" type="submit">Далее</button> </form> </body> </html> script green-handler.php PHP: <? session_start(); $err0 = '<p class="error">Запрещённая операция. 58 <a href="/">На главную</a></p>'; if($_SESSION["address_dynamic"]){ $address_dynamic = $randomNumber = $_SESSION["address_dynamic"]; $token = $_SESSION["token"]; $d = filter_input(INPUT_POST, 'd'); if($d) { if(!ctype_digit($d)) die('<p class="error">Запрещённая операция. 55 <a href="/">На главную</a></p>'); // В адресе должный быть только цифры if($address_dynamic != $d ) die('<p class="error">Запрещённая операция. 56 <a href="/">На главную</a></p>'); // Не совпадают адреса else { $sql = "UPDATE user SET address_dynamic = ? WHERE token = ?"; // Подготовка и выполнение SQL запроса $stmt = mysqli_prepare($db, $sql); mysqli_stmt_bind_param($stmt, "is", $randomNumber, $token); if (mysqli_stmt_execute($stmt)) { //echo "Data updated successfully"; } } } else die($err0); } else die($err0'); Удачи!
Дополнение Независимо от реализации советую Вам закрывать кнопку submit пока не будет отчековано radio Код (Javascript): <script> window.addEventListener("load", () => { var form = document.forms[0]; var balls = form.ball; var nBalls = balls.length; var start = document.querySelectorAll(".start")[0]; start.setAttribute("disabled","true"); //------------------------------------------------ clickBall = function(e){ start.removeAttribute("disabled"); for (i = 0; i < nBalls; i++) balls[i].removeEventListener("click", clickBall); } //------------------------------------------------ for (i = 0; i < nBalls; i++) balls[i].addEventListener("click", clickBall); //------------------------------------------------ form.addEventListener("submit", (e) => { e.preventDefault(); fd = new FormData(form); fetch("?", { method: "POST", body: fd, }) .then((response) => response.text()) .then((text) => { form.d.value = text; form.submit(); }); }); //------------------------------------------------ }); </script> В green-handler.рхр в 28 строке нужно убрать кавычку. Улачи!