Allow a form to be submitted only once

Small, short code snippets that other people may find useful. Do you have a good regex that you would like to share? Share it! Even better, the code can be commented on, and improved.

Moderator: General Moderators

Post Reply
timvw
DevNet Master
Posts: 4897
Joined: Mon Jan 19, 2004 11:11 pm
Location: Leuven, Belgium

Allow a form to be submitted only once

Post by timvw »

We are a little company that sells some products.
Each product we store in a database,
and we have an online inventory program.

Code: Select all

CREATE TABLE products (
name varchar(255) NOT NULL default '',
stock int(11) NOT NULL default '0',
PRIMARY KEY (name)
)

INSERT INTO products VALUES ('milkshake', 100);
INSERT INTO products VALUES ('coca-cola', 100);
INSERT INTO products VALUES ('fanta', 100);
We have pages that allow our employees to register all incoming and outgoing
products. And pages to correct these transfers.

This is the page the employees use to register a sale (outgoing product).

Code: Select all

<?php
    // connect with the database
    mysql_connect('localhost','anonymous','');
    mysql_select_db('test');

    if (isset($_POST['update'])) {

      // perform an update
      $name = mysql_escape_string($_POST['name']);
      $change = mysql_escape_string($_POST['change']);

      $query = "UPDATE products SET stock = stock - $change WHERE name='$name'";
      mysql_query($query);

      // retrieve the products
      $products = array();

      $query = "SELECT * FROM products";
      $result = mysql_query($query);
      while ($row = mysql_fetch_assoc($result)) {
        array_push($products,$row);
      }

      // generate nice HTML output with the results we found

      echo '<table>';
      echo '<tr>';
      echo '<th>NAME</th>';
      echo '<th>STOCK</th>';
      echo '</tr>';

      foreach($products as $product) {

        echo '<tr>';
        echo '<td>'.$product['name'].'</td>';
        echo '<td>'.$product['stock'].'</td>';
        echo '</tr>';
      }

      echo '</table>';

    } else {

      // retrieve the products
      $products = array();

      $query = "SELECT * FROM products";
      $result = mysql_query($query);
      while ($row = mysql_fetch_assoc($result)) {
        array_push($products,$row);
      }

      // generate nice HTML output with the results we found

      echo '<table>';
      echo '<tr>';
      echo '<th>NAME</th>';
      echo '<th>STOCK</th>';
      echo '<td>SOLD ITEMS</th>';
      echo '</tr>';

      foreach($products as $product) {

        echo '<tr>';
        echo '<td>'.$product['name'].'</td>';
        echo '<td>'.$product['stock'].'</td>';
        echo '<td>';
        echo '<form method="post">';
        echo '<input type="hidden" name="name" value="'.$product['name'].'" />';
        echo '<input type="text" name="change" />';
        echo '<input type="submit" name="update" value="update" />';
        echo '</form>';
        echo '</td>';
        echo '</tr>';
      }

      echo '</table>';
    }
?>
This application works exactly how we want it.
Now Joe, our employee, sells 5 products to a customer and registers this update.
Suddenly, he realizes that he only sold 4 products,
and thus wants to undo the last update he submitted.
He decides to hit the back button in his browser,
clicks away that annoying dialog,
and he recieves a page that says there have left 10 (2 times 5) products.

To avoid this problem, i have a quite simple solution.

1-) Add a column to our products that contains the timestamp when the product was last updated.

Code: Select all

ALTER TABLE products ADD lastupdate TIMESTAMP NOT NULL;
2-) Add a hidden field to our form that contains this lastupdate.

Code: Select all

echo '&lt;input type="hidden" name="lastupdate" value="'.$product&#1111;'lastupdate'].'" /&gt;';
3-) Add a test before we start the update

Code: Select all

// find out when this product was updated the last time
$query = 'SELECT * FROM products WHERE product_id='.$product_id;
$result = mysql_query($query);
$row = mysql_fetch_assoc($result);

