PHP MYSQL reusable delete query - php

trying to make a delete function which will delete from any table using a field and value.
problem is its not working and im not getting any errors from it, I can add stuff to the database but not delete them.
if (isset($_REQUEST['delete']))
{
$table = $_POST['table'];
$field = $_POST['field'];
$value = $_POST['value'];
deleteFromTable($database, $table, $field, $value);
}
function deleteFromTable($pdo, $table, $field, $value)
{
$stmt = $pdo->prepare('DELETE FROM :table WHERE :field = :value');
$criteria = [
'table' => $table,
'field' => $field,
'value' => $value
];
$stmt->execute($criteria);
}

Tables cannot be used as a parameter. While insecure, you're better off just placing it into the query:
$stmt = $pdo->prepare("DELETE FROM `$table` WHERE $field = :value");
or
$stmt = $pdo->prepare('DELETE FROM `'.$table.'` WHERE `'.$field.'` = :value');
You really should check to make sure that $table is an acceptable value before you proceed with the query.
You also need colons on your parameters, like so:
$criteria = [
':value' => $value
];

You cannot use parameters for table names, and you also cannot use parameters for column names. As it is currently written, you ARE generating a syntax error when you execute your prepared statement, whether or not you can see it. If you do this:
$stmt = $pdo->prepare("DELETE FROM `$table` WHERE :field = :value");
you will not generate a syntax error, but it will still not be doing what you think it is. It will not treat :field as a column identifier, but as a value! So if the user posts something like
$_POST = ['table' => 'table1','field' => 1, 'value' => 1]
then in effect, the query will be
DELETE FROM `table1` WHERE '1' = '1'
This will delete every row from your table, which I assume you would not want.
The statement you need is
$stmt = $pdo->prepare("DELETE FROM `$table` WHERE `$field` = :value");
But concatenating user input into your query like this is obviously a dangerous thing to do.
If you want to safely create a query like this, you can use a whitelist approach, where you define which tables can be deleted from, and by which keys, and check your user input against that before running your delete. Here is a basic example:
$allowed_delete_keys = array(
'table1' => array('columnA', 'columnB'),
'table2' => array('columnX', 'columnY')
);
if (!isset($allowed_delete_keys[$_POST['table']])) {
echo "You can't delete from this table.";
exit;
} else {
if (!in_array($_POST['field'], $allowed_delete_keys[$_POST['table']])) {
echo "You can't delete records based on this key";
exit;
} else {
deleteFromTable($database, $table, $field, $value);
}
}

Related

Creating an SQL Query based on data from an Array

I'm trying to find a way to simplify an existing function which communicated with our database. The function currently has several parameters (upwards of 15), and everytime a record is added or updated, all the parameters are required.
I have the following PHP Function (simplified):
function addSomethingToDB($var1, $var2, $var3, $var4...) {
# Do SQL Injection checks
...
$query = 'INSERT INTO `table` (`var1`,`var2`,`var3`,`var4`) VALUES ($var1, $var2, $var3, $var4)';
# OR
$stmt = $db->prepare('INSERT INTO `table` (`var1`,`var2`,`var3`,`var4`) VALUES (?,?,?,?)');
$stmt->bind_param('ssss', $var1, $var2, $var3, $var4);
}
The above code obviously gets pretty messy if you have more than a few variables. And it's difficult to work with if not all variables are required. Because of this I attempted a second scenario where I either had one main/required parameter followed by an array or I just had an array as the parameter.
function addSomethingToDB($var1, array $attributes) {}
The goal here was to allow the array to have a more flexible approach in case the SQL query either needs to be extended in the future, or to build the SQL query based on optional values.
For example:
If Var2 and Var4 are not provided, the array would look like:
{
'var1': 'Var1_Value',
'var3': 'Var3_Value'
}
and the SQL would be:
$query = 'INSERT INTO `table` (`var1`,`var3`) VALUES ($var1, $var3);
As you can see, in the above scenario, the query was adapted for only the necessary values.
What I want to try and achieve is to build the SQL query based on the values provided. The first was I assume would be to have an IF ELSE statement or a SWITCH. Which gives me something weird like the following:
function getlogs($type, $id='') {
$types = array('client_log', 'system_log', 'user_log', 'api_log', 'project_log', 'lead_log');
if (in_array($type, $types)) {
if ('client_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `client_id` = ' . $id . ' AND `type` = "client_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "client_log"';
}
} elseif ('project_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `project_id` = ' . $id . ' AND `type` = "project_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "project_log"';
}
} elseif ('user_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `staff_id` = ' . $id . ' AND `type` = "staff_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "staff_log"';
}
} elseif ('lead_log' == $type) {
if (!empty($id)) {
$query = 'SELECT * FROM `logs` WHERE `client_id` = ' . $id . ' AND `type` = "lead_log"';
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = "lead_log"';
}
} else {
$query = 'SELECT * FROM `logs` WHERE `type` = ' . $type;
}
$logs = Config::$db->query($query);
return $logs->fetch_all(MYSQLI_ASSOC);
} else {
return 'invalid log type';
}
$stmt->close();
}
The above is not quite the code I want to be writing, it's a similar example where the query related to the Log Type is being called. But that is a lot of mess that is not pleasing to look at. Also, the above code does not use Arrays which is what I hope to be using.
Lastly, the code I am hoping to write is mostly related to Updating existing records. So, say we have a User. The user has an Email and a Password and Address. According to the above code (first one) we will be updating the Email, Password, and Address every time the user updates any one of his field. I would like to avoid that.
My assumption is that I'd have to do something like so:
# 1. Loop Array using maybe For or Foreach
# 2. Have a whitelisted array of allowed values.
# 3. Append to query if an Array value exists.
# 4. Run query.
I fear my problem is at Point 3. I can't seem to figure out how to build the query without going through a lot of messy IF ELSE statements.
Now, by this point, I have certainly searched around SO to find a similar question, however, searches related to SQL and Arrays are almost entirely related to adding "multiple rows in a single SQL query" or something similar.
You can approach this by using arrays, in which keys are column name and containing the values
$columns = [
'field1' => 'value1',
'field2' => 'value2',
'field3' => 'value3',
'field4' => 'value4'
];
addSomethingToDB($columns);
function addSomethingToDB($cols){
# Do SQL Injection checks
$query = "INSER INTO `tablename` ( ".implode(",",array_keys($cols))." ) VALUES ( '".implode("','",array_values($cols))."' )";
}

