i'm working with php (ubuntu software).
I have some data into a PMA database, that i extract like this :
<?php
try
{
$db = mysql_connect('localhost', 'dbName', 'dbPwd');
mysql_select_db('dbName', $db);
}
catch(Exception $e)
{
die('Erreur : '. $e->getMessage());
}
$sql = "SELECT p.ean13, pl.id_product, pl.name FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '1 agapanthe %'";
$result_agpt = mysql_query($sql, $db);
$data = mysql_fetch_row($result_agpt);
Now that I have my data into an array like this :
ean13 - id_product - name
NULL - 5389 - 1 agapanthe ivoire //////////////////////////////////////////////////////////////////////////////////////////////////////////////
4027234262147 - 5387 - 1 agapanthe lilas ////////////////////////////////////////////////////////////////////////////////////////////////
NULL - 5388 - 1 agapanthe parme ////////////////////////////////////////////////////////////////////////////////////////////////////////////
NULL - 5386 - 1 agapanthe violet //////////////////////////////////////////////////////////////////////////////////////////////////////////////
I want to display, for each row of my result, the following informations :
ean13 : NULL ////
id_product : 5389 ////
product_name : 1 agapanthe ivoire
And for each of this row, i must truncate the name like this :
"1 agapanthe ivoire" => "1 agapanthe"
I'm doing this using the preg_match function, as below :
foreach($data as $value)
{
// preg_match to truncate product names : '1 agapanthe verte' => '1 agapanthe'
$pattern = '/^(1\sagapanthe)\s(\w*)/is';
$match_truncate = preg_match($pattern, $data[2], $matches);
$name = $matches[1];
echo "Name : ".$name; echo "<br />";echo "<br />";
$attribute = $matches[2];
echo "Attribut : ".$attribute; echo "<br />";
I tried several ways of doing this foreach, but i can't manage to make it work.
EDIT : My aim here is to truncate every name of my product, as to modify its content into PMA. Should I consider using PDO over mysql connection ?
Any help or comment would be greatly appreciated :) !
Regards,
Stelio Kontos.
You appear to have several issues here. First, and most important is stop using the mysql database extension. Instead use mysqli or pdo. The older mysql extension is deprecated in all supported version of PHP, and has been dropped from PHP 7.0.
Second the manner in which you built your SQL string is susceptible to SQL Injection. It is always a bad idea to concatenate data that could come from a user into an sql statement string. Even if it passes through the database first. Best way is to use parameterized sql strings which is available in mysqli and pdo (but not mysql).
Here is a link to the docs http://php.net/manual/en/function.mysql-fetch-row.php
But to answer your question, using the mysql extension, the fetch needs to be inside the loop. It returns a single row at a time as an array with numerical indexes. So after:
$data = mysql_fetch_row($result_agpt);
$data will contain
[0] => 'NULL',
[1] => 5389,
[2] => '1 agapanthe ivoire'
The foreach is incorrect in this instance. You will need to do something more like
$data = mysql_fetch_row($result_agpt);
while($data !== false) {
$ean13 = $data[0];
$id = $data[1];
$name = $data[2];
// do something with it
$data = mysql_fetch_row($result_agpt);
}
Finally, regular expressions are hard to get just right. I use an online regex tester like the one at https://regex101.com/ to make sure my patterns work. Here is one I think might work for you /([0-9]+ \w+) (\w+)/is.
Not sure if I understand the question properly, but if you want to display only portion of the value from the database column pl.name, you could rework the SQL query:
$sql = "SELECT p.ean13, pl.id_product, '1 agapanthe' FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '1 agapanthe %'"
The above query will display always 1 agapanthe as product name, no need for complex regular expressions here ;)
EDIT:
You can provide the portion for the LIKE clause dynamically and then use it to select it as column:
$like = "something";
$sql = "SELECT p.ean13, pl.id_product, '".$like."' FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '".$like."%'";
Also - do not forget to escape the contents, if they are going to be user-defined or use prepared statements and PDO to access/query your database. This way you will be protected from SQL injection attacks.
Related
Unless I am missing something very obvious, I would expect the values of $data1 and $data2 to be the same?? But for some reason when I run this scenario twice (its run once each function call so I'm calling the function twice) it produces different results.
Call 1: PDO = Blank, Sprintf = 3 rows returned
Call 2: PDO = 1 row, Sprintf = 4 rows (which includes the PDO row)
Can someone tell me what I'm missing or why on earth these might return different results?
$sql = "SELECT smacc.account as Smid,sappr.*,CONCAT('$domain/',filepath,new_filename) as Image
FROM `{$dp}table`.`territories` pt
JOIN `{$dp}table`.`approvals` sappr ON pt.approvalID = sappr.ID
JOIN `{$dp}table`.`sm_accounts` smacc ON pt.ID = smacc.posted_territory_id
LEFT JOIN `{$dp}table`.`uploaded_images` upimg ON pt.imageID = upimg.ID
WHERE postID = %s AND countryID = %s AND smacc.account IN (%s) AND languageID = %s";
echo sprintf($sql,$postID,$countryID,implode(',',$accs),$langID);
$qry1 = $db->prepare(str_replace('%s','?',$sql));
$qry1->execute(array($postID,$countryID,implode(',',$accs),$langID));
$data1 = $qry1->fetchAll();
print'<pre><h1>PDO</h1>';print_r($data1);print'</pre>';
$qry2 = $db->query(sprintf($sql,$postID,$countryID,implode(',',$accs),$langID));
$data2 = $qry2->fetchAll();
print'<pre><h1>Sprintf</h1>';print_r($data2);print'</pre><hr />';
The root of the problem is the implode(',',$accs) function.
While you are using sprintf() it will generate a coma separated list and that list will be injected into the query string.
The result will be something like this:
smacc.account IN (1,2,3,4,5)
When you are binding the same list with PDO, it handles it as one value (a string: '1,2,3,4,5'). The "result" will be something like this:
smacc.account IN ('1,2,3,4,5')
Note the apostrophes! -> The queries are not identical.
In short, when you are using PDO and binding parameters, you have to bind each value individually (you can not pass lists as a string).
You can generate the query based on the input array like this:
$query = ... 'IN (?' . str_repeat(', ?', count($accs)-1) . ')' ...
// or
$query = ... 'IN (' . substr(str_repeat('?,', count($accs)), 0, -1) . ')'
This will add a bindable parameter position for each input value in the array. Now you can bind the parameters individually.
$params = array_merge(array($postID, $countryID), $accs, array($langID));
$qry1->execute($params);
Yes as Kris has mentioned the issue with this is the IN part of the query. Example 5 on the following link helps fix this: http://php.net/manual/en/pdostatement.execute.php. I tried using bindParam() but that didn't seem to work so will use Example 5 instead.
i'm using K2 in Joomla 3.3.
I'm trying to set params (items ids ) to module k2_content from item.php file.
The result must to be between brackets, something like:
["96","68"]
My code is:
$query = "SELECT * FROM #__k2_items WHERE extra_fields_search = '$myautor' AND catid !=1 " ;
$db->setQuery($query);
$losautores = $db->loadObjectList();
$result = array();
foreach ($losautores as $key => $value) {
$result[] = '" '.$value->id.' "';
}
$string_version = implode(',', $result);
$autoresfinal = '['.$string_version.']';
If i test using print, looks ok.
But passing the var to pramas, i get 1064 error.
$params->set('items', $autoresfinal);
To test I tried
$autoresfinal = ["96","68"];
And works fine.
Any idea why doesn't work?
Thank you.
If you assign ["x","y"] you are assigning an array. Here you are transforming the array in a string.
Try simply
$result = [ ];
foreach ($db->loadObjectList() as $key => $value) {
$result[] = $value->id;
}
$params->set('items', $result);
Also, if you wanted to convert the array into a string (possibly JSON), a faster and safer way is to use json_encode (with the appropriate options).
UPDATE
The above remains true, but I had missed your complaint about error 1064. That is a SQL syntax error and it happens before you encode the results.
The reason - as noticed by Fred -ii- - is that in this query, #__k2_items needs escaping with backticks:
$query = "SELECT * FROM #__k2_items WHERE
extra_fields_search = '$myautor' AND catid !=1 " ;
should be:
$query = "SELECT * FROM `#__k2_items` WHERE
extra_fields_search = '$myautor' AND catid !=1 " ;
Also, you probably want to use prepared statements and parameterized queries (find an example here) instead of just plugging $myautor into a string. If you had an author called D'Artagnan, the query would become
....search = 'D'Artagnan' AND ...
which would again fail. Or if I called an author ' OR ''=', the query would become
...search = '' OR ''='' AND ...
which, since '' is always equal to '', would match for all the records in your table.
I need help with building a query, I have a form ( multiple dropdowns) and need to display a data table according to the user selection.
filters.php (on server side - ajax )
$criteria[0] = $_POST['Size'];
$criteria[1] = $_POST['Name'];
$criteria[2] = $_POST['Color'];
$criteria[3] = $_POST['Sku'];
$criteria[4] = $_POST['Features'];
$statement = $dbh->prepare("select * from table_name where Sku = :sku AND Size = :size AND Color = :color AND Features LIKE :features ");
$statement->execute(array(':name' => $criteria[1], ':Size'=> $criteria[0], ':color'=>$criteria[2], ':sku'=>$criteria[3], ':features'=> '%'.$features[4].'%'));
$statement->setFetchMode(PDO::FETCH_ASSOC);
$subrows = $statement->fetchAll();
// ect..
When all the criteria are selected ( all dropdowns have a value ) the call will return data , but what if the user only selects one, two or three dropdowns? It will return an empty table which is logic if using the query above.
I was thinking maybe I should try first to check if the variables are set, then perform for each scenario a query ? Im kind of lost how to implement it.
The idea is to have a small div with dropdowns next to the data table which allows filtering.
thanks
If statements dependent on the $_POST key may be the best way - and add the corresponding part to the query string:
i.e. :
$queryString = "select * from table_name where ";
if($_POST['SKU']){
$queryString .= "Sku = :sku";
}
Furthermore, to handle the "AND" parts :
$and = (count($_POST) > 1 ? 1 : 0);
and inside each if statement, the first line should be:
$queryString .= ($and ? "AND " : "");
Furthermore, I would suggest using an ORM, (Idiorm being my personal favorite, which I use in this example: )
$queryBuilder = ORM::for_table("table");
if($_POST['Sku'])
$queryBuilder = $queryBuilder->where("Sku", $_POST['Sku']);
and so on and so forth.
Check the desired variables to see if they contain data, if so you have to build the query. You can add to the array at the same time! For example:
$myarray = array();
if(!empty($criteria[0])){
$myarray["Size"] = $criteria[0];
}
I have a sql query that is generated using php. It returns the surrogate key of any record that has fields matching the search term as well as any record that has related records in other tables matching the search term.
I join the tables into one then use a separate function to retrieve a list of the columns contained in the tables (I want to allow additions to tables without re-writing php code to lower ongoing maintenance).
Then use this code
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$qry_string.="UPPER(";
$qry_string.=$cur_col;
$qry_string.=") like '%";
$qry_string.=strtoupper($term_searching);
$qry_string.="%' or ";
}
}
To generate the rest of the query string
select tbl_sub_model.sub_model_sk from tbl_sub_model inner join [about 10 other tables]
where [much code removed] or UPPER(tbl_model.image_id) like '%HONDA%' or
UPPER(tbl_model.image_id) like '%ACCORD%' or UPPER(tbl_badge.sub_model_sk) like '%HONDA%'
or UPPER(tbl_badge.sub_model_sk) like '%ACCORD%' or UPPER(tbl_badge.badge) like '%HONDA%'
or UPPER(tbl_badge.badge) like '%ACCORD%' group by tbl_sub_model.sub_model_sk
It does what I want it to do however it is vulnerable to sql injection. I have been replacing my mysql_* code with pdo to prevent that but how I'm going to secure this one is beyond me.
So my question is, how do I search all these tables in a secure fashion?
Here is a solution that asks the database to uppercase the search terms and also to adorn them with '%' wildcards:
$parameters = array();
$conditions = array();
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$conditions[] = "UPPER( $cur_col ) LIKE CONCAT('%', UPPER(?), '%')";
$parameters[] = $term_searching;
}
}
$STH = $DBH->prepare('SELECT fields FROM tbl WHERE ' . implode(' OR ', $conditions));
$STH->execute($parameters);
Notes:
We let MySQL call UPPER() on the user's search term, rather than having PHP call strtoupper()
That should limit possible hilarious/confounding mismatched character set issues. All your normalization happens in one place, and as close as possible to the moment of use.
CONCAT() is MySQL-specific
However, as you tagged the question [mysql], that's probably not an issue.
This query, like your original query, will defy indexing.
Try something like this using an array to hold parameters. Notice % is added before and after term as LIKE %?% does not work in query string.PHP Manual
//Create array to hold $term_searching
$data = array();
foreach ($col_array as $cur_col) {
foreach ($search_terms_array as $term_searching) {
$item = "%".strtoupper($term_searching)."%";//LIKE %?% does not work
array_push($data,$item)
$qry_string.="UPPER(";
$qry_string.=$cur_col;
$qry_string.=") LIKE ? OR";
}
}
$qry_string = substr($qry_string, 0, -3);//Added to remove last OR
$STH = $DBH->prepare("SELECT fields FROM table WHERE ". $qry_string);//prepare added
$STH->execute($data);
EDIT
$qry_string = substr($qry_string, 0, -3) added to remove last occurrence of OR and prepare added to $STH = $DBH->prepare("SElECT fields FROM table WHERE". $qry_string)
how do you embed your sql scripts in php? Do you just write them in a string or a heredoc or do you outsource them to a sql file? Are there any best practices when to outsource them ? Is there an elegant way to organize this?
Use a framework with an ORM (Object-Relational Mapping) layer. That way you don't have to put straight SQL anywhere. Embedded SQL sucks for readability, maintenance and everything.
Always remember to escape input. Don't do it manually, use prepared statements. Here is an example method from my reporting class.
public function getTasksReport($rmId, $stage, $mmcName) {
$rmCondition = $rmId ? 'mud.manager_id = :rmId' : 'TRUE';
$stageCondition = $stage ? 't.stage_id = :stageId' : 'TRUE';
$mmcCondition = $mmcName ? 'mmcs.username = :mmcName' : 'TRUE';
$sql = "
SELECT
mmcs.id AS mmc_id,
mmcs.username AS mmcname,
mud.band_name AS mmc_name,
t.id AS task_id,
t.name AS task,
t.stage_id AS stage,
t.role_id,
tl.id AS task_log_id,
mr.role,
u.id AS user_id,
u.username AS username,
COALESCE(cud.full_name, bud.band_name) AS user_name,
DATE_FORMAT(tl.completed_on, '%d-%m-%Y %T') AS completed_on,
tl.url AS url,
mud.manager_id AS rm_id
FROM users AS mmcs
INNER JOIN banduserdetails AS mud ON mud.user_id = mmcs.id
LEFT JOIN tasks AS t ON 1
LEFT JOIN task_log AS tl ON tl.task_id = t.id AND tl.mmc_id = mmcs.id
LEFT JOIN mmc_roles AS mr ON mr.id = t.role_id
LEFT JOIN users AS u ON u.id = tl.user_id
LEFT JOIN communityuserdetails AS cud ON cud.user_id = u.id
LEFT JOIN banduserdetails AS bud ON bud.user_id = u.id
WHERE mmcs.user_type = 'mmc'
AND $rmCondition
AND $stageCondition
AND $mmcCondition
ORDER BY mmcs.id, t.stage_id, t.role_id, t.task_order
";
$pdo = new PDO(.....);
$stmt = $pdo->prepare($sql);
$rmId and $stmt->bindValue('rmId', $rmId); // (1)
$stage and $stmt->bindValue('stageId', $stage); // (2)
$mmcName and $stmt->bindValue('mmcName', $mmcName); // (3)
$stmt->execute();
return $stmt->fetchAll();
}
In lines marked (1), (2), and (3) you will see a way for conditional binding.
For simple queries I use ORM framework to reduce the need for building SQL manually.
It depends on a query size and difficulty.
I personally like heredocs. But I don't use it for a simple queries.
That is not important. The main thing is "Never forget to escape values"
You should always really really ALWAYS use prepare statements with place holders for your variables.
Its slightly more code, but it runs more efficiently on most DBs and protects you against SQL injection attacks.
I prefer as such:
$sql = "SELECT tbl1.col1, tbl1.col2, tbl2.col1, tbl2.col2"
. " FROM Table1 tbl1"
. " INNER JOIN Table2 tbl2 ON tbl1.id = tbl2.other_id"
. " WHERE tbl2.id = ?"
. " ORDER BY tbl2.col1, tbl2.col2"
. " LIMIT 10, 0";
It might take PHP a tiny bit longer to concatenate all the strings but I think it looks a lot nicer and is easier to edit.
Certainly for extremely long and specialized queries it would make sense to read a .sql file or use a stored procedure. Depending on your framework this could be as simple as:
$sql = (string) View::factory('sql/myfile');
(giving you the option to assign variables in the view/template if necessary). Without help from a templating engine or framework, you'd use:
$sql = file_get_contents("myfile.sql");
Hope this helps.
I normally write them as function argument:
db_exec ("SELECT ...");
Except cases when sql gonna be very large, I pass it as variable:
$SQL = "SELECT ...";
$result = db_exec ($SQL);
(I use wrapper-functions or objects for database operations)
$sql = sprintf("SELECT * FROM users WHERE id = %d", mysql_real_escape_string($_GET["id"]));
Safe from MySQL injection
You could use an ORM or an sql string builder, but some complex queries necessitate writing sql. When writing sql, as Michał Słaby illustrates, use query bindings. Query bindings prevent sql injection and maintain readability. As for where to put your queries: use model classes.
Once you get to a certain level, you realise that 99% of the SQL you write could be automated. If you write so much queries that you think of a properties file, you're probably doing something that could be simpler:
Most of the stuff we programmers do is CRUD: Create Read Update Delete
As a tool for myself, I built Pork.dbObject. Object Relation Mapper + Active Record in 2 simple classes (Database Abstraction + dbObject class)
A couple of examples from my site:
Create a weblog:
$weblog = new Weblog(); // create an empty object to work with.
$weblog->Author = 'SchizoDuckie'; // mapped internally to strAuthor.
$weblog->Title = 'A test weblog';
$weblog->Story = 'This is a test weblog!';
$weblog->Posted = date("Y-m-d H:i:s");
$weblog->Save(); // Checks for any changed values and inserts or updates into DB.
echo ($weblog->ID) // outputs: 1
And one reply to it:
$reply = new Reply();
$reply->Author = 'Some random guy';
$reply->Reply = 'w000t';
$reply->Posted = date("Y-m-d H:i:s");
$reply->IP = '127.0.0.1';
$reply->Connect($weblog); // auto-saves $reply and connects it to $weblog->ID
And, fetch and display the weblog + all replies:
$weblog = new Weblog(1); //Fetches the row with primary key 1 from table weblogs and hooks it's values into $weblog;
echo("<h1>{$weblog->Title}</h1>
<h3>Posted by {$weblog->Author} # {$weblog->Posted}</h3>
<div class='weblogpost'>{$weblog->Story}</div>");
// now fetch the connected posts. this is the real magic:
$replies = $weblog->Find("Reply"); // fetches a pre-filled array of Reply objects.
if ($replies != false)
{
foreach($replies as $reply)
{
echo("<div class='weblogreply'><h4>By {$reply->Author} # {$reply->Posted}</h4> {$reply->Reply}</div>");
}
}
The weblog object would look like this:
class Weblog extends dbObject
{
function __construct($ID=false)
{
$this->__setupDatabase('blogs', // database table
array('ID_Blog' => 'ID', // database field => mapped object property
'strPost' => 'Story', // as you can see, database field strPost is mapped to $this->Story
'datPosted' => 'Posted',
'strPoster' => 'Author',
'strTitle' => 'Title',
'ipAddress' => 'IpAddress',
'ID_Blog', // primary table key
$ID); // value of primary key to init with (can be false for new empty object / row)
$this->addRelation('Reaction'); // define a 1:many relation to Reaction
}
}
See, no manual SQL writing :)
Link + more examples: Pork.dbObject
Oh yeah i also created a rudimentary GUI for my scaffolding tool: Pork.Generator
I like this format. It was mentioned in a previous comment, but the alignment seemed off to me.
$query = "SELECT "
. " foo, "
. " bar "
. "FROM "
. " mytable "
. "WHERE "
. " id = $userid";
Easy enough to read and understand. The dots line up with the equals sign keeping everything in a clean line.
I like the idea of keeping your SQL in a separate file too, although I'm not sure how that would work with variables like $userid in my example above.