Function to revert mysql statement - php

How can I implement a undo changes function in my php application, so if the user for example added a new member to a group, or deleted item a notification message will appear: The selected item have been deleted, undo delete action(link that trigger the undo function).
The following is a working code:
public function deleteRecord($id) {
$group = $this->query('SET autocommit=0');
$group = $this->query('DELETE FROM `members` WHERE memberID = '.$id.'');
}
public function undo_delete() {
$member = $this->query('rollback');
if($member) {
trigger_error('Undo action has been completed. ', E_USER_NOTICE);
return true;
}
}
The only problem with this code is the transaction has not been submitted, so rollback or log out will undo all the changes that has been done during the user session, I am not sure if this is the best approach to create a redo function, any suggestions?

You will not be able to undo with 2 different requests .Because when you will call the undo . It will undo all delete happened for all the members . To solve it I would suggest -
You create a new table where you keep the information of the deleted record, eg- deleted_members.
When user calls for undo fetch the data from the deleted_members table and insert into original members table.
After successful insert in original table ,remove the data from the delete information table.
I hope this helped.

Transactions do not survive across PHP requests. If a transaction is still open at the end of the first request, it will be implicitly rolled back when the MySQL connection closes at the end of the request. As a result, there is no longer a transaction to roll back when an undo is requested later.
If you want to implement this functionality, it will need to be implemented in application logic.

Related

PHP MySQL update not execute if call twice in short time

I'm using CodeIgniter with MySQL. The case is, customer do shop, and pay with account.
Code as follow:
$globallockname = 'MEM_LOCK_ORDER_PAYLOCK';
//lock with memcache add
if (!$this->getGlobalLock($globallockname, 10)){
dexit(array('error'=>true,'msg'=>'get loack failed'));
}
//check if account is enough
$member = $this->admin_model->getItem('member', array('id'=>$this->_member['id']), false);
if ($this->_member['account'] >= $amountpay){]
//pay
$this->admin_model->update('member', "`account` = `account` - ".$amountpay, "`id` = ".$this->_member['id']);
}else{
//unlock
$this->delGlobalLock($globallockname);
dexit(array('error'=>true,'msg'=>'account is short, please recharge'));
}
//unlock
$this->delGlobalLock($globallockname);
When a customer clicks "buy" once, everything goes fine, but if they click "buy" twice in a short time, this script will execute twice, and the first update will not work.
I've checked the return of $db->query, each of them return true.
You are updating the same field twice. So you will get the last update only.
ie the first update is overwritten with second update..
hope this helps...
once buy button is clicked, disable it and show with progressing caption. This will avoid further clicks.
I did a stupid thing, there's nothing wrong with mysql or codeigniter. In my index controller, I go member member info and then set it again, so the memcache lock did not work, the information was overwrite...

MySQL - INSERT validation in query

