| 1 |
<?php
|
| 2 |
// $Id: taxtreenodes.module,v 1.1 2007/12/07 09:24:53 attiks Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Original author: Peter Droogmans (Attiks)
|
| 7 |
* Inspired by site_map module
|
| 8 |
*/
|
| 9 |
|
| 10 |
/**
|
| 11 |
* Implementation of hook_help().
|
| 12 |
*/
|
| 13 |
function taxtreenodes_help($section) {
|
| 14 |
switch ($section) {
|
| 15 |
case 'taxtreenodes':
|
| 16 |
$output = '<p>'. t('This module displays a hierarchical tree of a taxonomy including all nodes.') .'</p>';
|
| 17 |
$output = '<p>'. t('You can show/hide the number of items under each term.') .'</p>';
|
| 18 |
$output = '<p>'. t('You can make the whole tree jquery collapsible.') .'</p>';
|
| 19 |
}
|
| 20 |
}
|
| 21 |
|
| 22 |
/**
|
| 23 |
* Implementation of hook_perm().
|
| 24 |
*/
|
| 25 |
function taxtreenodes_perm() {
|
| 26 |
return array('access taxtreenodes');
|
| 27 |
}
|
| 28 |
|
| 29 |
/**
|
| 30 |
* Implementation of hook_settings().
|
| 31 |
*/
|
| 32 |
function taxtreenodes_admin_settings() {
|
| 33 |
|
| 34 |
$vocab_options = array();
|
| 35 |
if (module_exists('taxonomy')) {
|
| 36 |
foreach (taxonomy_get_vocabularies() as $vocabulary) {
|
| 37 |
$vocab_options[$vocabulary->vid] = $vocabulary->name;
|
| 38 |
}
|
| 39 |
$vocab_options[''] = t('(none)');
|
| 40 |
}
|
| 41 |
|
| 42 |
$nodetypes = node_get_types('names');
|
| 43 |
$nodetypes[''] = t('(none)');
|
| 44 |
|
| 45 |
$form['taxtreenodes_options']['taxtreenodes_show_count'] = array(
|
| 46 |
'#type' => 'checkbox',
|
| 47 |
'#title' => t('Show node counts by categories (exclude using list below)'),
|
| 48 |
'#return_value' => 1,
|
| 49 |
'#default_value' => variable_get('taxtreenodes_show_count', 0),
|
| 50 |
'#description' => t('When enabled, this option will show the number of nodes in each taxonomy term.'),
|
| 51 |
);
|
| 52 |
|
| 53 |
$form['taxtreenodes_options']['taxtreenodes_show_count_exclude'] = array(
|
| 54 |
'#type' => 'select',
|
| 55 |
'#title' => t('Categories to exclude from default behaviour'),
|
| 56 |
'#default_value' => variable_get('taxtreenodes_show_count_exclude', array()),
|
| 57 |
'#options' => $vocab_options,
|
| 58 |
'#multiple' => TRUE,
|
| 59 |
'#description' => t('Ctrl-click (Windows) or Command-click (Mac) to select more than one value.'),
|
| 60 |
);
|
| 61 |
|
| 62 |
$form['taxtreenodes_options']['taxtreenodes_collapsible'] = array(
|
| 63 |
'#type' => 'checkbox',
|
| 64 |
'#title' => t('Make the tree collapsible using jQuery (exclude using list below)'),
|
| 65 |
'#return_value' => 1,
|
| 66 |
'#default_value' => variable_get('taxtreenodes_collapsible', 0),
|
| 67 |
'#description' => t('When enabled, the tree will be displayed collapsed, all terms will toggle the display of their childs.'),
|
| 68 |
);
|
| 69 |
|
| 70 |
$form['taxtreenodes_options']['taxtreenodes_collapsible_exclude'] = array(
|
| 71 |
'#type' => 'select',
|
| 72 |
'#title' => t('Categories to exclude from default behaviour'),
|
| 73 |
'#default_value' => variable_get('taxtreenodes_collapsible_exclude', array()),
|
| 74 |
'#options' => $vocab_options,
|
| 75 |
'#multiple' => TRUE,
|
| 76 |
'#description' => t('Ctrl-click (Windows) or Command-click (Mac) to select more than one value.'),
|
| 77 |
);
|
| 78 |
|
| 79 |
$form['taxtreenodes_options']['taxtreenodes_nodetypes_to_exclude'] = array(
|
| 80 |
'#type' => 'select',
|
| 81 |
'#title' => t('Node types to exclude from the tree'),
|
| 82 |
'#default_value' => variable_get('taxtreenodes_nodetypes_to_exclude', array()),
|
| 83 |
'#options' => $nodetypes,
|
| 84 |
'#multiple' => TRUE,
|
| 85 |
'#description' => t('Ctrl-click (Windows) or Command-click (Mac) to select more than one value.'),
|
| 86 |
);
|
| 87 |
|
| 88 |
return system_settings_form($form);
|
| 89 |
}
|
| 90 |
|
| 91 |
/**
|
| 92 |
* Implementation of hook_menu().
|
| 93 |
*/
|
| 94 |
function taxtreenodes_menu($may_cache) {
|
| 95 |
$items = array();
|
| 96 |
|
| 97 |
if ($may_cache) {
|
| 98 |
$items[] = array(
|
| 99 |
'path' => 'admin/settings/taxtreenodes',
|
| 100 |
'title' => t('Tax Tree Nodes'),
|
| 101 |
'description' => t('Settings.'),
|
| 102 |
'callback' => 'drupal_get_form',
|
| 103 |
'callback arguments' => 'taxtreenodes_admin_settings',
|
| 104 |
'access' => user_access('administer site configuration'),
|
| 105 |
'type' => MENU_NORMAL_ITEM
|
| 106 |
);
|
| 107 |
$items[] = array(
|
| 108 |
'path' => 'taxtreenodes',
|
| 109 |
'title' => t('Tax Tree Nodes'),
|
| 110 |
'description' => t('Display a tax tree node.'),
|
| 111 |
'callback' => 'taxtreenodes_page',
|
| 112 |
'access' => user_access('access taxtreenodes'),
|
| 113 |
'type' => MENU_SUGGESTED_ITEM
|
| 114 |
);
|
| 115 |
}
|
| 116 |
|
| 117 |
return $items;
|
| 118 |
}
|
| 119 |
|
| 120 |
/**
|
| 121 |
* Menu callback for the Tax Tree Node.
|
| 122 |
*/
|
| 123 |
function taxtreenodes_page($vid = 0) {
|
| 124 |
|
| 125 |
if (!is_numeric($vid)) {
|
| 126 |
return drupal_not_found();
|
| 127 |
}
|
| 128 |
|
| 129 |
drupal_add_css(drupal_get_path('module', 'taxtreenodes') .'/taxtreenodes.css');
|
| 130 |
|
| 131 |
global $_taxtreenodes_collapsible_;
|
| 132 |
$_taxtreenodes_collapsible_ = (variable_get('taxtreenodes_collapsible', 0) && !array_key_exists($vid, variable_get('taxtreenodes_collapsible_exclude', array())))
|
| 133 |
|| (!variable_get('taxtreenodes_collapsible', 0) && array_key_exists($vid, variable_get('taxtreenodes_collapsible_exclude', array())));
|
| 134 |
|
| 135 |
global $_taxtreenodes_show_count_;
|
| 136 |
$_taxtreenodes_show_count_ = (variable_get('taxtreenodes_show_count', 0) && !array_key_exists($vid, variable_get('taxtreenodes_show_count_exclude', array())))
|
| 137 |
|| (!variable_get('taxtreenodes_show_count', 0) && array_key_exists($vid, variable_get('taxtreenodes_show_count_exclude', array())));
|
| 138 |
|
| 139 |
if ($_taxtreenodes_collapsible_) {
|
| 140 |
jquery_interface_add();
|
| 141 |
drupal_add_js(drupal_get_path('module', 'taxtreenodes') .'/taxtreenodes.js');
|
| 142 |
}
|
| 143 |
|
| 144 |
$output = '';
|
| 145 |
$output = 'vid='.$vid;
|
| 146 |
|
| 147 |
$result = db_query('SELECT vid, name, description FROM {vocabulary} WHERE vid IN (\'%s\') ORDER BY weight ASC, name', $vid);
|
| 148 |
while ($t = db_fetch_object($result)) {
|
| 149 |
$output .= _taxtreenodes_taxonomy_tree($t->vid, $t->name, $t->description);
|
| 150 |
}
|
| 151 |
|
| 152 |
$output = '<div id="taxtreenodes_'. $vid .'" class="taxtreenodes">'. $output .'</div>';
|
| 153 |
|
| 154 |
return $output;
|
| 155 |
}
|
| 156 |
|
| 157 |
/**
|
| 158 |
* Render taxonomy tree
|
| 159 |
*
|
| 160 |
* @param $tree The results of taxonomy_get_tree() with optional 'count' fields.
|
| 161 |
* @param $name An optional name for the tree. (Default: NULL)
|
| 162 |
* @param $description An optional description of the tree. (Default: NULL)
|
| 163 |
* @return A string representing a rendered tree.
|
| 164 |
*/
|
| 165 |
function _taxtreenodes_taxonomy_tree($vid, $name = NULL, $description = NULL) {
|
| 166 |
|
| 167 |
$title = $name ? check_plain($name) : '';
|
| 168 |
|
| 169 |
$last_depth = -1;
|
| 170 |
|
| 171 |
$cat_depth = variable_get('taxtreenodes_categories_depth', 'all');
|
| 172 |
if (!is_numeric($cat_depth)) {
|
| 173 |
$cat_depth = 'all';
|
| 174 |
}
|
| 175 |
|
| 176 |
$output = $description ? '<div class="description">'. check_plain($description) .'</div>' : '';
|
| 177 |
|
| 178 |
$output .= '<div class="tree">'."\n";
|
| 179 |
// taxonomy_get_tree() honors access controls
|
| 180 |
$tree = taxonomy_get_tree($vid);
|
| 181 |
|
| 182 |
$nodestoexclude = variable_get('taxtreenodes_nodetypes_to_exclude', 0);
|
| 183 |
if ($nodestoexclude != 0) {
|
| 184 |
$nodestoexclude = "'" . implode ("','", $nodestoexclude) . "'";
|
| 185 |
}
|
| 186 |
|
| 187 |
// Load all the nodes so we can hide the empty ones
|
| 188 |
foreach ($tree as $term) {
|
| 189 |
|
| 190 |
$term->count = taxtreenodes_taxonomy_term_count_nodes($term->tid, $nodestoexclude);
|
| 191 |
|
| 192 |
if ($term->count) {
|
| 193 |
|
| 194 |
// Adjust the depth of the <ul> based on the change
|
| 195 |
// in $term->depth since the $last_depth.
|
| 196 |
if ($last_depth == -1) {
|
| 197 |
$output .= '<ul class="depth_'. $term->depth .'">'."\n";
|
| 198 |
}
|
| 199 |
else if ($term->depth > $last_depth) {
|
| 200 |
for ($i = 0; $i < ($term->depth - $last_depth); $i++) {
|
| 201 |
$output .= '<ul class="depth_'. $term->depth .'">'."\n";
|
| 202 |
}
|
| 203 |
}
|
| 204 |
else if ($term->depth < $last_depth) {
|
| 205 |
for ($i = 0; $i < ($last_depth - $term->depth); $i++) {
|
| 206 |
$output .= '</li>'."\n";
|
| 207 |
$output .= '</ul>'."\n";
|
| 208 |
$output .= '</li>'."\n";
|
| 209 |
}
|
| 210 |
}
|
| 211 |
|
| 212 |
$output .= '<li class="term">';
|
| 213 |
$output .= theme('taxtreenodes_term', $term, $cat_depth);
|
| 214 |
$term->nodes = taxtreenodes_taxonomy_select_nodes(array($term->tid), 'or', 0, FALSE, 'n.sticky DESC, n.title ASC', $nodestoexclude);
|
| 215 |
if (db_num_rows($term->nodes)) {
|
| 216 |
$output .= theme('taxtreenodes_nodes', $term->nodes, $cat_depth);
|
| 217 |
}
|
| 218 |
|
| 219 |
// Reset $last_depth in preparation for the next $term.
|
| 220 |
$last_depth = $term->depth;
|
| 221 |
}
|
| 222 |
}
|
| 223 |
|
| 224 |
// Close last term
|
| 225 |
$output .= '</li>'."\n";
|
| 226 |
|
| 227 |
// Bring the depth back to where it began, -1.
|
| 228 |
if ($last_depth > -1) {
|
| 229 |
for ($i = 0; $i < ($last_depth + 1); $i++) {
|
| 230 |
$output .= '</ul>'."\n";
|
| 231 |
if ($last_depth + 1 - $i > 1) {
|
| 232 |
$output .= '</li>'."\n";
|
| 233 |
}
|
| 234 |
}
|
| 235 |
}
|
| 236 |
$output .= "</div>\n";
|
| 237 |
$output = theme('box', $title, $output);
|
| 238 |
|
| 239 |
return $output;
|
| 240 |
}
|
| 241 |
|
| 242 |
/**
|
| 243 |
* Theme functions.
|
| 244 |
*/
|
| 245 |
|
| 246 |
function theme_taxtreenodes_nodes($nodes, $cat_depth) {
|
| 247 |
|
| 248 |
$output = '';
|
| 249 |
|
| 250 |
$output .= '<ul class="nodelist">';
|
| 251 |
while ($node = db_fetch_object($nodes)) {
|
| 252 |
$output .= '<li class="node-type-'. $node->type .'">'. theme('taxtreenodes_node', $node, $cat_depth) .'</li>';
|
| 253 |
}
|
| 254 |
$output .= '</ul>'."\n";
|
| 255 |
|
| 256 |
return $output;
|
| 257 |
|
| 258 |
}
|
| 259 |
|
| 260 |
function theme_taxtreenodes_node($node) {
|
| 261 |
return l($node->title, drupal_get_path_alias('node/'. $node->nid), array('title' => $node->title, 'class' => 'linktonode'));
|
| 262 |
}
|
| 263 |
|
| 264 |
function theme_taxtreenodes_term($term, $cat_depth) {
|
| 265 |
|
| 266 |
$output = '';
|
| 267 |
|
| 268 |
global $_taxtreenodes_collapsible_;
|
| 269 |
global $_taxtreenodes_show_count_;
|
| 270 |
|
| 271 |
if ($_taxtreenodes_collapsible_) {
|
| 272 |
|
| 273 |
if ($term->count) {
|
| 274 |
$output .= '<a href="#" class="taxtreenodestoggle">'. check_plain($term->name);
|
| 275 |
|
| 276 |
if ($_taxtreenodes_show_count_) {
|
| 277 |
$output .= " ($term->count)";
|
| 278 |
}
|
| 279 |
|
| 280 |
$output .= '</a>';
|
| 281 |
|
| 282 |
}
|
| 283 |
else {
|
| 284 |
$output .= check_plain($term->name);
|
| 285 |
}
|
| 286 |
|
| 287 |
}
|
| 288 |
else {
|
| 289 |
|
| 290 |
if ($term->count) {
|
| 291 |
|
| 292 |
if ($cat_depth < 0) {
|
| 293 |
$output .= l($term->name, taxonomy_term_path($term), array('title' => $term->description));
|
| 294 |
}
|
| 295 |
else {
|
| 296 |
$output .= l($term->name, "taxonomy/term/$term->tid/$cat_depth", array('title' => $term->description));
|
| 297 |
}
|
| 298 |
|
| 299 |
if ($_taxtreenodes_show_count_) {
|
| 300 |
$output .= " ($term->count)";
|
| 301 |
}
|
| 302 |
|
| 303 |
}
|
| 304 |
else {
|
| 305 |
$output .= check_plain($term->name);
|
| 306 |
}
|
| 307 |
|
| 308 |
}
|
| 309 |
|
| 310 |
return $output;
|
| 311 |
}
|
| 312 |
|
| 313 |
/*
|
| 314 |
* Copy of taxonomy_term_count_nodes with support to exclude node types
|
| 315 |
* !!! Plain old concat of the NOT IN statement, otherwise the ' is escaped !!!!!
|
| 316 |
*/
|
| 317 |
|
| 318 |
function taxtreenodes_taxonomy_term_count_nodes($tid, $type = 0) {
|
| 319 |
static $count;
|
| 320 |
|
| 321 |
if (!isset($count[$type])) {
|
| 322 |
// $type == 0 always evaluates TRUE if $type is a string
|
| 323 |
if (is_numeric($type)) {
|
| 324 |
$result = db_query(db_rewrite_sql('SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 GROUP BY t.tid'));
|
| 325 |
}
|
| 326 |
else {
|
| 327 |
$result = db_query(db_rewrite_sql("SELECT t.tid, COUNT(n.nid) AS c FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 AND n.type not in ($type) GROUP BY t.tid"));
|
| 328 |
}
|
| 329 |
while ($term = db_fetch_object($result)) {
|
| 330 |
$count[$type][$term->tid] = $term->c;
|
| 331 |
}
|
| 332 |
}
|
| 333 |
|
| 334 |
foreach (_taxonomy_term_children($tid) as $c) {
|
| 335 |
$children_count += taxtreenodes_taxonomy_term_count_nodes($c, $type);
|
| 336 |
}
|
| 337 |
return $count[$type][$tid] + $children_count;
|
| 338 |
}
|
| 339 |
|
| 340 |
/*
|
| 341 |
* Copy of taxonomy_select_nodes with support to exclude node types
|
| 342 |
* Removed parts we don't need
|
| 343 |
*/
|
| 344 |
|
| 345 |
function taxtreenodes_taxonomy_select_nodes($tids = array(), $operator = 'or', $depth = 0, $pager = TRUE, $order = 'n.sticky DESC, n.created DESC', $type = 0) {
|
| 346 |
if (count($tids) > 0) {
|
| 347 |
// For each term ID, generate an array of descendant term IDs to the right depth.
|
| 348 |
$descendant_tids = array();
|
| 349 |
if ($depth === 'all') {
|
| 350 |
$depth = NULL;
|
| 351 |
}
|
| 352 |
foreach ($tids as $index => $tid) {
|
| 353 |
$term = taxonomy_get_term($tid);
|
| 354 |
$tree = taxonomy_get_tree($term->vid, $tid, -1, $depth);
|
| 355 |
$descendant_tids[] = array_merge(array($tid), array_map('_taxonomy_get_tid_from_term', $tree));
|
| 356 |
}
|
| 357 |
|
| 358 |
if ($operator == 'or') {
|
| 359 |
$args = call_user_func_array('array_merge', $descendant_tids);
|
| 360 |
$placeholders = implode(',', array_fill(0, count($args), '%d'));
|
| 361 |
|
| 362 |
if (is_numeric($type)) {
|
| 363 |
$sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created, n.type FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $placeholders .') AND n.status = 1 ORDER BY '. $order;
|
| 364 |
}
|
| 365 |
else {
|
| 366 |
$sql = 'SELECT DISTINCT(n.nid), n.sticky, n.title, n.created, n.type FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $placeholders .') AND n.type not in (' . $type . ') AND n.status = 1 ORDER BY '. $order;
|
| 367 |
}
|
| 368 |
|
| 369 |
$sql_count = 'SELECT COUNT(DISTINCT(n.nid)) FROM {node} n INNER JOIN {term_node} tn ON n.nid = tn.nid WHERE tn.tid IN ('. $placeholders .') AND n.status = 1';
|
| 370 |
}
|
| 371 |
$sql = db_rewrite_sql($sql);
|
| 372 |
$sql_count = db_rewrite_sql($sql_count);
|
| 373 |
if ($pager) {
|
| 374 |
$result = pager_query($sql, variable_get('default_nodes_main', 10), 0, $sql_count, $args);
|
| 375 |
}
|
| 376 |
else {
|
| 377 |
$result = db_query_range($sql, $args, 0, variable_get('feed_default_items', 10));
|
| 378 |
}
|
| 379 |
}
|
| 380 |
|
| 381 |
return $result;
|
| 382 |
}
|