WordPress query posts without ACF repeater subfields AND specific value - mysql

I have two custom post types, playlists and games. Playlists have a ACF repeater field, that contain games.
My goal: I am trying to build a simple query, that gets all playlists that DO NOT have this game listed in the repeater field AND alll playlists that are empty (do not have any repeater fields / database entries at all). I want to use the get_posts() function to achieve my goal, if possible.
My problem: I can only get the query to show ONLY the playlists that HAVE the game by using the = operator in the "game_filter" meta query compare field (but I want the opposite). If I choose the != or <> or IS NOT operator, it spits out all available posts. And if I also include my "empty_playlists" meta query, it ALWAYS shows me all playlists, no matter what operators I use, even tho it is a OR and the listed posts do have entrys.
I tried a lot but just cant get it to work. All exmaples I found were about different things, mostly about the % problem with the repeater field names and its solution. I hope someone can help me with this real quick. What am I doing wrong? Please help fellow and better coders! :-)
This is my code
$excluded_playlists_game_id = 274;
$user_id = 1;
$args = array(
'post_type' => PSiCorePostTypes::PLAYLISTS,
'author' => $user_id,
'order_by' => 'title',
'order' => 'ASC',
'suppress_filters' => false,
'meta_query' => array(
'relation' => 'OR',
'game_filter' => array(
'type' => 'NUMERIC',
'key' => 'psi_playlist_games_%_item',
'compare' => '!=',
'value' => $excluded_playlists_game_id,
),
'empty_playlists' => array(
'key' => 'psi_playlist_games_%_item',
'compare' => 'NOT EXISTS',
),
),
);
$user_playlists = get_posts( $args );
and this function for the % problem.
add_filter( 'posts_where', 'get_user_playlists_query_allow_wildcard' );
function get_user_playlists_query_allow_wildcard( $where ) {
global $wpdb;
$where = str_replace(
"meta_key = 'psi_playlist_games_%_item",
"meta_key LIKE 'psi_playlist_games_%_item",
$wpdb->remove_placeholder_escape( $where )
);
return $where;
}

Related

How to solve more complex wp_query meta_query with WP title

If I were using a SQL statement, this is what it would look like:
select * from courses where active = 1 AND (title = $string OR description = $string)
'courses' is a custom post type
'active' is a custom field created with ACF
'description' is a custom field created with ACF
'title' is the standard WP title for posts
This is what I have so far:
$args_courses = array(
'post_type' => 'courses',
'posts_per_page' => 9999,
'meta_query' => array(
array(
'key' => 'active',
'value' => 1,
'compare' => '='
),
'relation' => 'AND',
array(
'key' => 'description',
'value' => $string,
'compare' => '='
),
'relation' => 'OR',
array(
'key' => 'title',
'value' => $string,
'compare' => '='
)
),
);
It seems as though I can't access the WP title through a meta_query, but can't figure out how to turn my query around so 'active' remains the first column to check on.
Thanks in advance for any help!
What you are trying to do is not supported by the default WP_Query functionality, however you can extend it as described in the following answer from the WordPress Stackexchange site. Note that you you may need to alter it further for your specific use case.
https://wordpress.stackexchange.com/questions/178484/wp-query-args-title-or-meta-value
A few notes on your current query.
relation should only be used once in your meta_query. WordPress uses arrays to do the grouping, and wouldn't know to use "AND" or "OR" in your example.
It looks like you want "all" posts, not really 9999 posts, so you should use -1 to fetch everything in one result set.

WordPress Filtering Custom Post Type by Metadata and Search Heirachy

I have a custom Post Type called cptMovie which has two Custom Field (Metadata) "Language" and "Box office" assigned to it.
I also have a Custom Taxonomy called movieTax with 4 tax Terms "Action", "Comedy", "Family" ,and "Horror" .
In a WordPress File structe like below:
index.php
page.php
page-movie.php
taxonomy.php
taxonomy-movieTax.php
I am able to list and display all the Custom Post Types (movie) under Taxonomy (movieTax) using indexed page page-movie.php and custom wp-query and eventually link them to the taxonomy-movieTax.php to have all Post under each tax terms.
Up to here I am Ok and getting the resut BUT I need to add some Filters for users like Filtering the result by Language or Boc office Metadata,
Now my question is Which Part of the WP Template Hierarchy is in charge of displaying the result? Do I have to create the search.php if so how a query like this:
$arg = array(
'meta_query' => array(
array(
'key' => 'language',
'value' => 'english'
)
)
);
$filter-lang = new WP_Query( $arg );
in page-movie.php will end up in search.php?
Thanks,
You don't need to create a "search.php" file.
You can add the below code in "page-movie.php" or "taxonomy-movieTax.php" file, as per your requirement.
$arg = array(
'meta_query' => array(
'relation' => 'OR',
array(
'key' => 'language',
'value' => $lang_val,
'compare' => '='
),
array(
'key' => 'box_office',
'value' => $box_office_val,
'compare' => '='
)
)
);
$filter-lang = new WP_Query( $arg );
Here, $lang_val & $box_office_val are values to search for language key & box office key respectively. However, if you want to create separate search file, you also need to specify post type in arguments.

