i am using safemysql class for parametrized queries https://github.com/colshrapnel/safemysql.
Usualy, when preparing a query, it goes like this:
$entries = $db->getAll("SELECT * FROM table WHERE age = ?i AND name = ?s ",$age,$name);
This kind of queries where i know in advance the total number of parametres to be parsed are pretty straight fwd but it seems i am stacked at queries where I do not know how many parametres I will be using - eg. a search form:
What I would like to do, is parametrize the folowing query:
if($_POST['nameparts']){
$parts = explode(' ',$_POST['nameparts']);
foreach((array)$parts as $part){
$q .= " AND ( `name` LIKE '%".$part."%' OR `firstname` LIKE '%".$part."%' ) ";
}
if($_POST['age'])
$q .= " AND `age` = '".$_POST['age']."' ";
$entries = $dbs->getAll("SELECT * FROM table WHERE 1 = 1 ".$q." ");
Any suggestions?
It doesn't look like safemysql supports variable number of placeholders (otherwise you could build array of parameters in parallel with your query). But you can use methods like escapeString(...). It'll give you the same level of safety, but not so elegant. For example:
$q .= " AND `age` = ".$dbs->escapeInt($_POST['age')]." ";
As with any other SQL query with user-supplied data, to handle this in a truly safe way (or rather to push off the work where it belongs), use placeholders.
Yup, let's start with that goal and not lose sight of it - if the query text contains [user-supplied] data then the code is in violation of one of safemysql's (and safe SQL usage) tenants and the following is not necessarily true anymore!
[safemysql is] safe because every dynamic query part [or "bit of user data"] goes into query via placeholder.
The solution is then to build the query text with placeholders and the data array dynamically - but separately. At no time is the DQL (SQL syntax) and the data mixed. It is this separation (and the guarantee of the lower levels) that guarantees that there is no SQL Injection when this approach is followed.
$data = array();
$q = "SELECT * FROM table WHERE 1 = 1 ";
if($_POST['nameparts']){
$parts = explode(' ',$_POST['nameparts']);
foreach((array)$parts as $part){
$q .= " AND (`name` LIKE ?s OR `firstname` LIKE ?s )";
$data[] = '%' . $part . '%'; // add one for each replacement
$data[] = '%' . $part . '%';
}
if($_POST['age']) {
$q .= " AND `age` = ?i ";
$data[] = $_POST['age'];
}
}
And now we have the query text with placeholders and an array of the data to bind. Yippee, we are almost there! Now, create the array that will be passed and invoke the method supplying an array for the parameters.
$params = array($q);
$params = array_merge($params, $data);
$entries = call_user_func_array(array($dbs, 'getAll'), $params);
And, finished!
Related
I'm working on a website that presents leaderboard data from a MySQL database as a table, that which can be filtered and searched through by the user. I was able to construct the table through PHP calls, such as
php echo $row['ranking'];
Similarly, I was able to create a pagination that limits the MySQL query to 50 rows per page.
What I haven't been able to achieve, is the filtering/searching of the data, as well as a pagination that doesn't require the reloading of the page. I attempted to create filtering through PHP variables
$sql = "SELECT * FROM New_2v2_Data $filters";
but couldn't get it to work outside of just editing the PHP code.
$racevar = '%';
$classvar = '%';
$specvar = '%';
$playervar = '%';
$realmvar = '%';
$factionvar = '%';
$r1 = '0';
$r2 = '1800';
$race ="raceId LIKE '$racevar'";
$class = "classId LIKE '$classvar'";
$spec ="specId LIKE '$specvar'";
$player ="player LIKE '$playervar'";
$realm ="realmName LIKE '$realmvar'";
$faction="factionId LIKE '$factionvar'";
$rating ="rating between $r1 and $r2";
$filters = "WHERE $race AND $class AND $spec AND $player AND $realm AND $faction AND $rating";
$sql = "SELECT * FROM New_2v2_Data $filters";
$rs_result = mysql_query ($sql); //run the query
I've found filtering solutions for individual variables, for example names, but I haven't been able to find anything that takes in multiple variables into account. Even then, the filtering only worked on tables that were static.
I was thinking maybe if a dropdown/checkbox were to change a PHP variable depending on what is chosen, and then reloading the PHP for the table to include the additional "WHERE" statement, filtering could work.
Some advice on how I would go about doing this would be great, thank you.
You can conditionally include the various limits and build the SQL just from those which have something set.
$racevar = 'a'; // A value to show when this would be included
$classvar = '%';
$specvar = '%';
$playervar = '%';
$realmvar = '%';
$factionvar = '%';
$r1 = '0';
$r2 = '1800';
$condition= [];
$bindData = [];
if ( $racevar != '%'){
$condition[] ="raceId LIKE ?";
$bindData[] = $racevar;
}
if ( $classvar != '%'){
$condition[] = "classId LIKE ?";
$bindData[] = $classvar;
}
// Repeat above for all of the conditions
if ( $r1 != 0 or $r2 != 0 ) {
$condition[] = "rating between ? and ?";
$bindData[] = $r1;
$bindData[] = $r2;
}
$sql = "SELECT * FROM New_2v2_Data";
if ( count($condition) > 0 ) {
$sql .= " WHERE ".implode(' and ', $condition);
}
echo $sql;
The idea is to build a list of conditions, only when the values have something which is a limit. This can then be added as a where clause.
You then can have various input fields/select fields which allow the user to select the criteria and call this routine with the selections.
I've updated the answer to use bind variables, so that using prepare will give you more security and then you can either bind the values or (using PDO) execute with the array of bind values.
You need to make the filters selectable or dynamic in a way that you can pass them on to your SQL statement.
Your solution for the dropdown could be one of them indeed. You could even do that with a 'search' input text field. Then you make your WHERE statement:
WHERE (`column1` LIKE '%$search%' OR `column2` LIKE '%$search%' OR `column3` LIKE '%$search%',) LIMIT 0,10
I´m trying to put together the most elegant way of searching in two fields for multiple (number of words can vary) terms that needs to only provide results when all words are found (AND instead of OR).
The below gives me an SQL syntax error message:
$search = $_GET["search"];
$searcharray = explode('|', $search);
$query=("SELECT username,sender,message,subject,timestamp,threadid,msgtype
FROM Messages WHERE ('" . implode("'|'",$searcharray) . "') IN CONCAT
(message,subject) ORDER BY timestamp");
I could of course set up a foreach loop for each match on the first word that breaks with an instruction to not add the result if any of the other words are not found in the two fields, but that´s alot more for the PHP script to handle, I think.
Any suggestions?
IN has to be followed by a list of values in parentheses, or a SELECT subquery. You can't use it for pattern matching.
To search a column for a word, you need to use LIKE, with % around the word. And there's no shortcut for searching for multiple words, you have to search for each of them and combine them with AND.
$tests = array_map(function($word) {
return "CONCAT(message, subject) LIKE '%$word%'";
}, $searcharray);
$where = implode(' AND ', $tests);
$query = "SELECT username,sender,message,subject,timestamp,threadid,msgtype
FROM Messages WHERE $where ORDER BY timestamp";
As mentioned in the comments, the code is suseptable to SQL injection. That being said, and since I don't want to re-write all of the code ;-), here's one way to construct the where clause.
$search = $_GET["search"];
$searcharray = explode('|', $search);
$qstr = "SELECT
`username`,
`sender`,
`message`,
`subject`,
`timestamp`,
`threadid`,
`msgtype`
FROM `Messages`
WHERE ";
$w = array();
foreach($searcharray as $key => $val) {
$w[] = "CONCAT(`message`,`subject`) LIKE '%" . $val . "%'";
}
$w_str = implode(" AND ",$w);
$qstr .= $w_str . " ORDER BY `timestamp`";
I'm currently coding a simple search script in PHP that requires three variables from the user namely:
$Capacity, $Location and $RoomType
Capacity is a required field which the jquery validate plugin checks for numerical entry on input - but Location and RoomType are optional.
I'm trying to now draft a SQL query that will search the table rooms.
There are three columns in the table also called Capacity, Location and RoomType that I want to search using the variables.
How would I write this SQL query? Especially with $Capacity being required, $Location / $RoomType expected to be left blank or filled in at the users discretion?
You could use LIKE ...% in your sql query, so that even when blank, it'll be treated as a wildcard.
$q = 'SELECT * FROM foo WHERE capacity = "'.$capacity.'" AND location LIKE "'.$location.'%" AND roomtype LIKE "'.$roomtype.'%"';
Of course, remember to escape the inputs.
Something like this should work:
function BuildSearchQuery($Capacity, $Location, $RoomType)
{
$where = array();
if (!empty($Capacity))
{
$where[] = "Capacity = '" . mysql_real_escape_string($Capacity) . "'";
}
if (!empty($Location))
{
$where[] = "Location = '" . mysql_real_escape_string($Location) . "'";
}
if (!empty($RoomType))
{
$where[] = "RoomType = '" . mysql_real_escape_string($RoomType) . "'";
}
if (empty($where))
{
return false;
}
$sql = "select * from `table` where ";
$sql += implode(" AND ", $where);
return $sql;
}
Although nowadays many frameworks exists that allow you to do this more easily and less error-prone than manually crafting queries.
$query =select * from table where Capacity =$Capacity
if(isset($Location) && $Location!='') $query.= and Location LIKE '%$location%'
if(isset($RoomType) && $RoomType!='') $query.= and RoomType LIKE '%$RoomType%'
Making use of LIKE or = operator in query is upto you.
Depend on how complex it is (and or not ???)
But basically
Select ... From Rooms Where Capacity = #Capacity
and ((Location = #Location) Or (IsNull(#Location,'') = ''))
and ((RoomType = #RoomType) or (IsNull(#RoomType,'') = ''))
Or some such.
If you aren't using parameterised queryies then replace #.... with the escaped inputs.
I'm very new to php/SQL (1 day) so forgive me if I am doing this in a backwards way.
This php below is intended to return the 100 most recent entries into the DB. I attempt to do it by selecting 100 entries to be returned and sort by the date(time stamp) the entry was made. Will this return the 100 more recent entries to the DB? Or am I understanding this wrong?
$type = isset($_GET['type']) ? $_GET['type'] : "global";
$offset = isset($_GET['offset']) ? $_GET['offset'] : "0";
$count = isset($_GET['count']) ? $_GET['count'] : "100";
$sort = isset($_GET['sort']) ? $_GET['sort'] : "date DESC";
// Localize the GET variables
$udid = isset($_GET['udid']) ? $_GET['udid'] : "";
$name = isset($_GET['name']) ? $_GET['name'] : "";
// Protect against sql injections
$type = mysql_real_escape_string($type);
$offset = mysql_real_escape_string($offset);
$count = mysql_real_escape_string($count);
$sort = mysql_real_escape_string($sort);
$udid = mysql_real_escape_string($udid);
$name = mysql_real_escape_string($name);
// Build the sql query
$sql = "SELECT * FROM $table WHERE ";
switch($type) {
case "global":
$sql .= "1 ";
break;
case "device":
$sql .= "udid = '$udid' ";
break;
case "name":
$sql .= "name = '$name' ";
break;
}
$sql .= "ORDER BY $sort ";
$sql .= "LIMIT $offset,$count ";
$result = mysql_query($sql,$conn);
if(!$result) {
die("Error retrieving scores " . mysql_error());
}
//echo $result;
$rows = array();
while($row = mysql_fetch_assoc($result)) {
$rows[] = $row;
}
This should work, although date is a MySQL keyword, so you would either need to enclose date in backquotes or rename that column.
Also, definitely make sure you've sanitized those inputs before building your query. Building a query off of user-editable values from $_GET or $_POST with no sanitation is very unsafe.
For WHERE parameters, you should be running mysql_real_escape_string() on those (which I see you are, I'm not sure if you were before or not). That's enough because you're wrapping those values in quotes in your query, and since you're escaping that string, any attempt to break out of those quotes won't work.
For the stuff like the ORDER BY you have, I would define a valid "list" of allowed values and check to make sure your parameter is in that list. For example:
$valid_orderbys = array('`date` DESC', '`date` ASC', '`name` DESC', '`name` ASC');
if (in_array($_GET['sort'], $valid_orderbys))
{
// you're good, you can use this value
}
else
{
// unexpected value, either alert the user or
// use a default value you define
}
Or for LIMIT, you could use PHP's built-in is_numeric() to verify that the value you're being given is a number, not a crafted string.
It's not enough to simply escape the $table, ORDER BY and LIMIT parameters because they're not wrapped in quotes and therefore someone can just maliciously inject a value of ; DROP TABLE whatever; --. This ends up making your query something like:
SELECT * FROM ; DROP TABLE whatever; --WHERE ...
Queries are separated by semicolons, so there are three queries here. The first fails because it's invalid, the second succeeds in dropping the table, and the third is just a comment so nothing happens. But you can see, if you let users throw whatever they want as one of those parameters, it's a wide open security hole. (I'm not sure if enclosing the table name in backquotes helps this, someone let me know if you know. But in any case, you can do the same attack on the LIMIT and ORDER BY parameters.)
If you have a unique, auto-increment ID for each record (as you should), it would be more efficient to ORDER BY id DESC.
Actually, you already got it right. You should only look into specifying the columns instead of using *.
I want to make an category-system CMS. Everything is fine, except a big trouble.
How can I can handle and generate the mysql query depends by some inputs like:
site.com/some-category&sortby=views&from=smt&anotherInput=key
For example, for this input my query should be something like
SELECT * FROM `articles` WHERE from='smt' AND afield='key' ORDER BY VIEWS
But these inputs will be different. How I can write this code? I don't know much things about designs patterns, but, I've heard about Factory pattern, is this a part of my solution?
Than
Factory pattern can help you with e.g. connecting/quering various databases without need to rewrite the entire code. This has nothing to do about query itself.
You can look at PDO extension, I usually use it together with prepared statements.
It will let you write queries like this:
$prepare = $db->prepare('
SELECT
*
FROM
articles
WHERE
from=:from AND afield=:afield
ORDER BY
views
');
$prepare->bindValue(':from', $_GET['from'], PDO::PARAM_STR);
$prepare->bindValue(':afield', $_GET['afield'], PDO::PARAM_STR);
$prepare->execute();
return $prepare;
The good thing about it is that you don't need to protect this from sql injections as PDO makes it for you. Also, the query is cached and you can run it several times with different params.
Very bad practice to use raw GET params in query directly, i.e. you shouldn't make constructions like
SELECT * FROM articles WHERE from=$_GET['from'] AND afield='key' ORDER BY VIEWS
but instead something like
if ($_GET['from'] == 'smt') $from = 'smt'
SELECT * FROM articles WHERE from='$from' AND afield='key' ORDER BY VIEWS
and so on
P.S. keyword is 'sql injection'
You can build the query string as pieces depending on what you need:
$query = "SELECT * FROM `articles` WHERE 1 = 1";
$where = ''
if (isset($_GET['from'])) {
$where .= " AND `from` = '" . mysql_real_escape_string($_GET['from']) . "'"
}
if (isset($_GET['anotherInput'])) {
$where .= " AND `from` = '" . mysql_real_escape_string($_GET['anotherInput']) . "'"
}
if (isset($_GET['sortby'] == 'views') {
$orderby = " ORDER BY `views` DESC"
} else {
$orderby = " ORDER BY `id` DESC"
}
$query = $query . $where . $orderby;
$result = mysql_query($query);
This is sort of the straight PHP/MySQL way, but I actually do suggest that you use prepared statements as in Pavel Dubinin's answer.
This has nothing to do with patterns. Use the $_GET superglobal variable to dynamically generate your query string.
$query = "SELECT * FROM articles WHERE from='".
$_GET['from'].
"' AND afield='".
$_GET['anotherInput'].
"' ORDER BY ".
$_GET['sortby'];
Disclaimer: This is prone to SQL injection. Use input escaping and prepared statements, ex PDO, in a production environment like:
$query = "SELECT * FROM articles WHERE from='?' AND afield='?' ORDER BY ?";
$clean_from = htmlentities($_POST['from'], ENT_QUOTES, 'UTF-8');
$clean_anotherInput = htmlentities($_POST['anotherInput'], ENT_QUOTES, 'UTF-8');
$clean_sortby = htmlentities($_POST['sortby'], ENT_QUOTES, 'UTF-8');
$clean_inputs = array($clean_from, $clean_anotherInput, $clean_sortby);
$sth = $dbh->prepare($query);
$sth->execute($clean_inputs);