Recently, while helping @lisarex on a project with some views, location & gmap goodness, at some point there were many problems with the taxonomy terms, and led to this one little awesome snippet of code.
There were a long line of taxonomy issues, but most of those were sorted out, or alternative ways found to ignore them. The scenario for the issue is that we had a great view set up that served multiple purposes. The primary page view though, simply rendered a gmap of listings filtered by State, then City using views arguments. There was also an attachment view that was used with the page view to render out summary lists when the 2 arguments were empty, listing the available State/City locations, how many nodes were below that, and allowing quick filtering. The nodes on the map were set to link directly to that listing.
One last piece was needed, and in this case, it was a list of taxonomy terms that were associated with a node referenced to the listing using the node_reference CCK type. Using views relationships, this was easy enough to accomplish, but 2 horrible things became apparent:
- Duplicate terms were created
- Sorting of the terms was "random", based on the order the nodes were presented
If you search for this term on Drupal.org, there are quite a few listings on this similar topic, and all the "geniuses" that actually responded said "Oh, just use a taxonomy view, not a node view". Well, that is WRONG for 99% of the issues that were posted. The need here is to provide a list of the taxonomy terms associated with ONLY the nodes being presented. Not to just dump a full list of terms. This use case I've implemented was in a block view that takes the same arguments as the page view does, which is the state/city pair. The block only appears if both arguments are present, and we are filtered down to our most specific locations
This following code solves both of the above issues, WHILE still using a node view. It implements hook_views_pre_render to manipulate the $view->result data, and give us what we actually expected.
/**
* Implementation of hook_views_pre_render
* This function is in place to filter out duplicate taxonomy terms
* From listings. It will cycle each result, and store a new array of
* unique terms, and when a duplicate is found, will unset that result
*
* @param $view
*/
function your_module_views_pre_render($view){
// first we make sure we are dealing with the correct view
if ($view->name == "your_view_name") {
// create our array for comparisons
$unique_tids = array();
// let's cycle through each default result, and do some dirty stuff
foreach($view->result AS $k => $result){
if(in_array($result->node_node_data_YOUR_FIELD__term_data_tid, $unique_tids) || !$result->node_node_data_YOUR_FIELD__term_data_tid) {
/** we already have seen this TID in the results, so blow that crap away
* also will blow away any that are empty for some odd reason */
unset($view->result[$k]);
}
else {
// this is a term we haven't seen, so let's not blow it away, but add
// it to our array of unique id's to present as the "true" result of this view
$unique_tids[$k] = $result->node_node_data_YOUR_FIELD__term_data_tid;
}
}
/** now, we have an accurate unique list of terms in $unique_tids
* next, we cycle those to reorder the crap random ordering
* since these tids were pulled from the nodes in the order they were
* set to sort from the node type view */
$alpha_arr = array();
// cycle each of our unique tids, referencing the original key of the view->result array ($k)
foreach($unique_tids AS $k => $tid) {
// we need to grab the term now, not the tid to sort alpha
$alpha_arr[$k] = strtolower($view->result[$k]->node_node_data_YOUR_FIELD__term_data_name);
}
// sort the array, maintaining the $k key so that we may again reference back to the original data
asort($alpha_arr);
// create new array of results to overwrite the current one
$new_results = array();
/** cycle one last time now that we have unique terms, sorted alphabetically
the point of this is to now take our $k reference, and grab the original $view->result data
that references this item */
foreach($alpha_arr AS $v => $term_name) {
$new_results[] = $view->result[$v];
}
// get rid of the original result set
unset($view->result);
// replace it with our new, accurate result set
$view->result = $new_results;
}
}
?>
This may or may not be the best solution for this issue, but the code is using the following logic:
- First, grab the $view->result array, and cycle through it, checking for a TID that has not been seen yet, adding it a custom array that has a key of the original key of the specific $view->result item.
- Any items that are already in our array of unique items that is found again, we unset that item in the $view->result array
- After our cycle is done, two more cycles are performed
- First, we have to continue to preserve our $k which references the original $view->result item, and we assign the term name as the value of the array. Once that is complete, we can use asort() to resort this array by alphabetical order, and preserve our key.
- Lastly we loop through the array one last time using our keys to create an array with the original view->result item in the array in the order we have determined was appropriate
- Finally, we unset the original $view->result array, and assign our modified version as what we want to use for this view
And whoosh, the term list view is now only showing unique terms in alphabetical order. </hotsauce>