CakePHP - trying to order posts by relevance to tags shared with main post and date created

I'm trying to display eight of the most relevant posts, in order of relevance, based on tags shared with the current post being viewed and the date created...
Models:
Tag habtm Post
Post habtm Tag
DB:
posts(id, slug, ...)
tags(id, tag, ...)
posts_tags(post_id, tag_id)
Within controller action:
$post = $this->Post->find('first', array('conditions' => array('slug' => $slug)));
$this->set('post', $post);
$tags = $post['Tag'];
$relOrd = '';
foreach($tags as $tag){
$tagId = $tag['id'];
$relOrd .= " + (CASE WHEN PostsTag.tag_id = ".$tagId." THEN 1 ELSE 0 END)";
}
$relOrd = '(' . substr($relOrd, 3) . ') AS Relevance';
$morePosts = $this->Post->find('all', array(
'joins' => array(
array(
'table' => 'posts_tags',
'alias' => 'PostsTag',
'type' => 'LEFT',
'conditions' => array(
'PostsTag.post_id = Post.id',
)
)
),
'group' => 'Post.id',
'fields' => array($relOrd, 'Post.*'),
'order' => array('Relevance' => 'DESC', 'Post.created' => 'DESC'),
'limit' => 8,
));
$this->log($morePosts);
$this->set('morePosts', $morePosts);
It's almost working, although the relevance value is being treated as if each post has only one tag (being only 0 or 1). So it seems that the relevance value for each post is taking either 0 or 1 depending on the posts LAST tag rather than being accumulative based on ALL tags.
First of all, I'd take all of the logic out of the controller. Consider this:
$post = $this->Post->find('first', array('conditions' => array('slug' => $slug)));
$this->set('post', $post);
$this->set('morePosts', $this->Post->findRelevant($post));
Now your controller is easy to read and does it's job. You essentially start by describing what data you want by naming an imaginary model function, and then you write the model code to fulfill that request.
So here's a stab at the model code:
var $actsAs = array('Containable');
function findRelevant($post, $limit = 8) {
// create an array of ids of the tags from this post
$tags = array();
foreach($post['Tag'] as $num => $tag) {
$tags[$tag['id']] = $tag['id'];
}
// find other posts that have any of those tags
$relevant = $this->find('all', array(
'conditions' => array('Post.id <>' => $post['Post']['id']),
'order' => 'Post.created desc',
'contain' => array('Tag' => array('conditions' => array(
'Tag.id' => $tags
))),
));
// count the number of tags of each post and call it relevance
// (this number is essentially the number of tags in common
// with the original post because we used contain to get only
// the tags from the original post)
foreach($relevant as &$p) {
$p['Post']['relevance'] = count($p['Tag']);
}
// sort by relevance
$relevant = Set::sort($relevant, '{n}.Post.relevance', 'desc');
// limit the number of posts returned (defaults to 8)
return array_splice($relevant, 0, $limit);
}
Obviously it would be great to use the database logic to fetch the records (as you are attempting to do) so that it's as fast as possible and so that you minimize the amount of data that you retrieve, but I can't see how to do that for what you're trying to achieve.
This method should work fine and is not database specific. :)

Retrieve all data from one table, and some from another in CakePHP

