Problem: WP_Query search string needs to look into the taxonomy name.
By default, $args['s'] only searches inside the title, content, and excerpt, but I need to search inside a custom taxonomy name too. I tried with tax_query, but it doesn't have the name__like or LIKE operator for tax_query.
So, Now I'm trying to achieve it with the custom query; here's my code:
$keyword = "Monster";
$args['s'] = $keyword;
add_filter('posts_join', 'filter_post_join', 10, 1);
add_filter('posts_where', 'filter_post_where', 10, 1);
$posts = new WP_Query($args);
function filter_post_join( $join ) {
global $wpdb;
$join .= " LEFT JOIN $wpdb->term_relationships as txr ON ( {$wpdb->posts}.ID = txr.object_id )";
$join .= " LEFT JOIN $wpdb->terms as trms on ( txr.term_taxonomy_id = trms.term_id )";
return $join;
}
function filter_post_where( $where ) {
global $keyword;
$where .= " AND trms.name LIKE $keyword";
return $where;
}
Can anyone please tell me what I am doing wrong here?
I found a solution that kind of works: https://gist.github.com/markoman78/e22bbebe0d2305f294eb554d5a39d8c3
But it has a problem, it doesn't filter post_type; if I want to fetch posts from 'x' post type it will also fetch posts from other posts type that has matching tags name
Related
I need to change this query to use a prepared statement. Is it possible?
The query:
$sql = "SELECT id, title, content, priority, date, delivery FROM tasks " . $op . " " . $title . " " . $content . " " . $priority . " " . $date . " " . $delivery . " ORDER BY " . $orderField . " " . $order . " " . $pagination . "";
Before the query, there's code to check the POST variables and change the content of variables in the query.
//For $op makes an INNER JOIN with or without IN clause depending on the content of a $_POST variable
$op = "INNER JOIN ... WHERE opID IN ('"$.opID."')";
//Or
$op = "INNER JOIN ... ";
//For $title (depends of $op):
$title = "WHERE title LIKE'%".$_POST["title"]."%'";
//Or
$title = "AND title LIKE'%".$_POST["title"]."%'";
//For $content:
$content = "AND content LIKE '%".$_POST["content"]."%'";
//For $priority just a switch:
$priority = "AND priority = DEPENDING_CASE";
//For $date and $delivery another switch
$d = date("Y-m-d", strtotime($_POST["date"]));
$date = "AND date >= '$d' 00:00:00 AND date <= '$d' 23:59:59";
//Or $date = "AND date >= '$d' 00:00:00";
//Or $date = "AND date <= '$d' 23:59:59";
//For $orderField
$orderField = $_POST["column"];
//For $order
$order= $_POST["order"];
//For $pagination
$pagination = "LIMIT ".$offset.",". $recordsPerPage;
How I could do this query using prepared statement?
The query could be more static but this means to make different prepared statements and execute it depending of $_POST checks.
It depends on many variables because this query show results in a table that contains search fields and column to order.
A full example of query would be like this (depending of $_POST checks):
SELECT id, title, content, priority, date, delivery FROM tasks INNER JOIN op ON task.op = op.opId WHERE op IN (4851,8965,78562) AND title LIKE '%PHT%' AND content LIKE '%%' AND priority = '2' ORDER BY date DESC LIMIT 0, 10
An excellent question. And thank you for moving to prepared statements. It seems that after all those years of struggle, the idea finally is starting to take over.
Disclaimer: there will be links to my own site because I am helping people with PHP for 20+ years and got an obsession with writing articles about most common issues.
Yes, it's perfectly possible. Check out my article, How to create a search filter for mysqli for the fully functional example.
For the WHERE part, all you need is to create two separate arrays - one containing query conditions with placeholders and one containing actual values for these placeholders, i.e:
WHERE clause
$conditions = [];
$parameters = [];
if (!empty($_POST["content"])) {
$conditions[] = 'content LIKE ?';
$parameters[] = '%'.$_POST['content ']."%";
}
and so on, for all search conditions.
Then you could implode all the conditions using AND string as a glue, and get a first-class WHERE clause:
if ($conditions)
{
$where .= " WHERE ".implode(" AND ", $conditions);
}
The routine is the same for all search conditions, but it will be a bit different for the IN() clause.
IN() clause
is a bit different as you will need more placeholders and more values to be added:
if (!empty($_POST["opID"])) {
$in = str_repeat('?,', count($array) - 1) . '?';
$conditions[] = "opID IN ($in)";
$parameters = array_merge($parameters, $_POST["opID"]);
}
this code will add as many ? placeholders to the IN() clause as many elements in the $_POST["opID"] and will add all those values to the $parameters array. The explanation can be found in the adjacent article in the same section on my site.
After you are done with WHERE clause, you can move to the rest of your query
ORDER BY clause
You cannot parameterize the order by clause, because field names and SQL keywords cannot be represented by a placeholder. And to tackle with this problem I beg you to use a whitelisting function I wrote for this exact purpose. With it you can make your ORDER BY clause 100% safe but perfectly flexible. All you need is to predefine an array with field names allowed in the order by clause:
$sortColumns = ["title","content","priority"]; // add your own
and then get safe values using this handy function:
$orderField = white_list($_POST["column"], $sortColumns, "Invalid column name");
$order = white_list($_POST["order"], ["ASC","DESC"], "Invalid ORDER BY direction");
this is a smart function, that covers three different scenarios
in case no values were provided (i.e. $_POST["column"] is empty) the first value from the white list will be used, so it serves as a default value
in case a correct value provided, it will be used in the query
in case an incorrect value is provided, then an error will be thrown.
LIMIT clause
LIMIT values are perfectly parameterized so you can just add them to the $parameters array:
$limit = "LIMIT ?, ?";
$parameters[] = $offset;
$parameters[] = $recordsPerPage;
The final assembly
In the end, your query will be something like this
$sql = "SELECT id, title, content, priority, date, delivery
FROM tasks INNER JOIN ... $where ORDER BY `$orderField` $order $limit";
And it can be executed using the following code
$stmt = $mysqli->prepare($sql);
$stmt->bind_param(str_repeat("s", count($parameters)), ...$parameters);
$stmt->execute();
$data = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
where $data is a conventional array contains all the rows returned by the query.
I have the following getListQuery() in my model. I want to add another join (see further) and was wondering if the level part could be done another way (without GROUP_CONCAT):
protected function getListQuery()
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$this->getState(
'list.select',
'a.id AS id,' .
'a.dbid AS dbid,' .
'a.alias AS alias,' .
'GROUP_CONCAT(DISTINCT l.level ORDER BY l.level ASC) as `levels`'
)
);
$query->from('#__maintable AS a');
$query->join('LEFT', '#__leveltable AS l ON l.dbid = a.dbid');
$query->group($db->quoteName('a.id'));
$query->order($db->escape($this->state->get('list.ordering', 'a.id') . ' ' . $db->escape($this->state->get('list.direction', 'ASC'))));
return $query;
}
In the leveltable there can be more then one row with a corresponding 'dbid'.
I would also like to add a second table which has a relation to 'dbid' which also can have multiple rows with the same 'dbid' and it has more fields I would require then just the 'level' field from leveltable.
I am trying to work with a voting database system. I have a form to display all the candidates per candidate type. I'm still trying to to explore that. But this time, I want to try one candidate type, lets say for Chairperson, I want to display all candidate names for that type in the ballot form. However, there's an error with the line where i declare $query and made query statements, can somebody know what it is. I am very certain that my syntax is correct.
function returnAllFromTable($table) {
include 'connect.php';
$data = array ();
$query = 'SELECT * FROM ' . $table. 'WHERE candidateId=1'; //ERROR
$mysql_query = mysql_query ( $query, $conn );
if (! $mysql_query) {
die ( 'Go Back<br>Unable to retrieve data from table ' . $table );
} else {
while ( $row = mysql_fetch_array ( $mysql_query ) ) {
$data [] = $row;
}
}
return $data;
}
As #Darwin von Corax says, I sure that you have a problem between $table and WHERE
Your query:
$query = 'SELECT * FROM ' . $table. 'WHERE candidateId=1';
If $table = 'Chairperson';
You have:
'SELECT * FROM ChairpersonWHERE candidateId=1';
The your query should be:
$query = 'SELECT * FROM ' . $table. ' WHERE candidateId=1';
I have a custom post type (songs) and several custom fields associated with it. One of the custom fields is a checkbox (sample_playlist), the rest are text strings.
I have added a select element and I am using the checkbox value to filter the results when on edit.php?post_type=songs
add_filter( 'parse_query', array( &$this, 'wpse45436_posts_filter' ) );
function wpse45436_posts_filter( $query ){
// current page and post type checks omitted
$query->query_vars['meta_key'] = 'sample_playlist';
$query->query_vars['meta_value'] = 'on'; //these are the queries that get executed when filtering the posts that have the custom field checkbox checked
}
This works fine when I only attempt to filter the results based on the checkbox value.
Searching by other custom field values is also possible on that same page via
add_filter( 'posts_join', array( &$this, 'songs_search_join' ) );
add_filter( 'posts_where', array( &$this, 'songs_search_where' ) );
function songs_search_join ($join){
// current page, post type and $_GLOBAL['s'] checks omitted
$join .='LEFT JOIN '.$wpdb->postmeta. ' ON '. $wpdb->posts . '.ID = ' . $wpdb->postmeta . '.post_id ';
return $join;
}
function songs_search_where( $where ){
// current page, post type and $_GLOBAL['s'] checks omitted
$where = preg_replace(
"/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
"(".$wpdb->posts.".post_title LIKE $1) OR (".$wpdb->postmeta.".meta_value LIKE $1)", $where
);
return $where;
}
The search works fine when I only try to search terms by custom field values.
HOWEVER, when I try to use both of these sequentially (ie search then filter, or vice versa), I run into a problem with the search join using wp_postmeta as well as the filter by "custom field checkbox value" using it as well. Is there a way around this, that would allow me to utilize both of these sequentially?
the error I recieve: WordPress database error Not unique table/alias: 'wp_postmeta'
the resulting sql query output:
SELECT SQL_CALC_FOUND_ROWS wp_posts.ID FROM wp_posts INNER JOIN wp_postmeta ON (wp_posts.ID = wp_postmeta.post_id)LEFT JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id WHERE 1=1 AND (((wp_posts.post_title LIKE '%searchterm%') OR (wp_postmeta.meta_value LIKE '%searchterm%') OR (wp_posts.post_content LIKE '%searchterm%'))) AND wp_posts.post_type = 'songs' AND (wp_posts.post_status = 'publish' OR wp_posts.post_status = 'future' OR wp_posts.post_status = 'draft' OR wp_posts.post_status = 'pending' OR wp_posts.post_status = 'private') AND ( (wp_postmeta.meta_key = 'sample_playlist' AND CAST(wp_postmeta.meta_value AS CHAR) = 'on') ) ORDER BY wp_posts.post_date DESC LIMIT 0, 20
I needed to use an alias for $wpdb->postmeta in my search:
add_filter( 'posts_join', array( &$this, 'songs_search_join' ) );
add_filter( 'posts_where', array( &$this, 'songs_search_where' ) );
function songs_search_join ($join){
// current page, post type and $_GLOBAL['s'] checks omitted
$join .='LEFT JOIN '.$wpdb->postmeta. ' p ON '. $wpdb->posts . '.ID = p.post_id ';
return $join;
}
function songs_search_where( $where ){
// current page, post type and $_GLOBAL['s'] checks omitted
$where = preg_replace(
"/\(\s*".$wpdb->posts.".post_title\s+LIKE\s*(\'[^\']+\')\s*\)/",
"(".$wpdb->posts.".post_title LIKE $1) OR (p.meta_value LIKE $1)", $where
);
return $where;
}
I need to make a search engine where a user can search by name,course,member,year(text field) from the table fsb_profile fields are profile_name,profile_course,profile_member,profile_year
search will be with any one field
or
search will be with all the field
or
search will be with more than one field
-How it is possible by using only one query??
i am making the code like:-
$query="select * from fsb_profile
where profile_name = '".$_REQUEST['name']."'
and profile_member= '".$_REQUEST['type']."'
and profile_year= '".$_REQUEST['year']."'
and profile_course='".$_REQUEST['course']."'
or profile_name = '".$_REQUEST['name']."'
or profile_member= '".$_REQUEST['type']."'
or profile_year= '".$_REQUEST['year']."'
or profile_course='".$_REQUEST['course']."'";
-but it is not working?
try this query. using this query you can extract details using the combination of search factors
$query="select * from fsb_profile
where profile_name = '".$_REQUEST['name']."'
or profile_member= '".$_REQUEST['type']."'
or profile_year= '".$_REQUEST['year']."'
or profile_course='".$_REQUEST['course']."'";
If I understand you correctly, you want to search so that either all the fields match or that at least two fields match?
In that case I'd try the following:
$query="select * from fsb_profile
where
(
profile_name = '".$_REQUEST['name']."'
and profile_member= '".$_REQUEST['type']."'
and profile_year= '".$_REQUEST['year']."'
and profile_course='".$_REQUEST['course']."'
)
OR
(
(
profile_name = '".$_REQUEST['name']."'
AND
(
profile_member= '".$_REQUEST['type']."' OR
profile_year= '".$_REQUEST['year']."' OR
profile_course='".$_REQUEST['course']."'"
)
)
OR
(
profile_member= '".$_REQUEST['type']."'
AND
(
profile_year= '".$_REQUEST['year']."' OR
profile_course='".$_REQUEST['course']."'"
)
)
OR
(
profile_year= '".$_REQUEST['year']."' AND
profile_course='".$_REQUEST['course']."'"
)
)
This returns all sets where either all criteria match or a combination of at least two other criteria matches. I didn't try this really, but that's what I'd start off with.
First off, I would advise you to sanitize your input data. You should NEVER put user-entered data into an SQL query without checking it; that's just asking for trouble.
As for your question, it seems like you're having some trouble with the logic (ANDs and ORs) in your statement. With the statement you are using, you will get all records that match all four fields entered in the search engine, as well as all records that match ANY of the four fields entered. It might be best for you to just construct the query string on the fly, something like:
$arr = sanitize_data($_REQUEST);
$query = "select * from fsb_profile ";
$count = 0;
if ( isset($arr['name']) ) {
$query .= (($count > 0)?"and":"where")." profile_name = '".$arr['name']."' ";
count++;
}
if ( isset($arr['type']) ) {
$query .= (($count > 0)?"and":"where")." profile_member = '".$arr['type']."' ";
count++;
}
if ( isset($arr['year']) ) {
$query .= (($count > 0)?"and":"where")." profile_year = '".$arr['year']."' ";
count++;
}
if ( isset($arr['course']) ) {
$query .= (($count > 0)?"and":"where")." profile_course = '".$arr['course']."' ";
count++;
}
You need to add some If statements to only include search criteria if the information is filled in.
$query = "select * from fsb_profile"<br />
$subquery = ""<br />
If($_REQUEST['name') != "") {<br />
if($subquery == "") $subquery = "where "<br />
else $subquery .= "and "<br />
<br />
$subquery .= "profile_name = '" . $_REQUEST['name']<br/>
}
$query .= $subquery
You could continue to do that for all the items. Note that you can use a for statement and I would HIGHLY recommend parameterizing the search string to prevent SQL injection attacks. I have only include some of the code here for brevity.
This will search on ALL the criteria that is specified to find a result.