I am trying to filter some inputs of the user with select boxes. I am figuring if there is a better way doing this.
if(isset($_POST['action']))
{
$sql = "SELECT * FROM occasions WHERE naam IS NOT NULL";
if(isset($_POST['merk'])){
$merk = $_POST['merk'];
$merkQuery = implode(',', array_fill(0, count($merk), '?'));
$sql .= " AND merk IN(".$merkQuery.")";
}
if(isset($_POST['brandstof'])){
$brandstof = $_POST['brandstof'];
$brandstofQuery = implode(',', array_fill(0, count($brandstof), '?'));
$sql .= " AND brandstof IN(".$brandstofQuery.")";
}
//We prepare our SELECT statement.
$statement = $pdo->prepare($sql);
if(isset($_POST['merk'])){
//Execute statement.
$statement->execute(array_merge(array_values($merk)));
}
if(isset($_POST['brandstof'])){
//Execute statement.
$statement->execute(array_merge(array_values($brandstof)));
}
if(isset($_POST['merk']) && isset($_POST['brandstof']))
{
$statement->execute(array_merge(array_values($merk), array_values($brandstof)));
}
else
{
$statement->execute();
}
}
Cause if there are many select boxes that need filtering, the code would become long. I was wondering if there is a better way of filtering multiple select boxes.
Here is an example: link
I would suggest renaming the post variables; grouping them into a single two dimensional array.
<input type="checkbox" name="data[merk][bmw]" />
<input type="checkbox" name="data[merk][skoda] />
and so forth.
What this does, is it allows you to use a foreach to iterate through whatever values are checked.
$data = $_POST['data'] ?? []; // null coalesce defaults to a blank array if post var is null
foreach($data as $category=>$val) {
settype($val, 'array');
$query = implode(',', array_fill(0, count($val), '?'));
foreach($val as $k=>$v) {
$params[] = $k;
}
// DON'T DO THIS!
$sql .= " AND $category IN(".$query.")";
}
The reason you shouldn’t do it as shown is because you should never build a query with user-supplied data.
What you can do, however, is map user-supplied data with hard-coded data.
$map = [
// form value => db field
'merk' => 'MERK',
'brandstof' => 'BRANDSTOF',
// ... etc
];
and then when building your query,
$sql .= " AND $map[$category] IN($query)";
In the meantime, you have built your parameters in $params.
—-
Bottom line, what we have done is refactor the code since we were noticing things getting repeated. For example, you were having to repeat code for each occasion(?). One solution would be to continue to check each post value and call a function to calculate the ?s. But even then, it would be repetitive to type out all those isset()s.
In retrospect, it probably would have been better to do inputs like this:
<input type="checkbox" name="data[merk][]" value="bmw" />
<input type="checkbox" name="data[merk][]" value="skoda" />
This would no doubt be more intuitive, although you would still have to build the params array.
foreach($val as $v) {
$params[] = $v;
}
I have a form with 3 select boxes: age,room,type.
<form action="results.php" method="get">
<div class="form-group">
<select name="age">
<option value>Any</option>
<option value="1">15</option>
<option value="2">25</option>
<option value="3">30</option>
<option value="4">40</option>
</select>
</div>
<div class="form-group">
<select name="room">
<option value>Any</option>
<option value="1">1</option>
<option value="2">2</option>
</select>
</div>
<div class="form-group">
<select name="type">
<option value>Any</option>
<option value="1">Personal</option>
<option value="2">Business</option>
</select>
</div>
</form>
What i am trying to do with PDO is to make a small search.
If all variables are empty then my condition is:
$search = $db->query("SELECT * FROM table");
If 1 of them (as example the age) is not empty then i have:
if(!empty($_GET['age'])){
$age = $_GET['age'];
$search = $db->query("SELECT * FROM table WHERE age = '$age'");
}
Now, if 2 of them are npt empty i have:
if(!empty($_GET['age']) && !empty($GET['room'])){
$age = $_GET['age'];
$room = $_GET['room'];
$search = $db->query("SELECT * FROM table WHERE age = '$age' AND room = '$room'");
}
In order to avoid all possible search combinations, how can i make a search with the term if is not empty. I had made one in the past:
if(!empty($age)){
$where = "WHERE age = '$age'";
}
if(!empty($room)){
$where .= "and room = '$room'";
}
$query = "SELECT * FROM table $where";
How can i make it happen with PDO?? :/
I'd do something like this:
$param = array();
$query = 'SELECT ... FROM t WHERE 1=1';
if(!empty($_GET['age'])){
$param['age'] = $_GET['age'];
$query .= ' AND t.age = :age';
}
if(!empty($_GET['room'])){
$param['room'] = $_GET['room'];
$query .= ' AND t.room = :room';
}
if(!empty($_GET['type'])){
$param['type'] = $_GET['type'];
$query .= ' AND t.type = :type';
}
$dbh->prepare($query)->execute($param);
You might want to separate out the prepare and the execute. Check the return from the prepare before you try calling execute. Or, configure PDO can throw an exception when an error occurs, e.g.
$dbh->setAttribute(PDO::ERRMODE_EXCEPTION);
You will need to make a query builder of some kind. You will also want to use prepared statements, rather than directly injecting user-provided input into the sql query. That might look something like this:
<?php
$search = [
'age' => 42,
'room' => 'Millyway',
];
$criteria = [];
$params = [];
foreach($search as $field => $value) {
$criteria[] = "$field = :$field";
$params[$field] = $value;
}
$where = ($criteria ? ('WHERE ' . implode(' AND ', $criteria)) : '');
$query = "SELECT * FROM tablename $where";
$stmt = $db->prepare($query);
$stmt->execute($params);
while($obj = $stmt->fetchObject()) {
// iterate over your result set
}
Given search terms as key-values in $search (which can be any column and value in the table, and will need to be populated from wherever those values come from), this code will build $criteria, a set of WHERE clause fragments (using a parameterized sql parameter name, rather than injecting the value directly), and $params, the list of parameters to be passed into the (upcoming) prepared statement.
It then builds the full WHERE clause in $where, by either combining all of the $criteria that were built, or returning an empty string. This is then added directly into the query, and the query is executed using the parameters array that was built up. You then iterate over the result set like any other PDO query.
Among others, the main benefit of using parameterized SQL over injecting variables directly is that it protects you from SQL Injection attacks.
Note that there are many ways this code could be improved. You could easily put it in a function; add complexity to allow for different types of comparisons (e.g. <> or LIKE); even use it as the basis for a more complicated query builder that allows more complicated logic such as ((age = :age AND room = :room1) OR (room = :room2)); and so on. What you do is up to the needs of your application.
I have a multiselect field
<select name="duration[]" id="duration" title="Duration" multiple="multiple" size="3">
<option value="1">1 Months</option>
<option value="2">2 Months</option>
<option value="3">3 Months</option>
</select>
my php code implode multiple values i.e 123 as 1,2,3 and insert it in database. The problem is that the field is not a required field and when i leave it empty it give me error (Invalid arguments passed)
My php code below
$duration = array();
$duration = $_POST['duration'];
if($duration)
{
foreach($duration as $value)
{
$months[] = $value;
}
}
$sql = "SELECT * FROM tbl_courses WHERE duration IN (".implode($months, ',').") ";
thanks in advance
The two problems you have is you try to implode on user input which may not be an array, and your code is vulnerable to SQL Injection.
To address those you should first check if it's an array with is_array(), then check if it has any elements with count(), then finally implode but use array_map() to filter the values to prevent SQL Injection. This will not only prevent SQL Injection but will prevent syntax errors in your query because strings must be quoted in an IN clause.
function getInt($i) {
return (int)$i;
}
$inClause = '';
if(isset($_POST['duration']) && is_array($_POST['duration']) && count($_POST['duration']) > 0)
{
$inClause = 'WHERE ';
$inClause .= implode(', ', array_map('getInt', $_POST['duration']));
}
$sql = "SELECT * FROM tbl_courses $inClause";
$duration = array();
$duration = $_POST['duration'];
$sql = FALSE;
if($duration&&is_array($duration))
{
foreach($duration as $value)
{
$months[] = $value;
}
$sql = "SELECT * FROM tbl_courses WHERE duration IN (".implode($months, ',').") ";
}
if($sql){
//do something with sql
}
use is_array to check if $duration is an array.
Just remove the WHERE condition if it is not required
$sql = "SELECT * FROM tbl_courses";
if (count($months)>0)
$sql .= " WHERE duration IN (".implode($months, ',').") ";
and better use isset($_POST["x"]) instead of just an if.
So let's say $_POST['duration'] is null.
$duration = array();
$duration = $_POST['duration'];
Then you don't have a value to implode on because your foreach is not going to be executed.
One solution would be to put your code into an if( $_POST['duration'] ) respectively if( count($months) )
One of options is typecasting.
$duration = (array)$_POST['duration'];
Since you use contents of $_POST['duration'] in your query be aware of SQL injection techniques.
Check that $_POST['duration'] is set and build your WHERE statement if it is, if not leave it blank:
$where = !$_POST['duration'] ? '' : 'WHERE duration IN ('.implode($_POST['duration'],',').')';
$sql = 'SELECT * FROM tbl_courses '.$where;
I have also removed some of your unnecessary variable declaration and looping around $duration.
Please note you need to sanitise your data to protect against SQL injection. The easiest way in this case would to be to loop through the values and cast them as int:
foreach($_POST['duration'] as $value) {
$months[] = (int)$value;
}
$where = !$months ? '' : 'WHERE duration IN ('.implode($months,',').')';
$sql = 'SELECT * FROM tbl_courses '.$where;
I am trying to post the value chosen for a dropdown menu into my database table. But for some reason its not inputting the value into the database. I am trying to post cat_id into my database. So i use the code below to geenrate my dropdown list from values i alrady have in the database. Then below i have the function that inserts the info into the database. But for some reason its not working. I am suppose to put what is in select name="" right?
<select name="cat[<?=$row['pk_id']?>]">
<?php $cat = dbConnect("SELECT * FROM category");
if(empty($row['cat_id'])){
?>
<option value="">Select Category</option>
<?php
}
?>
<?php while($cat_r = mysql_fetch_array($cat)){
if($row['cat_id'] == $cat_r['cat_id']){
?>
<option value="<?=$cat_r[cat_id]?>" selected="selected"><?=stripslashes($cat_r[cat_name])?></option>
<?php
continue;
}
?>
<option value="<?=$cat_r[cat_id]?>"><?=stripslashes($cat_r[cat_name])?></option>
<?php } ?>
</select>
Here is my insert to MySQL
dbConnect("INSERT INTO post_info(add_to_random, show_home, source, display_vote_page, cat_id) values(1,1,1,0,cat[.$row['pk_id'].])");
Did i put something wrong here for the value for cat_id? I put cat[.$row['pk_id'].]) which is the select name="" for that dropdown list.
Code ported from comment:
if($_POST and $_POST['action'] == 'submit'){
foreach($_POST as $k=>$v){
$$k = $v;
}
foreach($cat as $k=>$v){
if($v =='') continue;
dbConnect("UPDATE twit_info set cat_id=" . $v . " where pk_id =". $k );
}
if(count($pkid)>0){
$pid = implode(',',$pkid);
dbConnect("UPDATE twit_info set add_to_vote = 1, display_vote_page = 1 where pk_id in(". $pid .")");
}
}
So in your foreach loop, you are extracting all post keys into global variables via the variable variable $$k (I'll get to this in a second). In your dbConnect() call, the quoting is incorrect. You should concatenate in $cat.
dbConnect("
INSERT INTO post_info
(add_to_random, show_home, source, display_vote_page, cat_id)
values(1,1,1,0, '" . mysql_real_escape_string($cat[$row['pk_id']]) . "')" );
I have added a call to mysql_real_escape_string(). This is necessary at a minimum, to protect all your queries from SQL injection. Your other UPDATE statements are also vulnerable at this point and you MUST perform some escaping on them as well.
Regarding the extraction of $_POST into global variables - I highly recommend against this. You are in effect imitating the behavior of register_globals which is considered very dangerous. The danger comes in that it is possible for anyone to post any key to your form, in addition to the ones you actually expect to receive, potentially initializing another variable in your script to a value sent via $_POST when your script doesn't expect it.
Although I really just recommend operating on $_POST directly, rather than extracting to global variables, if you must extract them to globals, I advise you to use a whitelist of acceptable $_POST keys:
// Make an array of allowed keys
$good_keys = ('action', 'cat', 'otherformkey');
foreach($cat as $k=>$v){
// Only extract if it is one of the allowed keys
if($v =='' || !in_array($k, $good_keys) continue;
// Cast to an integer
$v = intval($v);
$k = intval($k);
// Non-integer strings will cast to zero, so don't do the db action.
if ($v > 0 && $k > 0) {
dbConnect("UPDATE twit_info set cat_id=" . $v . " where pk_id =". $k );
}
// For string values which are quoted in the SQL (unlike the int values above)
// escape them with mysql_real_escape_string()
// $v = mysql_real_escape_string($v)
}
EDIT:
Thank you so much for your answers, you really amaze me with so much wisdom :)
I am trying to relay on TuteC's code a bit changed, but can't figure how to make it work properly:
$valor = $_POST['valor'];
$post_vars = array('iphone3g1', 'iphone3g2', 'nome', 'iphone41', 'postal', 'apelido');
foreach($post_vars as $var) {
$$var = "'" . mysql_real_escape_string($_POST[$var]). "', ";
}
$sql = "INSERT INTO clientes (iphone3g1, iphone3g2, nome, iphone41, postal, apelido, valor) VALUES ($$var '$valor')";
$query= mysql_query($sql);
I know there's a bit of cheating on the code, i would need to use substring so the $$var wouldn't output a "," at the end where i need the values, instead i tried to insert a variable that is a value ($valor = $_POST['valor'];)
What is going wrong?
And for the others who tried to help me, thank you very much, i am learning so much with you here at stackoverflow.
I have a form with several field values, when trying to write a php file that reads those values it came out a mostruosity:
$codigounico= md5(uniqid(rand()));
$modelo=$_POST['selectName'];
$serial=$_POST['serial'];
$nif=$_POST['nif'];
$iphone3g1=$_POST['iphone3g1'];
$iphone3g2=$_POST['iphone3g2'];
$iphone3g3=$_POST['iphone3g3'];
$iphone3g4=$_POST['iphone3g4'];
$iphone3gs1=$_POST['iphone3gs1'];
$iphone3gs2=$_POST['iphone3gs2'];
$iphone3gs3=$_POST['iphone3gs3'];
$iphone3gs4=$_POST['iphone3gs4'];
$iphone41=$_POST['iphone41'];
$iphone42=$_POST['iphone42'];
$iphone43=$_POST['iphone43'];
$iphone44=$_POST['iphone44'];
$total=$_POST['total'];
$valor=$_POST['valor'];
$nome=$_POST['nome'];
$apelido=$_POST['apelido'];
$postal=$_POST['postal'];
$morada=$_POST['morada'];
$notas=$_POST['notas'];
$sql="INSERT INTO clientes (postal, morada, nome, apelido, name, serial, iphone3g1, iphone3g2, iphone3g3, iphone3g4, total, valor, iphone3gs1, iphone3gs2, iphone3gs3, iphone3gs4, iphone41, iphone42, iphone43, iphone44, nif, codigounico, Notas)VALUES('$postal', '$morada', '$nome', '$apelido', '$modelo', '$serial', '$iphone3g1', '$iphone3g2', '$iphone3g3', '$iphone3g4', '$total', '$valor', '$iphone3gs1', '$iphone3gs2', '$iphone3gs3', '$iphone3gs4', '$iphone41', '$iphone42', '$iphone43', '$iphone44', '$nif', '$codigounico', '$notas')";
$result=mysql_query($sql);
This is a very dificult code to maintain,
can I make my life easier?
To restrict which POST variables you "import", you can do something like:
$post_vars = array('iphone3g1', 'iphone3g2', '...');
foreach($post_vars as $var) {
$$var = mysql_real_escape_string($_POST[$var]);
}
EDIT: Changed addslashes by mysql_real_escape_string (thanks #Czechnology).
The issue I see is repetition of the same names four times over. This is how I would reduce it to two occurrences (you could drop it to one with more finagling).
$sql = 'INSERT INTO clientes (postal, morada, nome, apelido, name, serial, iphone3g1, iphone3g2, iphone3g3, iphone3g4, total, valor, iphone3gs1, iphone3gs2, iphone3gs3, iphone3gs4, iphone41, iphone42, iphone43, iphone44, nif, codigounico, Notas) VALUES(:postal, :morada, :nome, :apelido, :modelo, :serial, :iphone3g1, :iphone3g2, :iphone3g3, :iphone3g4, :total, :valor, :iphone3gs1, :iphone3gs2, :iphone3gs3, :iphone3gs4, :iphone41, :iphone42, :iphone43, :iphone44, :nif, :codigounico, :notas)';
preg_match_all('/:(\w+)/', $sql, $inputKeys);
$tokens = $inputKeys[0];
$values = array_map($inputKeys[1], function($k){
return mysql_real_escape_string($_POST[$k]);
});
$sql = str_replace($tokens, $values, $sql);
$result = mysql_query($sql);
Depending on how you want to separate your logic, a reversed approach might be more useful, where you would specify the array of key names and iterate over that to generate the SQL string.
<?php
$inputKeys = array('postal', 'morada', 'nome', 'apelido', 'name', 'serial', 'iphone3g1', 'iphone3g2', 'iphone3g3', 'iphone3g4', 'total', 'valor', 'iphone3gs1', 'iphone3gs2', 'iphone3gs3', 'iphone3gs4', 'iphone41', 'iphone42', 'iphone43', 'iphone44', 'nif', 'codigounico', 'Notas');
$keyList = '(' . implode(',', $inputKeys) . ')';
$valueList = 'VALUES (';
foreach ($inputKeys as $k) {
$valueList .= mysql_real_escape_string($_POST[$k]);
$valueList .= ',';
}
$valueList = rtrim($valueList, ',');
$valueList .= ')';
$sql = 'INSERT INTO clientes '.$keyList.' '.$valueList;
$result = mysql_query($sql);
This approach drops the occurrences of the keys to one and will probably more naturally with your application.
TuteC had a good aim but failed in details.
It makes me wonder, why noone has a ready made solution, but had to devise it on-the-fly. Nobody faced the same problem before?
And why most people trying to solve only part of the problem, getting variables only.
The goal is not to get variables.
The goal is to get a query. So, get yourself a query.
//quite handy way to define an array, saves you from typing zillion quotes
$fields = explode(" ","postal morada nome apelido name serial iphone3g1 iphone3g2 iphone3g3 iphone3g4 total valor iphone3gs1 iphone3gs2 iphone3gs3 iphone3gs4 iphone41 iphone42 iphone43 iphone44 nif codigounico Notas");
$sql = "INSERT INTO clientes SET ";
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$sql.= "`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
$sql = substr($set, 0, -2);
This code will create you a query without boring repeating the same field name many times.
But that's still not all improvements you can make.
A really neat thing is called a function.
function dbSet($fields) {
$set = '';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
return substr($set, 0, -2);
}
put this function into your code library being included into all your scripts (you have one, don't you?)
and then use it for both insert and update queries:
$_POST['codigounico'] = md5(uniqid(rand()));//a little hack to add custom field(s)
if ($action=="update") {
$id = intval($_POST['id']);
$sql = "UPDATE $table SET ".dbSet($fields)." WHERE id = $id";
}
if ($action=="insert") {
$sql = "INSERT $table SET ".dbSet($fields);
}
So, your code become extremely short and reliable and even reusable.
The only thing you have to change to handle another table is $fields array.
It seems your database is not well planned as it contains seemingly repetitive fields (iphone*). You have to normalize your database.
The same approach to use with prepared statements can be found in this my question: Insert/update helper function using PDO
You could use a rather ugly part of PHP called variable variables, but it is generally considered a poor coding practice. You could include your database escaping at the same time. The code would look something like:
foreach($_POST as $key => $value){
$$key = mysql_real_escape_string($value);
}
The variable variables manual section says they do not work with superglobals like $_PATH, but I think it may work in this case. I am not somewhere where I can test right now.
PHP: extract
Be careful though and make sure you clean the data before using it.
$set = array();
$keys = array('forename', 'surname', 'email');
foreach($keys as $val) {
$safe_value = mysqli_escape_string($db, $_POST[$val]);
array_push($set, "$val='$safe_value'");
}
$set_query = implode(',', $set);
Then make your MySQL query something like UPDATE table SET $set_query WHERE... or INSERT INTO table SET $set_query.
If you need to validate, trim, etc, do it before the above code like this:
$_POST["surname"] = trim($_POST["surname"];
Actually, you could make your life easier by making your code a bit more complicated - escape the input before inserting into the database!
$sql =
"INSERT INTO clientes SET
"postal = '" . mysql_real_escape_string($_POST['postal']) . "', ".
"morada = '" . mysql_real_escape_string($_POST['morada']) . "', ".
...
First, I recommend you to create a key-value array like this:
$newClient = array(
'codigounico' => md5(uniqid(rand())),
'postal' => $_POST['postal'],
'modelo' => $_POST['selectName'],
...
);
In this array key is the column name your MySQL table.
In the code you've provided not every field is copied right from POST array (some are calculated, and some keys of the POST aren't equal with the tables column names), so you should use a flexible method.
You should still specify all columns and values but only once so code is still maintainable and you won't have any security errors if someone sends you a broken POST. As for me it looks more like configuration than coding.
Then I recommend you to write a function similar to this:
function buildInsertQuery($tableName, $keyValue) {
$result = '';
if (!empty($keyValue)) {
$delimiter = ', ';
$columns = '';
$values = '';
foreach ($keyValue as $key => $value) {
$columns .= $key . $delimiter;
$values .= mysql_real_escape_string($value) . $delimiter;
}
$columns = substr($columns, 0, -length($delimiter));
$values = substr($values, 0, -length($delimiter));
$result = 'INSERT INTO `' . $tableName . '` (' . $columns . ') VALUES (' . $values . ')';
}
return $result;
}
And then you can simply build your query with just one function call:
$query = buildInsertQuery('clientes', $newClient);