Search engine, what to do when user wants 'all' - php

I'm an absolute noob to programming and over the past 10 days or so I have been building a 'buy sell trade' website for things.
Anyway I have got to the point where I now have the ability for people to register, post ads and reply to them... but now I want to have a page where they can search through the adverts.
The search form works on 3 fields, 'make', 'model' and 'caliber' (site is for guns)
<form action="" method="get" autocomplete="off">
I'm looking for a
<input type="text" name="make" class="autosuggestmake" placeholder="Manufacturer"/>
<div class="dropdown">
<ul class="resultmake"></ul>
</div>
<input type="text" name="model" class="autosuggestmodel" placeholder="Model"/>
<div class="dropdown">
<ul class="resultmodel"></ul>
</div>
in
<select name="caliber" >
<option value="*">(Caliber) Any</option>
<option value=".177">.177</option>
<option value=".20">.20</option>
<option value=".22">.22</option>
<option value=".25">.25</option>
</select>
<input type="submit" value="Search" />
This is posted as GET data which I 'catch' with this code:
$advert = new Advert;
if (empty($_GET) === true){
$adverts = $advert->fetch_all();
} else {
$search_make = $_GET['make'];
$search_model = $_GET['model'];
$search_caliber = $_GET['caliber'];
$adverts = $advert->fetch_results($search_make, $search_model, $search_caliber);
}
My fetch_results query is this:
class Advert {
public function fetch_results($search_make, $search_model, $search_caliber) {
global $pdo;
$search_caliber = mysql_real_escape_string($search_caliber);
$search_make = mysql_real_escape_string($search_make);
$search_model = mysql_real_escape_string($search_model);
if (empty($search_make) === true){
$search_make = "*";
}
if (empty($search_model) === true){
$search_model = "*";
}
$query = $pdo -> prepare("SELECT * FROM `adverts` WHERE make = '$search_make' AND model = '$search_model' AND caliber = '$search_caliber'");
$query -> execute();
return $query -> fetchAll();
}
On my last question someone told me to start using PDO, so I did :)
My problem is when someone fills in the make field on my form and nothing else it will return nothing. I thought if the get variables were blank I would append a * and it will return anything but this is not the case :( I've been searching but I think my problem is I don't know the correct words to search to find the cure for my problem...
Any help would be greatly appreciated.

Having implemented a site with this myself, I strongly recommend Sphinx. This is what craigslist uses. It is simple enough for your needs, yet powerful enough to grow with you.
Another alternative is ElasticSearch, which I'm told is very good as well.

There are two approaches you can take. One is to rewrite the query in php based on the search conditions.
The other is a SQL solution:
SELECT *
FROM `adverts`
WHERE ('$search_make' = '' or make = '$search_make') AND
('$search_model' or model = '$search_model') AND
('$search_caliber' or caliber = '$search_caliber');
However, if the table has indexes on the columns, then this query probably will not use the indexes. Writing the php to include only the clauses with values creates a more efficient query.

Either you let your users browse "all" -- Use case = some collector looking for odd items.
Or you have a "Search By" pull down and make your users choose at least one of the search categories.
You could just send an error message if all the search criteria are blank -- but this tends to annoy users more than leading them through a set of search option screens.

Related

PHP MySQL Adjusting Query with QueryString

Apologies for the newbie question.
My website has a form.
<form action='' method='get'>
<select id="cSelector" name="cSelector">
<option value=""></option>
<option value="">Show All Items</option>
<option value="Compensation">Compensation</option>
</select>
<input type="submit" value="Submit">
</form>
My querystring, created on form submission, looks like this:
http://website.com/table_example.php?cSelector=Compensation
My query looks like this:
$stmt = $conn->prepare("
SELECT t1.CategoryID,t1.SubCategoryName, t1.CategoryName, t1.SubCategoryID, t2.ItemText from
(SELECT Category.CategoryID,CategoryName, SubCategoryName, SubCategoryID
FROM Category
JOIN SubCategory
ON Category.CategoryID = SubCategory.CategoryID) t1
RIGHT JOIN
(SELECT SubCategoryID, ItemText FROM Item) t2
ON (t1.SubCategoryID = t2.SubCategoryID)
WHERE 1 ".$searchQuery." AND CategoryName = ".$search2." ORDER BY ".$columnName." ".$columnSortOrder." LIMIT :limit,:offset");
The intended result produces a table queried by CategoryName.
My question. Why does this properly execute?
$search2='Compensation';
And this does not?
$search2 = "'".$_GET['cSelector']."'";
Any help would be very much appreciated. And thank you!
You're submitting this form via GET
<form action='' method='get'>
Your line though $search2 = "'".$_POST['cSelector']."'"; is using $_POST
It should be $_GET instead:
$search2 = "'" . $_GET['cSelector'] . "'";`
AFTER OP's CHANGES
This
$search2='Compensation';
and
$search2 = "'".$_GET['cSelector']."'";
are not the same. The top is just a string value. The bottom is a string value wrapped in quotes, so it isn't Compensation it is 'Compensation'.
The core of the issue is actually that you're not exactly sure what the query is. If the two strings sent were identical, they would both run, but they're not. Somehow.
The real need is visibility into your query. So something like
$strQuery = "SELECT t1.CategoryID,t1......";
echo "<pre>$strQuery</pre>";
$stmt = $conn->prepare($strQuery)
Now you can see what it's doing. You're operating blind as it is.
Two additional notes:
You'll hear from everyone that it's a bad idea to put paramaters you're getting from a get or post straight into a SQL query. They're not wrong.
String building for these things is always easier if you're a little more verbose about it. Grab the variable first, as you're going to want to do some processing on it anyway, trimming whitespace, protecting against quotes, etc. Then put it in your query string

Cannot insert radio button data into mariadb (mysql)

I have tried many different solutions to no avail you will see the attempts.
The columns in question are tinyint 1 pass and 0 fail. From poweradapter down. The textarea goes into the database just fine. I've tried enumerating the boolean and print_r($_POST and now im just lost.
*Also put through mysqli_real_escape
if (isset($_POST['submit'])) {
$tester = $_POST['tester'];
$manufacturer = $_POST['manufacturer'];
$model = $_POST['model'];
$serial = $_POST['serial'];
$poweradapter = $_POST['poweradapter'];
$query = "INSERT INTO tested (tester, manufacturer, model, serial, poweradapter)
VALUES ('$tester','$manufacturer','$model','$serial','$poweradapter');";
<div class="fb-radio-group form-group field-poweradapter"><label for="poweradapter" class="fb-radio-group-label">Power Adapter<span class="fb-required">*</span><span class="tooltip-element" tooltip="Plug into Laptop - Charges the Battery">?</span></label>
<div class="radio-group"><div class="radio"><label for="poweradapter-0"><input name="poweradapter" id="poweradapter-0" required="required" aria-required="true" value="1" type="radio">Pass</label></div>
<div class="radio"><label for="poweradapter-1"><input name="poweradapter" id="poweradapter-1" required="required" aria-required="true" value="0" type="radio">Fail</label></div>
</div></div>
Unless MariaDB works quite a bit differently than regular MySQL, it looks to me like you are only storing a MySQL statement in a variable without actually executing it.
You may want to do something like:
$query = $db->query("INSERT INTO tested (tester, manufacturer, model, serial, poweradapter)
VALUES ('$tester','$manufacturer','$model','$serial','$poweradapter')";
With $db containing your database connection.
So I suppose I'll just rewrite the form as the issue isn't the radio button entirely rather when I mix one of the textareas with it. Thank you all

Build a sql query in php from multiple form inputs

I'm looking for a way to get lots of user inputs, concatenate them into one sql query, and return the results from my database. I have tried a few different techniques so far including putting all the variables into an array then using implode() but I couldn't get it to work. For simplicity sake I have decided to just go with a couple of if statements to check if each variable has a value in it or not. If it does then it should add some sql. My error message from this is as follows:
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 'AND type = AND (city LIKE '%%') OR (addressLineOne LIKE
'%%') OR (`addres' at line 1
It appears that $type is not being picked up even though I gave it a input during the test. I have not given any other inputs values besides $type and $bedroom.
Any help and improvement on the code would be greatly appreciated. I'm new to PHP and SQL so sorry if it's something stupid, but I have tried to fix this for ages.
HTML
<form action="searchresults.php" method="get">
<fieldset>
<legend><h3>Search</h3></legend>
<p>Please enter criteria for your search.</p>
<label for="location">Location</label>
<input type="text" name="location" />
<select name="type">
<option value="Studio Flat" selected>Studio Flat</option>
<option value="Flat">Flat</option>
<option value="Detached">Detached</option>
<option value="Semi-detached">Semi-detached</option>
<option value="Terraced">Terraced</option>
<option value="Bungalow">Bungalow</option>
</select>
<label for="bedroom">Bedrooms</label>
<select name="bedroom">
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
</select>
<label for="min">Min Price</label>
<input type="number" name="min" />
<label for="max">Max Price</label>
<input type="number" name="max" />
<br />
<input type="submit" value="Search" />
</fieldset>
</form>
PHP
<?php
session_start();
include './auth.php'; // connection to db
$location = trim($_POST['location']);
$location = strip_tags($location);
$location = htmlspecialchars($location);
$bedroom = trim($_POST['bedroom']);
$bedroom = strip_tags($bedroom);
$bedroom = htmlspecialchars($bedroom);
$type = trim($_POST['type']);
$type = strip_tags($type);
$type = htmlspecialchars($type);
$max = trim($_POST['max']);
$max = strip_tags($max);
$max = htmlspecialchars($max);
$min = trim($_POST['min']);
$min = strip_tags($min);
$min = htmlspecialchars($min);
// build query
$query = "SELECT * FROM Listings WHERE `bedroom` = ".$bedroom." AND `type` = ".$type."";
if(isset($location)){
$query .= " AND (`city` LIKE '%".$location."%') OR (`addressLineOne` LIKE '%".$location."%') OR (`addressLineTwo` LIKE '%".$location."%') OR (`county` LIKE '%".$location."%')";
}
if(isset($max)){
$query .= " AND (`price` <= '%".$price."%')";
}
if(isset($min)){
$query .= " AND (`price` >= '%".$price."%')";
}
$query .= "ORDER BY price;";
// send query to database and return error if it fails
$input = mysqli_query($connect, $query) or die(mysqli_error($connect));
// output results
if(mysqli_num_rows($input)>0){ // if one or more results returned do this code
while($result = mysqli_fetch_array($input)){ // puts data in array then loops the following code
echo "<p><h3>".$result['addressLineOne']." ".$result['addressLineTwo']."
".$result['location']."</h3><h4>£".$result['price']."</h4>".$result['information']."</p><br /><hr />";
}
}else{ // no results then print the following
echo "Sorry, we couldn't find any results.
Please refine your search and try again.";
}
echo $query;
// close the connection
mysqli_close($connect)
?>
I know you're currently using mysqli, but PDO makes building dynamic queries much easier, so I strongly suggest you switch to it, if you're not very far along on this project.
In a mysqli prepared statement, you have to call mysqli_stmt::bind_param(), passing every parameter in the argument list. In contrast, PDO requires no binding, and the parameters are all passed to PDOStatement::execute() in an array. This answer will show you how your code would work with PDO.
<?php
$connection = new \PDO("mysql:host=$db_host;dbname=$db_name", $db_user, $db_pass);
$query = "SELECT * FROM Listings WHERE bedroom = :bed AND type = :type AND (city LIKE :loc OR addressLineOne LIKE :loc OR addressLineTwo LIKE :loc OR county LIKE :loc)";
$parameters = [
":bed" => $_POST["bedroom"],
":type" => $_POST["type"],
":loc" => "%$_POST[location]%",
];
if(!empty($_POST["max"])) {
$query .= " AND price <= :max";
$parameters[":max"] = $_POST["max"];
}
if (!empty($_POST["min"])) {
$query .= " AND price >= :min";
$parameters[":min"] = $_POST["min"];
}
$query .= " ORDER BY price";
$stmt = $connection->prepare($query);
$stmt->execute($parameters);
$results = $stmt->fetchAll(\PDO::FETCH_ASSOC);
if (empty($results)) { // no results then print the following
echo "Sorry, we couldn't find any results. Please refine your search and try again.";
}
foreach ($results as $result) {
//escape for HTML output
$result = array_map("htmlspecialchars", $result);
echo <<< HTML
<p>
<h3>$result[addressLineOne] $result[addressLineTwo] $result[location]</h3>
<h4>£$result[price]</h4>
$result[information]
</p>
<br />
<hr />
HTML;
}
I've also simplified your HTML output by using a heredoc string, but you should really have your HTML and PHP separated.
If this is part of a much bigger existing project, you will likely be sticking with mysqli, in which case I urge you to learn how to use prepared statements; the days of building queries with string concatenation are long behind us!
Using PDO and bound parameters as #miken32 suggests is a possibility, but I advise against it because it has several downsides:
You are currently using mysqli, which also supports bound parameters, so no reason to switch to PDO just to get parameter binding
#miken32's solution uses a named parameter (something only PDO supports) multiple times in the query. This only works when client-side parameter binding is enabled (i.e. PDO::ATTR_EMULATE_PREPARES set to true) which itself has multiple problems:
I could not quickly find out what the default for PDO::ATTR_EMULATE_PREPARES is, apparently "it depends on the driver".
With PDO::ATTR_EMULATE_PREPARES switched on, you cannot use bound parameters in your LIMIT clause anymore
The errors returned from PDO are completely different depending on whether you use client-side or server-side parameter binding, a missing parameter for example causes an SQLSTATE of HY000 with server-side and HY093 with client-side parameter binding. This complicates proper error handling.
There is no way to see the actual query, an ability that can be very useful (essential in my opinion) for debugging.
So, I say go ahead and build your query manually, but in a clean and safe way. To do that you need to understand these concepts:
HTML encoding
In general, the only place where you need htmlspecialchars() is when you pass data (for example from the database) to the browser.
So, change every
$location = trim($_POST['location']);
$location = strip_tags($location);
$location = htmlspecialchars($location);
to
$location = trim($_POST['location']);
There is no need for strip_tags() nor htmlspecialchars() at this point. Form data from the browser arrives on the PHP side without any encoding. Of course, if someone would actually enter some<br>city into the location field, he would not find any rooms, but it will not break anything if you get the rest of your application right.
Also change every
echo "<p>".$result['addressLineOne']." ... </p><br /><hr />";
to
echo "<p>".htmlspecialchars($result['addressLineOne'])." ... </p><br /><hr />";
Otherwise the people entering the data into the database could run a "cross site scripting attack" - maliciously or accidentally.
SQL encoding
When sending data to the database as part of a query, you have to encode it in a way that the database knows that it's variable data and not part of the SQL command.
So, instead of for example
$query .= " AND (`city` LIKE '%".$location."%') ";
you have to write
$query .= " AND (`city` LIKE '%".addslashes($location)."%') ";
this makes sure that if there is a quote character (') inside your $location variable it will be escaped, so it does not end the string at that point.
If you work with a character set other than UTF-8, you should use mysql_real_escape_string() instead of addslashes() but you are probably using UTF-8, so it's just fine.
Furthermore in this case you might want to remove or escape % and _ in the value, because they have a special meaning in a LIKE query.
You have two more problems in your code:
$query = "SELECT * FROM Listings WHERE `bedroom` = ".$bedroom." AND `type` = ".$type."";
Here you not only have to add addslashes() but also the quotes around the value:
$query = "SELECT * FROM Listings WHERE `bedroom` = '".addslashes($bedroom)."' AND `type` = '".addslashes($type)."'";
And here:
$query .= " AND (`price` <= '%".$price."%')";
Obviously the percent signs make no sense here, you clearly meant:
$query .= " AND (`price` <= '".addslashes($price)."')";
Libraries and template engines
Later on you would be well advised using a library or framework that does those things for you (because it's very easy to forget an addslashes() somewhere), but it can't hurt to do it manually at first in order to learn how it works under the hood.

Creating Custom MySQL Search (Drop Down Menus) [duplicate]

This question already has answers here:
When to use single quotes, double quotes, and backticks in MySQL
(13 answers)
Closed 6 years ago.
I have a database full of items for a video game. Swords, Shields, etc. I tagged all the items with level, effect, location found and stuff like that.
I can hard code pages to pull from the database. Such as a hard coded dagger that stuns query:
$results = $mysqli->query("SELECT name, type, level, effect
FROM Items
WHERE type = 'Dagger'
AND effect = "Stun"
ORDER BY name ASC");
while ($item= $results->fetch_assoc()) { $result_array[] = $item; }
However I want users to go through drop down menus so they can filter results from the database from themselves. I have no idea how to go about this. I have tried googling but a lot of it seems outdated or when I try it just doesn't work.
Something similair to this website - http://www.wowhead.com/items
So for example users could pick the "Effect" drop down and it creates another drop down where you can choose from; Freeze, Heal, Stun or whatever. Then pick level from drop down menu and enter 50. Then the database would pull the results from the database for daggers that stun and can be used at level 50.
Thanks!
It's pretty simple. First you have the form element.
<form type="post" action="controller.php">
<select name="weapon">
<option value="Dagger"> Dagger </option>
<option value="Sword"> Sword </option>
</select>
<select name="effect">
<option value="Stun"> Stun </option>
<option value="Knock Back"> Knock Back </option>
</select>
</form>
When the user selects a value from these dropdowns, they'll be sent over to the server in the $_POST array with their key's matching the "name" of the select element. The controller.php file is the file that will handle the form submission. You can change the location of this file etc.
Then in your form submission handler you want to handle the $_POST array and then create a prepared statement for security as we're dealing with user input.
/**
| ---------------------------------------------------
| controller.php
| ---------------------------------------------------
*/
if(isset($_POST)){
$weapon = isset($_POST['weapon']) ? $_POST['weapon'] : false;
$effect = isset($_POST['effect']) ? $_POST['effect'] : false;
if($weapon && $effect){
$mysqli = new mysqli('host', 'user', 'pass', 'database');
$result = $mysqli->prepare("SELECT name, type, level, effect
FROM Items
WHERE type = ?
AND effect = ?
ORDER BY name ASC");
$result->bind_param('ss', $weapon, $effect);
if($result->execute()){
while($row = $result->fetch_assoc()){
//access column names here by $row['index'];
}
}
}
}
The above script is checking if the post array is populated, then checks for our specific variables. from there, we establish the database connection, create the safe prepared statement, bind our parameters to our prepared statement, execute the query, then we iterate over the returned resultset with fetch_assoc.
I hope this helps you.

Best practice: Use same form for creation and update

I'm just curious and was wondering how you guys handle it if you want to use the same html form and as far as possible the same php code to create and update an item.
Example:
On one page you can create a database entry with name, email address and age.
On a different(?) page you see the form fields filled with your data and you can edit and save it.
I have my ways to accomplish this using pretty much the same code - but I'm hoping to learn something here. So how would you handle this task?
Thanks & Cheers, sprain
Pretty easily - if an ID of an existing item (which the user is authorised to edit) is supplied in the query string, then it's an edit operation.
If no ID is supplied in the query string, it's a create operation.
The fields are pre-populated based on the existing values from the database if it's an edit operation, or based on default values or empty strings if it's a create operation.
The way I see it is that reusing identical markup for form between create/edit works for some cases, but not for all. I find that forms -- though they may map to the same database table -- are really defined by their context. For example, if you had a 'users' table, you might have a 'create' form with username, email, password, but after that user exists you want them to retain their identity on their site, so the username field would not appear in an 'edit' context. I'm classically a PHP developer, but I have come to appreciate the approach that Django takes, where you create a model (table) that defines the basic validation for each field and you can create as many forms as you that build off of, or modify/extend from that definition. If you're writing from scratch, you'll probably find it practical to make your validation methods very portable and/or find ways to make your form fields context-sensitive.
That's the way I always do it now. Are you using an MVC system at all? I use one controller with two different actions (urls = person/new + person/edit/xxxx_id).
the code is then something like:
function new()
errors = []
if (get)
data = blank_record()
elseif (post)
data = posted_data
if (create(data))
redirect_to_listing()
else
errors = describe_errors
show_form(data, errors)
function edit()
errors = []
if (get)
data = get_from_db(id)
elseif (post)
data = posted_data
if (save())
redirect_to_listing()
else
errors = describe_errors
show_form(data, errors)
Note that once it gets to the form there's always an object called data that the form can render, it may be blank, from the db, or posted data. Either way it should always be the same format.
The reason I split new and edit is that I find that often enough they are actually quite different in their behaviours and the load and save steps.
I guess this is not the right answer but it might be interesting for you anyway.
There is an orm project called doctrine:
http://www.doctrine-project.org/projects/orm/1.2/docs/en
// User Id might be an existing id, an wrong id, or even empty:
$user_id = 4;
$user_id = null;
// Fetch the user from the database if possible
$user = Doctrine::getTable('Model_User')->find($user_id);
// If there was no record create a new one
if ( $user === false )
$user = new Model_User();
// Change some data
$user->title = $newValue;
// Perform an update or an insert:
$user->save();
As you see you don't have to care about sql.
Doctrine does that for you and your code becomes easier to read and to debug.
Yes, that's the only acceptable solution.
Here is a little example of CRUD application which store the input form in a template:
<?
mysql_connect();
mysql_select_db("new");
$table = "test";
if($_SERVER['REQUEST_METHOD']=='POST') { //form handler part:
$name = mysql_real_escape_string($_POST['name']);
if ($id = intval($_POST['id'])) {
$query="UPDATE $table SET name='$name' WHERE id=$id";
} else {
$query="INSERT INTO $table SET name='$name'";
}
mysql_query($query) or trigger_error(mysql_error()." in ".$query);
header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']);
exit;
}
if (!isset($_GET['id'])) { //listing part:
$LIST=array();
$query="SELECT * FROM $table";
$res=mysql_query($query);
while($row=mysql_fetch_assoc($res)) $LIST[]=$row;
include 'list.php';
} else { // form displaying part:
if ($id=intval($_GET['id'])) {
$query="SELECT * FROM $table WHERE id=$id";
$res=mysql_query($query);
$row=mysql_fetch_assoc($res);
foreach ($row as $k => $v) $row[$k]=htmlspecialchars($v);
} else {
$row['name']='';
$row['id']=0;
}
include 'form.php';
}
?>
form.php
<form method="POST">
<input type="text" name="name" value="<?=$row['name']?>"><br>
<input type="hidden" name="id" value="<?=$row['id']?>">
<input type="submit"><br>
Return to the list
</form>
list.php
Add item
<? foreach ($LIST as $row): ?>
<li><?=$row['name']?>
<? endforeach ?>
Of course, some fancy form constructor, like HTML_QuickForm2 coud be used instead of plain HTML template - you know its constant programmer's hunger not to repeat himself, even in naming an HTML field, field value and error key :)
But personally I prefer plain HTML.

Categories