MySQLi query to loop through array and update multiple rows

I have an array like:
$postdata[1] = 'This';
$postdata[2] = 'That';
$postdata[3] = 'The other';
And I want to loop through the array and update all of the rows where ID corresponds to the array key. Like:
foreach ($postdata as $key => $value) {
if ($key == 1) {
$update = $db->query("UPDATE site_email_templates SET Content='$postdata[1]' WHERE ID = 1");
} else if ($key == 2) {
$update = $db->query("UPDATE site_email_templates SET Content='$postdata[2]' WHERE ID = 2");
} else if ($key == 3) {
$update = $db->query("UPDATE site_email_templates SET Content='$postdata[3]' WHERE ID = 3");
}
}
What would be the simplest way to do this, not particularly knowing how many array keys there are, and keeping it all in one query?
You need to use prepared statements in order to avoid errors and vulnerabilities of all sorts and also to get some minor performance gain
$stmt = $db->prepare("UPDATE site_email_templates SET Content=? WHERE ID = ?");
$stmt->bind_param("ss", $content, $id);
foreach ($postdata as $id => $content)
{
$stmt->execute();
}
Reference: How can I prevent SQL injection in PHP?
Note: My answer is based on the PDO driver which in many aspects is better than mysqli. If you need mysqli solution please check the other answer provided by #Your Common Sense
The code below is tested on real environment and served with prepared statement preventing SQL-injection:
$sql = "UPDATE `site_email_templates` SET `Content` = (:content) WHERE `Id` = (:id)";
$stmt = $dbConn->prepare($sql);
foreach ($postdata as $id => $content)
{
$stmt->execute([':id' => $id, ':content' => $content]);
}
For more details about SQL injection you can read more:
https://www.owasp.org/index.php/SQL_Injection
For maximal speed, IODKU can do all the updates in a single statement. Caution: You should not use this for updating if you don't know that the ids exist.
INSERT INTO t
(id, -- A PRIMARY or UNIQUE key
col1, col2) -- column(s) to change
VALUES
(111, 22, 33),
(222, 33, 44),
...
ON DUPLICATE KEY UPDATE
col1 = VALUES(col1),
col2 = VALUES(col2);
You must provide some way to "bind" or "escape" the values to avoid sql-injection.

Update PDO bind key and values

so I recently switched to use pdo instead of mysqli. now I have a question about binding key values with mysqli. I looped through it escaped the key's and values and used them in my queries now I want to do the same thing in pdo but this isn't working and I don't know why this is my code:
foreach($userdata as $key => $value){
$sql = $this->db->prepare("UPDATE `users` SET :key = :value WHERE `id` = :userid");
$sql->execute(
array(
'key' => $key,
'value' => $value,
'userid' => $userid
)
);
}
ofcourse there's more code to see if it needs update and other type of inputs that need more validation but this is the main query i used but without binding. is this possible with pdo and binding parameter's and values?
this is the error i'm getting:
SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'username' = 'sjerdus' WHERE `id` = '2''
You have this error because you tried to update a field named dynamically. The SET :key = ... can't work because when the parameter will be replaced by its value, it will be escaped (and quoted) by PDO.
If you want to put a variable field name that will be updated, you have to manually concatenate the field name, but you'll have to check for the security yourself.
Try something like this :
//Security checks for $field variable...
$sql = $this->db->prepare("UPDATE `users` SET " . $field . " = :value WHERE `id` = :userid");
$sql->execute(
array(
'value' => $value,
'userid' => $userid
)
);
Here is you could do. I assume that the $userid you have provided is an integer. Where as when you use params in execute() directly. They are considered as string.
foreach($userdata as $key => $value){
$sql = $this->db->prepare("UPDATE `users` SET :key = :value WHERE `id` = :userid");
$sql->bindParam(':key', $key);
$sql->bindParam(':value', $value);
$sql->bindParam(':userid', $userid);
$sql->execute()
);
}
http://php.net/manual/en/pdostatement.bindparam.php

