I have a form on a php page that is submitted to the same page.
I noticed that if I reload/refresh the page the form gets re-submitted.
How do I code to avoid this in the most easy way?
One possibility is, to implement the post-redirect-get approach.
Simply said, a POST request will be never delivered to the browser. Instead you execute all necessary actions and store the information you need in the session, and then you make a redirect with code 303.
$page = 'show_result.php';
header('Location: '.$page, true, 303);
exit;
Doing it this way, the browser will show the "show_result.php" page (a GET request) instead of the page requested with POST. This is also the page that is added to the history, so refreshing and using the back button will never do another POST request. As a nice side effect you get rid of browser warnings about resending data, normally the user cannot decide what to do then anyway.
I think the biggest problem with this approach is, that you need a session to store error messages, that means you have to rely on cookies. If you do no redirect to display errors, the browser will show the warning about resending data.
This assume a lot of things, but maybe is what you are looking for:
if ($_POST)
{
$success = false;
/*
* if all goes OK managing POST data make $success = true;
*
*/
if ($success)
{
// this will redirects to your original
// form's page but using GET method
// so re-submitting will be no possible
header("location: {$_SERVER['PHP_SELF']}");
exit;
}
}
According to HTTP standard, you ought to make browser to do a GET request after sending POST one.
Here is a sketch example to do the form handling:
<?
if ($_SERVER['REQUEST_METHOD']=='POST') {
$err = array();
//performing all validations and raising corresponding errors
if (empty($_POST['name']) $err[] = "Username field is required";
if (empty($_POST['text']) $err[] = "Comments field is required";
if (!$err) {
//if no errors - saving data and redirect
header("Location: ".$_SERVER['PHP_SELF']);
exit;
} else {
// all field values should be escaped according to HTML standard
foreach ($_POST as $key => $val) {
$form[$key] = htmlspecialchars($val);
}
} else {
$form['name'] = $form['comments'] = '';
}
include 'form.tpl.php';
?>
Related
I have a form and I don't do client-side validation other than required attribute. I'm doing the validation on the server side.
lyrics/add.php
<form action="../scripts/lyrics/submit_lyrics.php" id="lyricsForm" method="post" autocomplete="off" enctype="multipart/form-data">
Form data is processed in a seperate php file. Retrieving and validating the form data in the following way:
scripts/lyrics/submit_lyrics.php
$form_data = new FormData(["artist", "album", "song", "year", "track_no", "lyrics"], "sssiis");
$form_data->validate();
What the validate method does is
public function validate() {
$valid = $this->check_form_data($fields);
$fields = implode(",", $fields);
if (!$valid) {
header("location: ".BASE."lyrics/add?status=error&problem=input&specifics=$fields");
exit;
}
}
check the form data and redirect the page (scripts/lyrics/submit_lyrics.php) to the form page (lyrics/add.php) with the information on validation (if it failed). Then I output an error message indicating that there's something wrong with the input using GET method.
I'm curious if I can do this using POST. I would need to modify this line
header("location: ".BASE."lyrics/add?status=error&problem=input&specifics=$fields");
make it redirect the page to BASE."lyrics/add" and also send the validation information using POST to that page. So I'd still be able output the validation error, but using POST instead of GET.
Is this possible?
No, this is not possible because you are rewriting the request. The browser will always send a "GET" request in response to a redirect request, unless it receives a 307 request in which it will repeat the request using the same method and parameters. See this related question on Programmers.
The workaround is to redirect to a new page with an embedded form that you generate, and have javascript POST that form for the user. It's an extra request but it's the only way to have the client make a second POST.
Using u_mulder's suggesion (storing values in a session), I've solved the problem in the following way:
scripts/lyrics/submit_lyrics.php
public function validate() {
$valid = $this->check_form_data($fields); // check if input fields are valid
$fields = implode(";", $fields); // invalid input fields
if (!$valid) { // not all fields are valid, so we return
session_start(); // start session
if (!isset($_SESSION["problem"])) { // making sure session variable doesn't exist
$_SESSION["problem"] = "input"; // error type: input, file_upload, db_insert, etc.
$_SESSION["specifics"] = $fields; // artist, album, etc. (for input)
}
header("location: ".BASE."lyrics/add?status=error"); // redirect to form page
exit;
}
}
lyrics/add.php
if (isset($_GET["status"])) {
$status = $_GET["status"];
switch ($status) {
case "error":
session_start();
if (isset($_SESSION["problem"])) {
$problem = $_SESSION["problem"];
$specifics = explode(";", $_SESSION["specifics"]);
$error_message = "There was an error.";
switch ($problem) {
case "input":
$error_message = "Invalid input on the following fields:";
break;
// other cases ...
}
session_destroy();
output("Error!", $error_message, $specifics);
} else {
// if the user reloads the page, session data is lost
// but we're still in the error page "lyrics/add?status=error" and have no error to show
// we either show an appropriate message or redirect to "lyrics/add"
session_destroy();
// output("Error!", "You're in the form validation error page, but there aren't any errors. Did you reload the page?");
// header("location: ".BASE."lyrics/add");
// exit;
}
break;
case "success":
// form submitted successfully
break;
}
} else {
// show form
}
I've got a small problem with my smarty project, logout problem to be precise. I have a index.php page which is the "main" page and it gets POST data and directs actions based on current data. There`s checking if the session variables has been set. Now when I login I have function like this:
function login($value)
{
$res = $this->sql->checkLogin($value);
if($res)
{
//checks if user is admin
$isadm = $this->sql->isAdm($value);
if($isadm == true)
{
$_SESSION['user'] = $value['name'];
$_SESSION['adm'] = true;
$message = 'Admin';
$this->tpl->assign('var', $message);
if($_SESSION['adm'] == true)
{
//sets some variables for admin users
$navigation = 'navi';
$this->tpl->assign('navigation', $navigation);
}
$this->tpl->display('maint_main.tpl');
}
//user is not admin
else
{
$_SESSION['user'] = $value['name'];
$_SESSION['adm'] = false;
$message = 'Perus';
$this->tpl->assign('var', $message);
if($_SESSION['adm'] == true)
{
$navigation = 'navi';
$this->tpl->assign('navigation', $navigation);
}
$this->tpl->display('maint_main.tpl');
}
}
//login failes, show login form and info
else
{
$message = 'Login failed';
$this->tpl->assign('var', $message);
$this->tpl->display('login_form.tpl');
}
}
and logout function :
function logout()
{
setcookie(session_name(), '', time()-42000, '/');
session_unset();
session_destroy();
$this->tpl->display('login_form.tpl');
}
These work just about the way they are supposed to but the real problem occurs when I log out and redirect to the login_form.tpl. If I use the back button of the browser the POST data with username and password is retrieved and the login goes through again. This causes that those pages behind login are still viewable. As I am not quite familiar with Smarty yet I couldn`t figure out any way to fix this. So basically how to prevent access to that POST data after logout?
I don't think this has anything to do with smarty. This is a browser/http generic issue. Most browsers will re-post form data after confirmation from the user.
One approach to make re-posts of the form invalid would be to pass along a secret code/token (perhaps a guid or your session id) which is also stored in session data. When the user logs out, clear their session (or at least the secret code you're checking). When the user logs in, check to make sure that the confirmation code matches the one for the current session.
This pattern is often used to manage csrf attacks and is often known as a 'synchronizer token'. This blog post provides a good explanation https://blog.whitehatsec.com/tag/synchronizer-token/
I have a login page and I want to achieve this effect:
If user fails to login, display error message. After user presses refresh button, the error message should not be visible anymore.
I dont want to pass $_GET variable, for example index?page=login$failure. I want to do this in invisible in url way.
<?php
if(isset($_POST['submit_form_register'])) {
...
if($login === false) {
$user->go_to('index.php?page=login'); //Redirects back, cleaning the $_POST data.
}
..
}
?>
Now how do I say for my form, that something before redirect went bad without addint it with $_GET?
Update. So using sessions, can I do like this?
<?php
if(isset($_POST['submit_form_register'])) {
...
if($login === false) {
$_SESSION['is_form_error'] = true;
$user->go_to('index.php?page=login'); //Redirects back, cleaning the $_POST data.
}
..
}
?>
And for HTML output:
<?php if(isset($_SESSION['is_form_error']) and ($_SESSION['is_form_error'] === true)) { ?>
<div>Your email or credential is invalid.</div>
<?php } unset($_SESSION['is_form_error']); ?>
But this one doesnt work for me. I bet its because something wrong with unset. Any tips?
SOLVED: Didn't have exit; after header(); in $user->go_to($url);
You could use Session for show and hide errors
When user enter wrong user/pass you set error session and use it in view and then delete session(after showed)
`//Set Session
if (!$success) {
$_SESSION['login_error'] = 1;
}
//show
if (isset($_SESSION['login_error'])) {
echo "Invalid Username/Password";
unset($_SESSION['login_error']);
}`
Don't forget to start code with session_start();
So on one page my users check a box and type agree in an input field to proceed to the next page, I am trying to use session cookies to stop people bypassing this by typing the URL however when you proceed to the next page it just displays blank? i have tried tests such as using Echo to display text at the beginning of the script and have enabled error reporting but the page still just displays white? any ideas why?
Check Box and Input Page php:
<?php
if(isset($_POST["terms"])&&isset($_POST["agree"])) {
$agree = $_POST["agree"];
$validated = false;
if($agree=="agree") $validated = true;
if($validated) {
setcookie("agree",($agree));
header("Location: nextpgae");
} else {
header("Location: homepage");
}
}
?>
Page it leads to that is displaying blank's php:
<?php
error_reporting(E_ALL);
ini_set('display_errors', '1');
$validated = false;
if(isset($_COOKIE["agree"])){
$agree = $_COOKIE["agree"];
if(&$agree==("agree")) $validated = true;
}
if($validated) {
} else {
header("Location: homepage");
?>
A blank page is usually symptomatic of a server error. Frequently, it indicates a syntax error in your PHP code. Your server error log will tell you exactly what is going on, but in this case you have missed a closing } after this line
header("Location: homepage");
You should implement tokens for this kind of procedure. This may help you: http://forum.codecall.net/topic/58268-form-tokens-with-php/
I have an HTML form that takes inputted data and sends it via the mail() function. I also have some validation techniques that validate the inputs, and I created an array variable $errors to log all of the errors; for example,
if the name was left empty, $errors[]="Name empty";
If the email was left empty, $errors[]="email empty";
and so on..
I was able to report the errors using the following technique:
print '<div id="formfeedback"><h3>Error!</h3><p>The following error(s) has occurred:<br />';
foreach ($errors as $msg) { //prints each error
print " - $msg<br />\n";
} // end of foreach
However, what I want is the following. I want the page to be redirected back to the original form that was used to input the information (I know the exact link location, so i can use a header() or even a <meta=http-equiv=refresh> to bring me back to the form page.
Also, on the form, I want to be able to post the errors above the form in some div (call it div=errors)
Would I be able to do the following?
<div id="errors">
<?php
print 'The following error(s) has occurred:<br />';
foreach ($_REQUEST[$errors] as $msg) { //prints each error
print " - $msg<br />\n";
} // end of foreach
?>
</div>
Thanks a lot!
Amit
I agree with #Fosco. I want to explain a little bit more-
There may be two cases-
1. You are doing raw php
2. You are coding on any php framework like CI or your own.
and this will help to identify error field and change style to make better user response. Also last input data remain as it was.
You are doing raw php
In this case you can receive the input data in same file/page.
I will do a common example later.
You are coding on any php framework like CI or your own.
In this case you load a view file to show the form page and you can pass data to view page/file when you load it.
For both of above case you can do some coding like-
/*
your input validation and verification goes here. where $error is generated too
In addition add some error status in above section,
you can do it in your $error array too. Also you store received data into $data here. index of $data should be similar as (corresponding) HTML input name.
You can do it like below
*/
$error_stat = array();
//if the input field name is "email" and email input data raises any error then
$error_stat['email'] = true;
// same for name
$error_stat['name'] = true;
// and so on
// now decide whether you will back to the form page or send the email and do other tasks
if(count($error_stat)<= 0){
// send email
// do aditional tasks
}
else{
// load the form again if its aframework or the form is in seperate file
// off course send $error,$data and $error_stat to the form page/file
}
// now here is a code segment of form page
<?php if(isset($error) && count($error)>0):?>
<div id="error-msg">
<?php
//display errors here
?>
</div>
<?php endif;?>
<form .... >
<input type="text" name="email" class="<?php echo (isset($error_stat['email'])?'error':'else'); ?>" value="<?php echo $data['email'];?>" />\
<!-- and so on ... -->
The simplest way to do this is to:
// Start the session
session_start();
// Store the errors in the session
$_SESSION['errors'] = $errors;
// Redirect to correct page
header('HTTP/1.1 303 See Other');
header('Location: http://original/page');
exit;
Then, on the form page:
// Start the session
session_start();
// extract the errors
$errors = isset($_SESSION['errors']) ? $_SESSION['errors'] : array();
// Display the form with errors
foreach ($errors as $msg) ... ;
Typically I would have the same page process the input and the submission. If the data was valid, the mail would be sent and the page would notify them of that (or redirect them elsewhere)... if the data was not valid, then the form would appear again and the errors could be displayed, without any fancy redirection.
make sure your session is started at the top of your application
include this basic class
class FormErrors
{
var $handler;
function __construct($fname)
{
$this->handler &= isset($_SESSION[$fname]) $_SESSION[$fname] : ($_SESSION[$fname] = array());
}
public function add($name, $value)
{
$this->handler[$name] = $value;
}
public function remove($name)
{
unset($this->handler[$name]);
}
public function getErrors()
{
return $this->handler;
}
}
so when your processing the errors you can go
if(isset($_POST))
{
$FormErrors = new FormErrors("registration");
if(strlen($_POST['username']) == 0)
{
$FormErrors->add('Username','Please enter a valid username');
}
//And for the rest of your checks
}
then within side your html do
foreach($FormErrors ->getErrors() as $name => $error)
{
echo sprintf("<p title=\"%s\">%s</p>",$name,$error);
}
Should work, and if you want to remove all known errors do
$FormErrors = new FormErrors("registration");
unset($FormErrors->handler,$FormErrors);