SWEET
Sweet, this looks to be just what I need!
Could you help out?
I'm trying to get a list of distinct terms of all nodes of a certain content type. The terms link to their (customized) term pages, for example allowing a listing of all "articles" (content type) about "disability" (term).
Right now the list contains duplicates as the view simply lists the terms of every node that passes the filtercriteria (being content type=article and published=yes). I can't use taxonomy views as those do not allow you to filter content types.
Your snippet seems to do want I'm looking for, but it somehow filters out all terms (no listing at all).
My view has three fields:
Taxonomy: Vocabulary name [name_1], excluded - used for grouping.
Taxonomy: Term ID [tid], excluded - used for linking to the term page (need the [tid] replacement pattern)
Taxonomy: Term [name], displayed - link customized
I pasted your snippet in my site specific custom module, changed the view name and subsituted YOUR_FIELD_ with "name". (for example: $result->node_node_data_name_term_data_tid)
Do you have an idea what I may have done wrong or what a possible solution may be?
double underscore...
Look closely at the example...
There is a double underscore ( __ ) between your field name and the last portion... Try that first, and let me know!
Jake Strawn
Drupal Rockstar
Wow! Thanks for the quick
Wow! Thanks for the quick reply! :)
I had noticed the double underscore but because that didn't seem to work I figured it was either a (highly consistent) typo or your placeholder was called YOUR_FIELD_ . :P
Nonetheless I tried again with a double underscore this time... but it didn't work. I also tried using the two other fields with a double underscore, just to see if that would change anything. When that didn't work I removed the two fields which are just in there for grouping and url construction. I wanted to see whether having more fields messed up the script somehow... no change. I also tried removing the first 'node_' as I have seen quite a few scripts using node_data_ ... but that obviously didn't change anything.
As you can tell I'm not a PHP coder, although your script does make sense to me. I still feel it has to do with picking the correct name pattern.
This is the query of the view:
SELECT node.nid AS nid,vocabulary.name AS vocabulary_name,term_data.tid AS term_data_tid,term_data.name AS term_data_name,term_data.vid AS term_data_vidFROM drupal_node node LEFT JOIN drupal_term_node term_node ON node.vid = term_node.vidLEFT JOIN drupal_term_data term_data ON term_node.tid = term_data.tidLEFT JOIN drupal_vocabulary vocabulary ON term_data.vid = vocabulary.vidWHERE (node.type in ('article')) AND (node.status <> 0)ORDER BY term_data_name ASC
I exported the view to a file: www.richardvankooten.nl/webspace/view.txt
Thanks again!
Okay, a few things to check...
Okay, not sure... the view export, at a quick glance, looks fine...
Make sure in the upper portion of this function the line
is replaced with:
and from what I can see of your example, you should be right, making sure that any references to node_node_data_YOUR_FIELD__term_data_name become node_node_data_name__term_data_name and node_node_data_YOUR_FIELD__term_data_tid become node_node_data_name__term_data_tid
That seems like that should be all the adjustments....
If you continue to have issues after those are checked... make sure your filters (node type & status) are actually returning true... that there are in fact article nodes that ARE published... (silly I know, but who knows!!)
If you continue to have trouble, I'll see if I can set up the exported view on a sandbox and play around for a minute. I will be pressed for time however between now & Friday when I leave for Drupalcon.
Good Luck!
Jake Strawn
Drupal Rockstar
One other thing...
Also... another thing I like to do when I'm not sure things are running right... potentially right after the line:
put something like
I do that a lot when developing something to just "make sure" that the code I expect to run is at least getting looked at...
Jake Strawn
Drupal Rockstar
Eureka! I did some more
Eureka!
I did some more debugging and played around with:
after
As that didn't return any results I dropped all the
prefixes and that did the trick. :)
Apparently
and
is all we needed.
Thanks for getting me on track! :D
Awesome!
Nice work... that was going to be my last thing was suggest dumping the $views varible out, and ensuring how the results were named...
Glad it works now!
Jake Strawn
Drupal Rockstar
Added to Drupal's documentation
http://drupal.org/node/770782
Hope you don't mind. :)
Post new comment