In my CakePHP site, I want to make a drop-down list of all Venues, and any Restaurants that have is_venue=1.
I've tried this in my events_controller:
$venueOptions = array(
'fields' => array('id', 'name_address'),
'order' => array('name'),
'join' => array(
array(
'table' => 'restaurants',
'alias' => 'Restaurants',
'type' => 'inner',
'fields' => array('id', 'name'),
'foreignKey' => false,
'conditions' => array('restaurants.is_venue = 1')
)
),
);
$venues = $this->Event->Venue->find('list', $venueOptions);
But it appears to still just be getting the venues. I don't really need an association between the two, since their associations will both be with an event, not each other.
Where have I gone wrong? Am I close, but just need to tweak this code, or am I just all-together doing it wrong?
I think you could do something along the lines of:
<?php
....
$v = $this->Venue->find( 'list' );
$r = $this->Restaurant->find( 'list' );
$venues = Set::merge( $v, $r );
natcasesort( $venues );
// print_r( $venues );
$this->set( 'venues', $venues );
...
?>
Which is quite like the code above - I just use the Set class and make sure to Controller::set the variable to the view.
Also added some basic sorting to show you one option even though array sorting has nothing really specific to do with CakePHP.
Also fixed some bad variable names where I had originally used $venues, and $restaurants - changed to be consistently $v and $r.
Join will not work if there's no relation between. Venue and Restaurant. You should call them separately and merge the results
$venues = $this->Event->Venue->find('list', $venueOptions);
$restaurants = $this->Event->Restaurant->find('list', array('conditions' => array('is_venue' => '1')));
$results = array_merge($venues, $restaurants);
// sort results
asort($results);

Cakephp model associastion

I have thee following simple model:
Item belongsTo CatalogItem
CatalogItem hasMany Item, and belongsTo Section
Section hasMany CatalogItem
I'm trying to get counts of items, grouped by catalogitem, for a certain section-
the equivalent of:
SELECT catalogitem.id, count(*) FROM section LEFT JOIN catalogitem ON section.id=catalogitem.section_id LEFT JOIN item ON item.catalogitem_id=catalogitem.id WHERE section.id=5 GROUP BY catalogitem.id
So simple in sql, yet I can't get it to work with cake models. Can anyone point as to how to do it with cake models, using the model->find?
I can't get it to group by correctly or join correctly on 3 tables :(
Edit:
highly prefer to get the info in single query
Here's a longer way, "cakeish" way:
class Item extends AppModel
{
/* snip */
var $virtualFields = array('item_count' => 'count(Item.id)');
function getCountForSection($sectionId)
{
$ca = $this->Catalogitem->find
(
'all',
array
(
'fields' => array('Catalogitem.id'),
'conditions' => array('Catalogitem.section_id' => $sectionId),
'recursive' => -1
)
);
$ca = Set::extract('/Catalogitem/id', $ca);
$ret = $this->find
(
'all',
array
(
'fields' => array('Item.catalogitem_id', 'item_count'),
'conditions' => array('Item.catalogitem_id' => $ca),
'group' => array('Item.catalogitem_id'),
'recursive' => -1
)
);
return $ret;
}
}
Then simply use it in your controller:
$ret = $this->Item->getCountForSection(1);
debug($ret);
How does it work:
Define a virtual field (cake 1.3+ only AFAIK) which will count items
Fetch all the Catalogitems belonging to a Section you're interested in
Use Set::extract() to get the Catalogitems in a simple array
Use the array of Catalogitems to filter Items while counting and grouping them
NB: You don't seem to be using Cake's naming conventions in your database. This may hurt you.
Sorry, in my first answer I somehow missed your GROUP BY requirement, which was the whole point of the question, I now realize. I haven't used this yet, but I came across it recently, and it looks like it might accomplish what you are looking for: Linkable Behavior.
http://planetcakephp.org/aggregator/items/891-linkable-behavior-taking-it-easy-in-your-db
Like Containable, but works with only right and left joins, produces much more compact queries and supports GROUP BY.
http://github.com/rafaelbandeira3/linkable
#azv
Would this work for you:
$section_id = 5;
$fields = array('CatalogItem.id as CatalogItemId', 'count(*) AS SectionCount');
$conditions = array('Section.id' => $section_id);
$joins = array(
array('table' => 'catalogitem',
'alias' => 'CatalogItem',
'type' => 'LEFT',
'conditions' => array('Section.id' => 'CatalogItem.section_id')
),
array('table' => 'item',
'alias' => 'Item',
'type' => 'LEFT',
'conditions' => array('Item.catalogitem_id' => 'CatalogItem.id')
));
$data = $this->Section->find('all',
array('fields' => $fields,
'conditions' => $conditions,
'joins' => $joins,
'group' => 'CatalogItem.id',
'recursive' => -1)
);
// access your data values
foreach ($data['Section'] as $i => $datarow) {
$catalogitem_id = $datarow['CatalogItemId'];
$section_count = $datarow['SectionCount'];
}
This way you are explicitly setting your joins and doing it all in one query. See here for more info on joins in Cake:
http://book.cakephp.org/view/1047/Joining-tables
Hope this helps. All the best,
-s_r