// compare the lastupdate in our form with the lastupdate in the database
if ($_POST['lastupdate'] < $row['lastupdate']) {
    echo "You should use the correction page instead!!!!";
} else {
    // perform the update - don't forget to set lastupdate to NOW() 
}



So we end up with the following code

Code: Select all

<?php
    // connect with the database
    mysql_connect('localhost','anonymous','');
    mysql_select_db('test');

    if (isset($_POST['update'])) {

      $name = mysql_escape_string($_POST['name']);
      $change = mysql_escape_string($_POST['change']);

      // find out when this product was updated the last time
      $query = "SELECT * FROM products WHERE name='$name'";
      $result = mysql_query($query);
      $row = mysql_fetch_assoc($result);

      // compare the lastupdate in our form with the lastupdate in the database
      if ($_POST['lastupdate'] < $row['lastupdate']) {
        echo "You should use the <a href="correction.php">correction page</a> instead!!!!";
      } else {

        // perform an update
        $query = "UPDATE products SET stock = stock - $change, lastupdate=NOW() WHERE name='$name'";
        mysql_query($query);

        // retrieve the products
        $products = array();

        $query = "SELECT * FROM products";
        $result = mysql_query($query);
        while ($row = mysql_fetch_assoc($result)) {
          array_push($products,$row);
        }

        // generate nice HTML output with the results we found

        echo '<table>';
        echo '<tr>';
        echo '<th>NAME</th>';
        echo '<th>STOCK</th>';
        echo '</tr>';

        foreach($products as $product) {

          echo '<tr>';
          echo '<td>'.$product['name'].'</td>';
          echo '<td>'.$product['stock'].'</td>';
          echo '</tr>';
        }

        echo '</table>';
      }

    } else {

      // retrieve the products
      $products = array();

      $query = "SELECT * FROM products";
      $result = mysql_query($query);
      while ($row = mysql_fetch_assoc($result)) {
        array_push($products,$row);
      }

      // generate nice HTML output with the results we found

      echo '<table>';
      echo '<tr>';
      echo '<th>NAME</th>';
      echo '<th>STOCK</th>';
      echo '<td>SOLD ITEMS</th>';
      echo '</tr>';

      foreach($products as $product) {

        echo '<tr>';
        echo '<td>'.$product['name'].'</td>';
        echo '<td>'.$product['stock'].'</td>';
        echo '<td>';
        echo '<form method="post">';
        echo '<input type="hidden" name="lastupdate" value="'.$product['lastupdate'].'" />';
        echo '<input type="hidden" name="name" value="'.$product['name'].'" />';
        echo '<input type="text" name="change" />';
        echo '<input type="submit" name="update" value="update" />';
        echo '</form>';
        echo '</td>';
        echo '</tr>';
      }

      echo '</table>';
    }
?>
Last edited by timvw on Mon May 17, 2004 9:01 am, edited 1 time in total.
User avatar
tim
DevNet Resident
Posts: 1165
Joined: Thu Feb 12, 2004 7:19 pm
Location: ohio

Post by tim »

handy
dave420
Forum Contributor
Posts: 106
Joined: Tue Feb 17, 2004 8:03 am

Post by dave420 »

What I'd suggest, is on the page that handles the $_POST handling, after you've done the processing, use header("location: /this_script.php"); exit(); to bounce the user back to the current page, but without the post data (so if they press F5, their browser won't ask to submit the data again). Couple that with cache-busting (covered in detail, pretty much everywhere), and you can be confident that users of the screen can't submit the same information twice, and never see out-of-date information. I use this technique for our sales tools in use on our intranet, and it works great.
User avatar
Pyrite
Forum Regular
Posts: 769
Joined: Tue Sep 23, 2003 11:07 pm
Location: The Republic of Texas
Contact:

Post by Pyrite »

Is there any other way to Clear the POST data other than using the header() function to re call the page?
magicrobotmonkey
Forum Regular
Posts: 888
Joined: Sun Mar 21, 2004 1:09 pm
Location: Cambridge, MA

Post by magicrobotmonkey »

unset($_POST); ??
Post Reply