Take the following scenario:
Item cost = 30
User money = 25
Form won't submit if user doesn't have enough money.
$error = false;
if($user_money < $item_cost){
//don't submit form
$error = true;
}
But is that enough? Can a user get around it and purchase the item even if there isn't enough money?
Would it be better to do something like this:
Keep the above:
$error = false;
if($user_money < $item_cost){
//don t submit form
$error = true;
}else{
$myclass->purchaseItem($item_id, $user_id);
}
public function purchaseItem($item_id, $user_id) {
//do the validation here again something like. I don t know how to do the query exactly.
$q = $this->db->mysqli->prepare("INSERT INTO buys (bl bla blah) VALUES (?,?,?) IF ... user has enough points in user_points table");
}
Hope that makes sense and I don't get down voted.
In your database you can use a trigger to check the constraint. Depending on you model you might need a transaction to prevent a record from being inserted incorrectly.
Assuming the following:
Two tables:
buys
wallet
If a user buys something (definite BUY, so not a shopping cart placement action), the wallet is updated in the same action.
To do this you can either write a transaction: See How to start and end transaction in mysqli? on how to.
and use 2 statements:
UPDATE wallet SET amount=amount-{buyAmount} WHERE user=?;
INSERT INTO buys (amount,user,orderId) VALUES (?,?,?);
(Of course buyAmount is also a ? in the prepared statement)
Or you can use a trigger. The trigger has to lock the user record when inserting in the buys table:
CREATE TRIGGER updateWallet() BEFORE INSERT ON buys
BEGIN
SET #updatedWalletAmount=0;
SELECT amount-NEW.buyAmount FROM wallet WHERE user=NEW.user FOR UPDATE;
IF(#updatedWalletAmount>0) THEN
UPDATE wallet SET amount=#updatedWalletAmount;
ELSE
SIGNAL SQLSTATE 'ERR0R'
SET
MESSAGE_TEXT = 'Not enough money',
MYSQL_ERRNO = 'USER-1';
END;
END;
The error will have to be caught in php.
Validating data on server shouldn't be made twice. Validating the data on the php side would be easier and as reliable as on your database server.
For more information on validating input data you can check this.

How to remove previous sql insert if one after fails?

When I insert a new record into one table (work_log), I update a record in another table (employers) with the last inserted record from work_log.
But if updating employers-table doesn't succeed, after successfully inserted the new record into work_log, I need to remove the newly added record to work_log since that entry would not be valid anymore.
Here's my script so far:
/**
* This first part has no direct affect on the question, but serves as additional information to understand the script better..
* - - -
* First, a new work session is inserted (this session has nothing to do with browser session)
* If this fails, the script does not continue, and the user is redirected back to the form with an error-message.
* otherwise, the script continues, and try to activate the session by adding a new work_log entry.
*/
$ins_session = $con['site']->prepare('INSERT INTO work_sessions (fields) VALUES (?)');
$ins_session->execute(array(values));
if($ins_session){
KD::notice('success','New work session is created.');
$session_id = $con['site']->lastInsertId();
} else {
KD::notice('error','Work session was not created.');
KD::redirect(); // stops the script, and redirects
}
/**
* This part affects my question
* - - -
* Add a new entry to the work log in order to automatically start the work session.
* If this entry is successfully inserted, then add an indicator to the corresponding employer, in the employers table, to indicate that this employer has an active session (and which one it is).
*/
$ins_work_log = $con['site']->prepare('INSERT INTO work_log (fields) VALUES (?)');
$ins_work_log->execute(array(values));
if($ins_work_log){
$upd_employer = $con['site']->prepare('UPDATE employers SET fk_work_sessions_id = ? WHERE id = ?');
$upd_employer->execute(array($session_id,$_POST['employer_id']));
if($upd_employer){
KD::notice('success','New session was created and started.');
KD::redirect();
} else {
// need to remove the entry from work_log.
KD::notice('Work session was created, but not started. Please start the session manually.');
}
}
To my understanding, I have to delete the last inserted record in the work_log-table?
Is there any other way to do this? like, in another order, or to automatically remove the entry from work_log if this (the update query) fails?
The work_log-table is innoDB, and row format is compact if that is important to know...
UPDATE
I've set it up like this:
It seems to work, but I'm a bit unsure if I'm using it correctly regarding the if/else statements.
$con['site']->beginTransaction();
$ins_work_log = $con['site']->prepare('INSERT INTO work_log (fields) VALUES (?)');
$ins_work_log->execute(array(values));
if($ins_work_log){
# update employer
$upd_employer = $con['site']->prepare('UPDATE employers SET fk_work_sessions_id = ? WHERE id = ?');
$upd_employer->execute(array($session_id,$_POST['employer_id']));
if($upd_employer){
$con['site']->commit();
KD::notice('success','New session was created and started.');
} else {
$con['site']->rollBack();
KD::notice('error','Work session was created, but not started. Please start the session manually.');
}
//
} else {
$con['site']->rollBack();
KD::notice('error','');
}
KD::redirect();
Will if($ins_work_log), and if($upd_employer), have any affect when the query hasn't been committed yet?
This is a classic case for using START TRANSACTION, COMMIT, and ROLLBACK.
http://dev.mysql.com/doc/refman/5.0/en/commit.html
Just make sure you are using a database engine that supports it.
Pseudocode:
query("START TRANSACTION;");
query("INSERT INTO table1 ...");
if (query("INSERT INTO table2 ..."))
query("COMMIT;");
else
query("ROLLBACK;");

Problem with UPDATE MySQL

I have a bit of an issue with my code.
I'm making an administrative panel for users to add things to the database. On occasion, they might try to save data without changing it (open a dialog, click save without changing anything). This, of course, will make mysql_affected_rows() return '0' when checking to see if the UPDATE query worked.
Is there another query to make that will always UPDATE regardless of whether the data is the same or not (or can I modify a simple UPDATE query to always update)?
EDIT
This is for users who don't have programming experience. Of course you wouldn't want to update if there's no reason to, but when a user tries to update and it doesn't happen I end up showing a failure message. Rather than there being something wrong, its just it doesn't need to be updated. I need a way to show the user that, instead of a generic 'failure' message. If it failed for another reason, I still need to know.
From the MySQL Documentation:
If you set a column to the value it currently has, MySQL notices this
and does not update it.
Instead of checking mysql_affected_rows, just check to see if the query was successful:
if(!mysql_query("UPDATE ..."))
{
//failure
}
else
{
$verification = mysql_query("SELECT ROW_COUNT() as rows_affected");
$row = mysql_fetch_row($verification);
$rows_affected = $row[0];
if ($rows_affected > 0)
{
//update was performed
}
else
{
//no update was needed
}
}

Prevent Users from Performing an Action Twice

We have some problems with users performing a specific action twice, we have a mechanism to ensure that users can't do it but somehow it still happens. Here is how our current mechanism works:
Client side: The button will be disabled after 1 click.
Server side: We have a key hash in the URL which will be checked against the key stored in SESSIONS, once it matches, the key is deleted.
Database side: Once the action is performed, there is a field to be flagged indicating the user has completed the action.
However, with all these measures, still there are users able to perform the action twice, are there any more safer methods?
Here is the partial code for the database side:
$db->beginTransaction();
// Get the user's datas
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// Proceed with performing the action
// --- Action Here ---
// Double checking process, the user data is retrieved again
$user = $db->queryRow("SELECT flag FROM users WHERE userid = {$auth->getProperty('auth_user_id)}");
if ($user['flag'] != 0) {
$db->rollback();
// Return with error
return false;
}
// --- The final inserting query ---
// Update the flag
$db->query("UPDATE users SET flag = 1 WHERE userid = {$auth->getProperty('auth_user_id)}");
$db->commit();
return true;
It is good to see that you have taken all measures to defeat the bad guys. Speaking in terms of bad guys:
Client side: This can easily be bypassed by simply disabling javascript. Good to have anyways but again not against bad guys.
Server side: This is important, however make sure that you generate a different hash/key with each submission. Here is a good tutorial at nettutes on how to submit forms in a secure fashion.
Database side: Not sure but I suspect, there might be SQL injection problem. See more info about the SQL Injection and how to possibly fix that.
Finally:
I would recommend to you to check out the:
OWASP PHP Project
The OWASP PHP Project's goal (OWASP PHP Project Roadmap) is to enable developers, systems administrators and application architects to build and deploy secure applications built using the PHP programming language.
Well the JS method and Hash method may be cheated by some notorious guy, but 3rd method seems to be very good in order to protect the redundancy. There must be some programming flaw to get passed this.
Why don't u just check the flag field on the page where you are inserting the values rather than where user performing the action (if you are doing it now)
Pseudocode follows:
<?
$act_id; // contains id of action to be executed
$h = uniqid('');
// this locks action (if it is unlocked) and marks it as being performed by me.
UPDATE actions SET executor = $h WHERE act_id = $act_id AND executor = '';
SELECT * FROM actions WHERE executor = $h;
//
// If above query resulted in some action execute it here
//
// if you want to allow for executing this exact action in the future mark it as not executed
UPDATE actions SET executor = '' WHERE act_id = $act_id;
Important things:
First query should be update claiming
the action for me if it is yet
unclaimed.
Second should be query
grabbing action to execute but only
if it was claimed by me.

Categories