Reputation: 13273
EDIT: I am very sorry, I found the mistake and it's very stupid. (See answer)
I seem to have a very strange problem. I have a website that uses 3rd party authorization for login. My users use two social networks for this: Facebook and Vkontakte (Russian analogue).
On log in, I search the database for the user with the passed social id (which is, depending on the chosen social network, user's id in FB or VK, for which I have two different columns) and fetch it.
In case a user has accounts in both FB and VK and has logged in through both of them, he or she now has two separate accounts on my site. However, he can join them into one, by logging in through one social network (this will be his master account) and using the 'user_bind' function with another social network.
This function finds the user's another account and relinks all data in the database to the master account. It then deletes the other account and adds its social id to the master account, so that now the user can log in through both social networks. Social id column has a UNIQUE index, naturally.
However, when the script executes, it seems to execute the UPDATE, which adds the social id, before the DELETE statement, which removes the old user. This produces an error, because it attempts to add an existing social id (because the old user is still there).
When I check the database after the script execution, the old user is gone, so I guess that means that the DELETE statement is indeed executed, but with a delay, in which other statements are executed. The MySQL Workbench's log confirms this, though I'm not sure whether it's reliable.
My question is how do I ensure that the DELETE (or any other MySQL statement for that matter) has actually been executed before executing the rest of the script? And why does this happen anyway?
Here's the adequately-commented code (though I will gladly accept an answer which has no code in it and just explains the principle).
The user_bind function:
function user_bind($eSourceType)
{
//$eSourceType can be 'fb' or 'vk', depending on the social network of the secondary account
$usrMe=get_gl_me(); //gets the user's account, through which he is logged in - the master account
if ($eSourceType=='fb') //if the social network that we are binding this account to is Facebook
{
$vSidName='facebook_id'; //name of the column which contains the social id
if (!$usrMe->get_private_property("facebook_id") & $usrMe->get_private_property("vkontakte_id") ) //check if the master account really doesn't have facebook_id set
{
$fb=get_facebook();//gets facebook object (from FB PHP SDK)
$sid=$fb->getUser();//gets user's id in facebook (social id)
}
else
{
error("The account has facebook_id set");
}
}
elseif($eSourceType=='vk')//same as before, but the id is fetched through $_GET, not object
{
$vSidName='vkontakte_id';
if ($usrMe->get_private_property("facebook_id") & !$usrMe->get_private_property("vkontakte_id") ) //check if it's the right account
{
$sid=$_GET['uid'];
}
else
{
error("The account has vkontakte_id set");
}
}
if(!$sid) //if we couldn't retrieve the social id
{
error("Can't bind: \$sid not set.");
}
$idNew=$usrMe->get_id();//get id (database id) of the master account
$usrOld=fetch_user_by_sid($sid, $eSourceType, true); //fetches the 'user' object by the social id we retrieved before
if ($usrOld)//if there is a user with this social id (if there is a secondary account)
{
$idOld=$usrOld->get_id();//get id of the secondary account
$tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments"); //get tables in which we have to relink users
foreach($tblsRelink as $tbl)
{
//update set users_idusers to userid
$sp=new Statement_Parameter; //this is a class from PHP.com: http://php.net/manual/en/mysqli-stmt.bind-param.php. It allows to bind variables to the prepared statement in MySQLi without much pain
$query="UPDATE $tbl SET users_idusers=" . db_stmt_operands($idNew, $sp, 'idNew') . " WHERE users_idusers=". db_stmt_operands($idOld, $sp, 'idOld'); //db_stmt_operands inserts question marks in the query, while binding the variables through Statement_Parameter
$affected_rows=db_submit($query, $sp);//see below for the db_submit() function explanation
}
//delete old user
$sp=new Statement_Parameter; //clear Statement_Parameter
$query="DELETE FROM users WHERE idusers=" . db_stmt_operands($idOld, $sp, 'idOld');
$affected_rows=db_submit($query, $sp);
echo "<br>affected: $affected_rows<BR>"; //this actually returns 1
//lets see if the user was actually deleted
$usrTest=fetch_user_by_sid($sid, $eSourceType, true); //fetch the user by the social id
if($usrTest) //if a user is fetched
{
debug_array($usrTest); //custom implementation of print_r
error("User still exsists. Oh no.");//it always does
}
}
$usrMe->set_private_property($vSidName, $sid);//sets the property 'facebook_id' or 'vkontakte_id' to the social id that we got in the beginning
$usrMe->update();//UPDATE statement, which brings the object's properties in the database up to date (in our case: adds the social id)
//the UPDATE statement doesn't execute because the old user is still there
}
The db_submit function:
function db_submit($query, $sp=NULL)
{
$mysqli = db_connect(); //fetches PHP MySQLi object
if ($stmt = $mysqli->prepare($query)) //if the statement is successfully prepared
{
if($sp)//if there is a Statement_Parameter passed
{
$sp->Bind_Params($stmt); //bind parameters from SP
}
if($stmt->execute())//try to execute the statement
{
//on success
if ($mysqli->insert_id) //if this was an INSERT
{
return $mysqli->insert_id;
}
else //if this was DELETE or UPDATE
{
return $mysqli->affected_rows;
}
}
else
{
//on failure
error("Could not submit: could not execute statement. Query: $query." . $stmt->error); //this kills the script
}
}
else
{
error("Could not submit. Query: $query." . $mysqli->error);
}
}
Upvotes: 0
Views: 216
Reputation: 13273
The thing is: private_properties (including the social ids) or the object 'user' are stored in a separate table ('users_private'), which was linked to the main table ('users') through a foreign key.
I included the 'users_private' table in the array of tables that require relinking:
$tblsRelink=array("comments", "posts", "users_private", "vote_posts", "vote_comments");
This resulted in the record in 'users_private' for the old user being relinked to the new user (which now had 2 records - how reckless of me not to make this field UNIQUE). So when the old user got deleted, its associated 'users_private' record was not, because it was now linked to the new user. Naturally, attempt to add the social id was producing an error, because that id already was there, relinked from the old user.
This could be prevented by either
or, even better, both.
Upvotes: 1