User login

Taxonomy_term_count_nodes gives stupid results when child terms have same nodes

The Drupal core function in taxonomy.module, taxonomy_term_count_nodes, has no check in it to remove duplicate nodes, which can happen in situations where a nodde belongs to both a child and a parent. In my opinion there should be a different function taxonomy_term_and_children_count_nodes or a flag in the function to say if nodes belonging to children terms should be counted at all, and if they are, there should be proper checking to remove duplicates.

Here's the Drupal taxonomy.module original, and follows will be my hack with a flag, which doesn't do what I request above yet but does give the option to opt out of counting nodes belonging to children terms:

<?php
/**
 * Count the number of published nodes classified by a term.
 *
 * @param $tid
 *   The term's ID
 *
 * @param $type
 *   The $node->type. If given, taxonomy_term_count_nodes only counts
 *   nodes of $type that are classified with the term $tid.
 *
 * @return int
 *   An integer representing a number of nodes.
 *   Results are statically cached.
 */
function taxonomy_term_count_nodes($tid, $type = 0) {
  static $count;

  if (!isset($count[$type])) {
    // $type == 0 always evaluates TRUE if $type is a string
    if (is_numeric($type)) {
      $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'));
    }
    else {
      $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 = '%s' GROUP BY t.tid"), $type);
    }
    while ($term = db_fetch_object($result)) {
      $count[$type][$term->tid] = $term->c;
    }
  }

  foreach (_taxonomy_term_children($tid) as $c) {
    $children_count += taxonomy_term_count_nodes($c, $type);
  }
  return $count[$type][$tid] + $children_count;
}
?>

And the Agaric opt-out of counting child term nodes version:

<?php
/**
 * Count the number of published nodes classified by a term.
 *
 * @param $tid
 *   The term's ID
 *
 * @param $type
 *   The $node->type. If given, taxonomy_term_count_nodes only counts
 *   nodes of $type that are classified with the term $tid.
 *
 * @param $children
 *   Optional flag to indicate whether to count the nodes of child terms.
 *   TRUE or FALSE, defaults to TRUE.
 *
 * @return int
 *   An integer representing a number of nodes.
 *   Results are statically cached.
 */
function taxonomy_term_count_nodes($tid, $type = 0, $children = TRUE) {
  static $count;

  if (!isset($count[$children][$type][$tid])) {
    if (is_numeric($type)) {
      $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'));
    }
    else {
      $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 = '%s' GROUP BY t.tid"), $type);
    }
    while ($term = db_fetch_object($result)) {
      $count[$children][$type][$term->tid] = $term->c;
    }
  }

  if ($children) {
    foreach (_taxonomy_term_children($tid) as $c) {
      $children_count += taxonomy_term_count_nodes($c, $type);
    }   
    return $count[$children][$type][$tid] + $children_count;
  }
  else {  // best practice to use else or not?  unnecessary with return above
    return $count[$children][$type][$tid]
  }
}
?>

While doing this I think I found a bug in the static caching: it checks if $count[$type] is set, when the query that runs if it is not set, and what is ultimately returnedd, is ddependent not just on the node type but the term tid.

Resolution

Comments

As used in a contributed

As used in a contributed module, place_taxonomy, to list people (profiles) and actions (another content type) in each country:

Before:

Country/Region People Actions
Argentina 34 6
BENIN 2

After:

Country/Region People Actions
Argentina 8 1
BENIN 1

Since I always have to look

Since I always have to look it up, here's how to apply the patch:

cd /var/www/agaricroot/
sudo wget http://agaricdesign.com/sites/agaricdesign.com/files/taxonomy_term_count_nodes_children_0.patch
sudo patch -p0 < taxonomy_term_count_nodes_children_0.patch

I have better solution

This solution not needed to decide, that I want counts without child, but it's recognize duplicates.

function taxonomy_term_count_nodes($tid, $type = 0) { 
  // $type == 0 always evaluates TRUE if $type is a string
  if (is_numeric($type)) {
    $result = db_query(db_rewrite_sql('SELECT t.tid, n.nid FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 AND t.tid = %d'), $tid);
  }
  else {
    $result = db_query(db_rewrite_sql("SELECT t.tid, n.nid AS c FROM {term_node} t INNER JOIN {node} n ON t.vid = n.vid WHERE n.status = 1 AND n.type = '%s' AND t.tid = %d"), $type, $tid);
  }
  $count = array();
  while ($term = db_fetch_object($result)) {
    $count[] = $term->nid;
  }
  foreach (_taxonomy_term_children($tid) as $c) {
    $child = _uc_nbb_display_term_count_nodes_best($c, $type);
  }
  if (is_array($child)) {
    return $count = array_merge($count, $child);
  }
  // Doing until don't have a child
  else {
    array_unique($count);
    return count($count);
  }
}

function _taxonomy_term_children($tid) { 
  $result = db_query('SELECT tid FROM {term_hierarchy} WHERE parent = %d', $tid);
  while ($term = db_fetch_object($result)) {
    $children[] = $term->tid;
  }
  return isset($children) ? $children : array();
}

I don't understeand, why Drupal programmers constructs queries thats selects all data from tables and not just that you want as in my example. Is that way have any pros?

Post new comment

The content of this field is kept private and will not be shown publicly.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.
  • You can use Markdown syntax to format and style the text. Also see Markdown Extra for tables, footnotes, and more.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <img> <blockquote> <small> <h2> <h3> <h4> <h5> <h6> <sub> <sup> <p> <br> <strike> <table> <tr> <td> <thead> <th> <tbody> <tt> <output>
  • Lines and paragraphs break automatically.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.