PDO: IF statement in query?

I was wondering if it is possible to use something like a if statement in a PDO query.
My current query is:
public static function UpdateTable( $id, $name, $variable )
{
self::query("
UPDATE table
SET name = :name,
WHERE id = :id
", array(
":name" => $name,
":id" => $id
));
}
Which is prepared and executed by this:
public static function query($q, $params)
{
$stmt = self::$con->prepare( $q );
$stmt->execute( $params );
}
All of this works fine, but I also got the var $variable.
And I want that database column to be updated if $variable is not empty.
I can make a if / else statement in the "UpdateTable" function, but that quite ugly in my opinion en I couldn't find anything else on Google how to do this. So I would like to learn how to ;)
You can also tokenize your your queries based off any conditions in php like the following:
public static function UpdateTable( $id, $name, $variable )
{
$params = array(":name" => $name, ":id" => $id);
$update = "";
if(isset($variable)) {
$update = ", columnname = :variable ";
$params[':variable'] = $variable;
}
$query = "UPDATE table SET name = :name {$update} WHERE id = :id ";
self::query($query,$params);
}
You really should do it in the PHP function - it would save a trip to the database in case $variable is empty. But, if someone points a gun to your head and threaten to pull the trigger unless you do it in the query, you can add it to the where clause:
UPDATE table
SET name = :name,
WHERE id = :id
AND :variable IS NOT NULL
or something like that(never worked with PHP, so I don't know if passing a null variable will turn into SQL null...)

Ignore particular WHERE criteria

I want to execute a parameterized query to perform a search by user-supplied parameters. There are quite a few parameters and not all of them are going to be supplied all the time. How can I make a standard query that specifies all possible parameters, but ignore some of these parameters if the user didn't choose a meaningful parameter value?
Here's an imaginary example to illustrate what I'm going for
$sql = 'SELECT * FROM people WHERE first_name = :first_name AND last_name = :last_name AND age = :age AND sex = :sex';
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
Obviously, this will not work because the number of provided parameters does not match the number of expected parameters. Do I have to craft the query every time with only the specified parameters being included in the WHERE clause, or is there a way to get some of these parameters to be ignored or always return true when checked?
SELECT * FROM people
WHERE (first_name = :first_name or :first_name is null)
AND (last_name = :last_name or :last_name is null)
AND (age = :age or :age is null)
AND (sex = :sex or :sex is null)
When passing parameters, supply null for the ones you don't need.
Note that to be able to run a query this way, emulation mode for PDO have to be turned ON
First, start by changing your $sql string to simply:
$sql = 'SELECT * FROM people WHERE 1 = 1';
The WHERE 1 = 1 will allow you to not include any additional parameters...
Next, selectively concatenate to your $sql string any additional parameter that has a meaningful value:
$sql .= ' AND first_name = :first_name'
$sql .= ' AND age = :age'
Your $sql string now only contains the parameters that you plan on providing, so you can proceed as before:
$query = $db->prepare($sql);
$query->execute(array(':first_name' => 'John', ':age' => '27');
If you can't solve your problem by changing your query... There are several libraries that help with assembling queries. I've used Zend_Db_Select in the past but every framework likely has something similar:
$select = new Zend_Db_Select;
$select->from('people');
if (!empty($lastName)) {
$select->where('lastname = ?', $lastname);
}
$select->order('lastname desc')->limit(10);
echo $select; // SELECT * FROM people WHERE lastname = '...' ORDER BY lastname desc LIMIT 10
I've tested the solution given by #juergen but it gives a PDOException since number of bound variables does not match. The following (not so elegant) code works regardless of no of parameters:
function searchPeople( $inputArr )
{
$allowed = array(':first_name'=>'first_name', ':last_name'=>'last_name', ':age'=>'age', ':sex'=>'sex');
$sql = 'SELECT * FROM sf_guard_user WHERE 1 = 1';
foreach($allowed AS $key => $val)
{
if( array_key_exists( $key, $inputArr ) ){
$sql .= ' AND '. $val .' = '. $key;
}
}
$query = $db->prepare( $sql );
$query->execute( $inputArr );
return $query->fetchAll();
}
Usage:
$result = searchPeople(array(':first_name' => 'John', ':age' => '27'));

Categories