So I have some data coming in via POST from a form with a large number of checkboxes and I'm trying to find records in the database that match the options checked. There are four sets of checkboxes, each being sent as an array. Each set of checkboxes represents a single column in the database and the values from the checked boxes are stored as a comma-delimited string. The values I'm searching for will not necessarily be consecutive so rather than a single LIKE %value% I think I have to break it up into a series of LIKE statements joined with AND. Here's what I've got:
$query = "";
$i = 1;
$vals = [];
foreach($_POST["category"] as $val){
$query .= "category LIKE :cat".$i." AND ";
$vals[":cat".$i] = "%".$val."%";
$i++;
}
$i = 1;
foreach($_POST["player"] as $val){
$query .= "player LIKE :plyr".$i." AND ";
$vals[":plyr".$i] = "%".$val."%";
$i++;
}
$i = 1;
foreach($_POST["instrument"] as $val){
$query .= "instrument LIKE :inst".$i." AND ";
$vals[":inst".$i] = "%".$val."%";
$i++;
}
$i = 1;
foreach($_POST["material"] as $val){
$query .= "material LIKE :mat".$i." AND ";
$vals[":mat".$i] = "%".$val."%";
$i++;
}
$query = rtrim($query, " AND ");
$tubas = R::convertToBeans("tuba", R::getAll("SELECT * FROM tuba WHERE ".$query, $vals));
This does seem to work in my preliminary testing but is it the best way to do it? Will it be safe from SQL injection?
Thanks!
As long as you use parameterized queries (like you do), you should be safe from SQL injection. There are edge cases though, using UTF-7 PDO is vulnerable (I think redbean is based on PDO)
I would change the code to something like this, minimizes the foreach clutter.
$query = 'SELECT * FROM tuba';
$where = [];
$params = [];
$checkboxes = [
'category',
'player',
'instrument',
'material'
];
foreach ($checkboxes as $checkbox) {
if (!isset($_POST[$checkbox])) {
// no checkboxes of this type submitted, move on
continue;
}
foreach ($_POST[$checkbox] as $val) {
$where[] = $checkbox . ' LIKE ?';
$params[] = '%' . $val . '%';
}
}
$query .= ' WHERE ' . implode($where, ' AND ');
$tubas = R::convertToBeans('tuba', R::getAll($query, $params));
Related
Im a new and just learning php. I have a data table with search boxes with this code.
$condition = '';
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$condition .= ' AND username LIKE "%'.$_REQUEST['username'].'%" ';
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$condition .= ' AND useremail LIKE "%'.$_REQUEST['useremail'].'%" ';
}
What I need is to search with both username AND useremail. I have attempted everything I know and spent a few hours searching for a solution but with no success.
You could write a complicated set of IF's with equals and not equals tests all over the place, but as the list of test gets bigger the IF's get almost impossible to maintain or understand. So it might be simpler to just build and array of things to AND in the query
$condition = '';
$and = []; #init the array
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$and[] = ['name' => 'username', 'value' => $_REQUEST['username'] ];
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$and[] = [''name' => 'useremail', 'value' => $_REQUEST['useremail'] ];
}
#now build the condition string
foreach ($and as $i => $andMe) {
if ( $i != 0 ){
// AND required here
$condition .= ' AND ';
}
$condition .= $andMe['name'] . ' = ' . $andMe['value'];
}
Also I have replaced the LIKE with an = as it seems more appropriate, I assume you dont ask people to enter something a bit like there user name and email, but in fact ask for the actual username or email
Of course that would still be susceptible to SQL Injection Attack So a better solution would be
#now build the condition string
foreach ($and as $i => $andMe) {
if ( $i != 0 ){
// AND required here
$condition .= ' AND ';
}
$condition .= $andMe['name'] . ' = ?';
}
And then prepare the query and use the value part to bind to the parameters.
Issue is you have leading AND in your query.
push condition to array then join conditions.
like that
$condition_array = [];
if(isset($_REQUEST['username']) and $_REQUEST['username']!="") {
$condition_array[] = 'username LIKE "%'.$_REQUEST['username'].'%" ';
}
if(isset($_REQUEST['useremail']) and $_REQUEST['useremail']!=""){
$condition_array[] = 'useremail LIKE "%'.$_REQUEST['useremail'].'%" ';
}
$condition = implode(" AND ",$condition_array);
You can create an array of all the keys that are to be searched. Then, create a new array and collect all conditions. Implode them in the end with AND as the glue. This way, query is made correctly without needing to add 100 different if conditions.
Use PDO objects to avoid SQL injection attacks. In the below snippet, if you ever need to add 1 more column for search, just add it in the below $keys array and rest works as usual without needing any further refactoring.
Snippet:
<?php
$keys = ['username', 'useremail'];
$conditions = [];
$placeholders = [];
foreach($keys as $key){
if(!empty($_REQUEST[ $key ])){
$conditions = " $key LIKE ?";
$placeholders[] = '%' . $_REQUEST[ $key ] . '%';
}
}
// you need to create $mysqli object here
if(count($conditions) === 0){
$stmt = $mysqli->prepare('select * from table');
$stmt->execute();
// rest of your code
}else{
$stmt = $mysqli->prepare('select * from table where '. implode(" AND ", $conditions));
$stmt->bind_param(str_repeat('s', count($placeholders)), ...$placeholders);
$stmt->execute();
// rest of your code
}
I have a problem, I wanted to make a query to the search form data where people insert some filters will search for products appear less in this specific case are houses.
had made a query with "or" does not work as well as this it makes the fields alone, and "and" not also works because I do not want users to fill out all the fields, so is the they want, so I did it
$texto = array();
$contador=0;
if($_SESSION["finalidade"]!=""){
$contador++;
$texto[1]="`finalidade` LIKE ".$finalidade;}
if($_SESSION["tipoimovel"]!=""){
$contador++;
$texto[2]="`tipoimovel` LIKE ".$tipoimovel;}
if($_SESSION["nquarto"]!=0){
$contador++;
$texto[3]="`nquartos` = ".$nquarto;}
if($_SESSION["pmin"]!=0){
$contador++;
$texto[4]="`preco` > ".$pmin;}
if($_SESSION["pmax"]!=0){
$contador++;
$texto[5]="`preco` < ".$pmax;}
if($_SESSION["elevador"]!=""){
$contador++;
$texto[6]="`elevador` LIKE ".$elevador;}
if($_SESSION["garagem"]!=""){
$contador++;
$texto[7]="`garagem` LIKE ".$garagem;}
if($_SESSION["vistapriv"]!=""){
$contador++;
$texto[8]="`vistapreveligiada` LIKE ".$vistapriv;}
if($_SESSION["piscina"]!=""){
$contador++;
$texto[9]="`piscina` LIKE ".$piscina;}
if($_SESSION["jardim"]!=""){
$contador++;
$texto[10]="`jardim` LIKE ".$jardim;}
if($_SESSION["condominiof"]!=""){
$contador++;
$texto[11]="`condominio` LIKE ".$condominiof;}
if($_SESSION["conselho"]!=""){
$contador++;
$texto[12]="`conselho` LIKE ".$conselho;}
if($_SESSION["frequesia"]!=""){
$contador++;
$texto[13]="`frequesia` LIKE ".$frequesia;}
if($_SESSION["refimovel"]!=""){
$contador++;
$texto[14]="`referencia` LIKE ".$refimovel;}
$textocompleto="";
$contador2=1;
for($y = 0; $y < 16; $y++) {
if($texto[$y]!=""){
if($contador2==1 && $contador2!=$contador){
$contador2++;
;
$textocompleto=$texto[$y]." AND "; }
elseif($contador2==$contador){
$contador2++;
$textocompleto=$textocompleto.$texto[$y];}
elseif($contador2==1 && $contador2==$contador){
$contador2++;
$textocompleto=$texto[$y]; }
else {
$contador2++;
$textocompleto=$textocompleto.$texto[$y]." AND ";}
}
}
$results = $mysqli->prepare("SELECT ID, imagem, frequesia ,conselho, preco FROM `imovel` WHERE ? ORDER BY `imovel`.`preco` DESC LIMIT ?,? ");
$results->bind_param("sii",$textocompleto, $page_position, $item_per_page);
$results->execute();
$results->bind_result($id ,$imagem, $frequesia ,$conselho, $preco); //bind variables to prepared statement
}
the problem is that it is not me to return anything, does not show me any value.
already do not know what else to do because if you make a if for each of the possibilities will give more than 100 if and will take a long time.
if anyone knows of some effective and quick way to do this please help me, because in fact not know what else to do to fix this problem, thank you
You cannot bind parts of an SQL statement, you can only bind values.
So this is valid:
$results = $mysqli->prepare("SELECT col1, col2 FROM tbl WHERE col3 = ?");
$results->bind_param("i", 1234);
and this is not:
$results = $mysqli->prepare("SELECT col1, col2 FROM tbl WHERE ?");
$results->bind_param("s", "col3 = 1234");
You can concatenate the strings, though:
prepare("SELECT ... WHERE ".$textocompleto." ORDER BY imovel.preco DESC LIMIT ?,? ");
You can use a prefix on your $\_SESSION variable like sf_{variable_name} for all required fields. After that, you can loop throughout them to build your query
(ps : code not tested):
$sql = "SELECT ";
$sql_cond = " WHERE ";
$texto = [];
foreach($_SESSION as $k => $v){
if(substr($v, 0, 3) == "sf_"){
$name = substr($k, 3, strlen($k))
$texto[$name] = $v;
$sql .= $name.", ";
$sql_cond .= $name." LIKE :".$name;
}
}
$final_sql = $sql.$sql_cond;
$results = $mysqli->prepare($final_sql);
foreach($texto as $k => $v){
$results->bind_param(":".$k, "%".$v."%");
}
$results->execute();
Ok, after some tests, i did the same with PDO object in a test db for medias (PDO is nearly the same stuff than MYSQLI object), and it work fine for me, just adapt your db ids for your case
$pdo = new PDO('mysql:host=localhost;dbname=media_center', 'root', 'root');
$_SESSION["sf_title"] = "Interstellar";
$_SESSION["sf_description"] = "sci-fi";
$sql = "SELECT * ";
$sql_cond = " WHERE ";
$texto = [];
foreach($_SESSION as $k => $v){
if(substr($k, 0, 3) == "sf_"){
$name = substr($k, 3, strlen($k));
$texto[$name] = $v;
$sql_cond .= $name." LIKE :".$name." OR ";
}
}
$sql_cond = substr($sql_cond, 0, strlen($sql_cond)-4);
$final_sql = $sql." FROM media ".$sql_cond;
$results = $pdo->prepare($final_sql);
foreach($texto as $k => $v){
$key = ":".$k;
$value = "%".$v."%";
$results->bindParam($key, $value);
}
$results->execute();
I guess you will need to wrap the values in single qoute ''
For example:
$texto[1]="`finalidade` LIKE ".$finalidade;
should be
$texto[1]="`finalidade` LIKE '".$finalidade."'";
Try this.. I think it will solve it
I have an intresting question:
I have a form that is send over GET, and i want to secure the database.
Problem is that there are 8 select boxes with arguments that can be passed in the form to the page, but they dont have to be there.
I am currently checking if the select box is sending default value, if its not, im storing it in array. After that i loop trough array and adding the search parameters to string :
$searchString = "WHERE Aktivno = 1";
foreach($pretragaArray as $key => $item){
$searchString = $searchString." AND";
$searchString = $searchString." ". $key ." = " . $item;
}
In the end i end up with search string query like this
WHERE Aktivno = 1 AND IDVrstaOglasa = 1
or
WHERE Aktivno = 1 AND IDOpstina = 15 AND IDGrad = 11 AND IDVrstaOglasa = 1 AND Broj_soba = 3 AND IDKategorijaNekretnine = 5
I am using PDO php class for querying the database.
My question is, is there a way to escape my string that is generated this way, and if not, is there a better way to query the database with dynamic number of atributes in WHERE clause.
You can generate a parameterised query with a dynamic number of parameters like this:
$searchString = "WHERE Aktivno = 1";
$params = array();
$paramNum = 1;
foreach($pretragaArray as $key => $item)
{
$paramName = ':param' . $paramNum++;
$searchString = $searchString." AND";
$searchString = $searchString." ". $key ." = " . $paramName;
$params[$paramName] = $item;
}
$db = new PDO("...");
$statement = $db->prepare("SELECT * FROM some_table " . $searchString);
$statement->execute($params);
$row = $statement->fetch(); // or fetchAll()...
I have an array like the following:
tod_house
tod_bung
tod_flat
tod_barnc
tod_farm
tod_small
tod_build
tod_devland
tod_farmland
If any of these have a value, I want to add it to an SQL query, if it doesnt, I ignore it.
Further, if one has a value it needs to be added as an AND and any subsequent ones need to be an OR (but there is no way of telling which is going to be the first to have a value!)
Ive used the following snippet to check on the first value and append the query as needed, but I dont want to copy-and-paste this 9 times; one for each of the items in the array.
$i = 0;
if (isset($_GET['tod_house'])){
if ($i == 0){
$i=1;
$query .= " AND ";
} else {
$query .= " OR ";
}
$query .= "tod_house = 1";
}
Is there a way to loop through the array changing the names so I only have to use this code once (please note that $_GET['tod_house'] on the first line and tod_house on the last line are not the same thing! - the first is the name of the checkbox that passes the value, and the second one is just a string to add to the query)
Solution
The answer is based heavily upon the accepted answer, but I will show exactly what worked in case anyone else stumbles across this question....
I didnt want the answer to be as suggested:
tod_bung = 1 AND (tod_barnc = 1 OR tod_small = 1)
rather I wanted it like:
AND (tod_bung = 1 OR tod_barnc = 1 OR tod_small = 1)
so it could be appended to an existing query. Therefore his answer has been altered to the following:
$qOR = array();
foreach ($list as $var) {
if (isset($_GET[$var])) {
$qOR[] = "$var = 1";
}
}
$qOR = implode(' OR ', $qOR);
$query .= " AND (" .$qOR . ")";
IE there is no need for two different arrays - just loop through as he suggests, if the value is set add it to the new qOR array, then implode with OR statements, surround with parenthesis, and append to the original query.
The only slight issue with this is that if only one item is set, the query looks like:
AND (tod_bung = 1)
There are parenthesis but no OR statements inside. Strictly speaking they arent needed, but im sure it wont alter the workings of it so no worries!!
$list = array('tod_house', 'tod_bung', 'tod_flat', 'tod_barnc', 'tod_farm', 'tod_small', 'tod_build', 'tod_devland', 'tod_farmland');
$qOR = array();
$qAND = array();
foreach ($list as $var) {
if (isset($_GET[$var])) {
if (!empty($qAND)) {
$qOR[] = "$var = 1";
} else {
$qAND[] = "$var = 1";
}
$values[] = $_GET[$var];
}
}
$qOR = implode(' OR ', $qOR);
if ($qOR != '') {
$qOR = '(' . $qOR . ')';
}
$qAND[] = $qOR;
$qAND = implode(' AND ', $qAND);
echo $qAND;
This will output something like tod_bung = 1 AND (tod_barnc = 1 OR tod_small = 1)
As the parameter passed to $_GET is a string, you should build an array of strings containing all the keys above, iterating it and passing the values like if (isset($_GET[$key])) { ...
You could then even take the key for appending to the SQL string.
Their are a lot of ways out their
$list = array('tod_house', 'tod_bung', 'tod_flat', 'tod_barnc', 'tod_farm', 'tod_small', 'tod_build', 'tod_devland', 'tod_farmland');
if($_GET){
$query = "";
foreach ($_GET as $key=>$value){
$query .= (! $query) ? " AND ":" OR ";
if(in_array($key,$list) && $value){
$query .= $key." = '".$value."'";
}
}
}
Sure you have to take care about XSS and SQL injection
If the array elements are tested on the same column you should use IN (...) rather than :
AND ( ... OR ... OR ... )
If the values are 1 or 0 this should do it :
// If you need to get the values.
$values = $_GET;
$tod = array();
foreach($values as $key => $value) {
// if you only want the ones with a key like 'tod_'
// otherwise remove if statement
if(strpos($key, 'tod_') !== FALSE) {
$tod[$key] = $value;
}
}
// If you already have the values.
$tod = array(
'tod_house' => 1,
'tod_bung' => 0,
'tod_flat' => 1,
'tod_barnc' => 0
);
// remove all array elements with a value of 0.
if(($key = array_search(0, $tod)) !== FALSE) {
unset($tod[$key]);
}
// discard values (only keep keys).
$tod = array_keys($tod);
// build query which returns : AND column IN ('tod_house','tod_flat')
$query = "AND column IN ('" . implode("','", $tod) . "')";
When I run the following MySQL query via PHP and all of the elements of $_GET() are empty strings, all the records in the volunteers table are returned (for obvious reasons).
$first = $_GET['FirstName'];
$last = $_GET['LastName'];
$middle = $_GET['MI'];
$query = "SELECT * FROM volunteers WHERE 0=0";
if ($first){
$query .= " AND first like '$first%'";
}
if ($middle){
$query .= " AND mi like '$middle%'";
}
if ($last){
$query .= " AND last like '$last%'";
}
$result = mysql_query($query);
What is the most elegant way of allowing empty parameters to be sent to this script with the result being that an empty $result is returned?
my solution:
$input = Array(
'FirstName' => 'first',
'LastName' => 'last',
'MI' => 'mi'
);
$where = Array();
foreach($input as $key => $column) {
$value = trim(mysql_escape_string($_GET[$key]));
if($value) $where[] = "`$column` like '$value%'";
}
if(count($where)) {
$query = "SELECT * FROM volunteers WHERE ".join(" AND ", $where);
$result = mysql_query($query);
}
There's no point in running a (potentially) expensive query if there's nothing for that query to do. So instead of trying to come up with an alternate query to prevent no-terms being searched, just don't run the search at all if there's no terms:
$where = '';
... add clauses ...
if ($where !== '') {
$sql = "SELECT ... WHERE $where";
... do query ...
} else {
die("You didn't enter any search terms");
}
With your current code, if everything is empty, you will get the WHERE 0=0 SQL which is TRUE for all rows in the table.
All you have to do is remove the if statements...