├── count_terms.inc ├── count_terms.info ├── count_terms.install ├── count_terms.module └── count_terms_handler_field.inc /count_terms.inc: -------------------------------------------------------------------------------- 1 | 'tid', 12 | 'field' => 'tid' 13 | ); 14 | $data['count_terms']['node_count'] = array( 15 | 'field' => array( 16 | 'handler' => 'count_terms_handler_field' 17 | ), 18 | 'filter' => array( 19 | 'handler' => 'views_handler_filter_numeric' 20 | ), 21 | 'sort' => array( 22 | 'handler' => 'views_handler_sort' 23 | ) 24 | ); 25 | $data['count_terms']['node_count']['title'] = t('Term Node Count'); 26 | $data['count_terms']['node_count']['help'] = t('The number of nodes associated with this term'); 27 | return $data; 28 | } 29 | 30 | /** 31 | * Implementation of hook_views_handlers(). 32 | */ 33 | function count_terms_views_handlers() { 34 | return array( 35 | 'handlers' => array( 36 | 'count_terms_handler_field' => array( 37 | 'parent' => 'views_handler_field_numeric' 38 | ) 39 | ) 40 | ); 41 | } -------------------------------------------------------------------------------- /count_terms.info: -------------------------------------------------------------------------------- 1 | name = Count Terms 2 | description = Allow Views to display number of terms associated to a node 3 | core = 6.x 4 | dependencies[] = taxonomy 5 | 6 | -------------------------------------------------------------------------------- /count_terms.install: -------------------------------------------------------------------------------- 1 | array( 35 | 'tid' => array( 36 | 'type' => 'int', 37 | 'unsigned' => TRUE, 38 | 'not null' => TRUE, 39 | 'default' => 0 40 | ), 41 | 'node_count' => array( 42 | 'type' => 'int', 43 | 'unsigned' => TRUE, 44 | 'not null' => TRUE, 45 | 'default' => 0 46 | ) 47 | ), 48 | 'primary key' => array('tid') 49 | ); 50 | return $schema; 51 | } -------------------------------------------------------------------------------- /count_terms.module: -------------------------------------------------------------------------------- 1 | ' . $op . '
' . print_r($node, 1) . '
'; 43 | 44 | switch ($op) { 45 | case 'insert': 46 | // since the node is first being created, all we have to do 47 | // is check which terms were saved to term_node 48 | $tids_to_update = count_terms_saved_terms($node); 49 | count_terms_update($tids_to_update); 50 | break; 51 | case 'presave': 52 | // when a node is updated we need to first get the tids previously saved 53 | // this is only possible in the presave op because term_node hasn't been altered yet 54 | if (isset($node->nid)) { 55 | $saved_tids = count_terms_saved_terms($node); 56 | } 57 | break; 58 | case 'update': 59 | // $saved_tids is saved in a static variable 60 | // we'll combine that with the proposed changes to the node, and that way we'll 61 | // be able to get a full list of term ids that need to be updated 62 | if (!empty($node->taxonomy)) { 63 | $submitted_tids = count_terms_submitted_terms($node->taxonomy); 64 | $tids_to_update = $saved_tids + $submitted_tids; 65 | $tids_to_update = array_unique($tids_to_update); 66 | count_terms_update($tids_to_update); 67 | } 68 | break; 69 | case 'delete': 70 | // the term ids that need to be updated are saved to the node already in $node->taxonomy 71 | $tids_to_update = array(); 72 | foreach ($node->taxonomy as $tid => $term) { 73 | $tids_to_update[$tid] = $tid; 74 | } 75 | count_terms_update($tids_to_update); 76 | break; 77 | } 78 | } 79 | 80 | /** 81 | * Updates terms in count_terms given an array of tids. 82 | */ 83 | function count_terms_update($tids_to_update) { 84 | // go through each tid provided, count the number of nodes attached 85 | // to it and update the count_terms table 86 | if (!empty($tids_to_update)) { 87 | $sql = 'UPDATE {count_terms} SET node_count = %d WHERE tid = %d'; 88 | $counts = count_terms_count_nodes($tids_to_update); 89 | foreach ($counts as $tid => $count) { 90 | db_query($sql, $count, $tid); 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * Function used to count nodes. It's like taxonomy_term_count_nodes but without static caching. 97 | * Also I don't think child terms should be included. 98 | */ 99 | function count_terms_count_nodes($terms) { 100 | if (!empty($terms)) { 101 | $tids_list = implode(',', $terms); 102 | $tids_list = rtrim($tids_list, ','); 103 | // note that we're only bringing back published nodes 104 | $sql = "SELECT tid, COUNT(tid) AS count FROM {term_node} tn INNER JOIN {node} n ON tn.vid = n.vid WHERE tid IN (%s) AND n.status = 1 GROUP BY tid"; 105 | $result = db_query($sql, $tids_list); 106 | $counts = array(); 107 | while ($term = db_fetch_object($result)) { 108 | $counts[$term->tid] = $term->count; 109 | } 110 | 111 | // if the term id didn't return any results, then there are no nodes attached to it 112 | foreach ($terms as $tid) { 113 | if (!isset($counts[$tid])) { 114 | $counts[$tid] = 0; 115 | } 116 | } 117 | } 118 | 119 | return $counts; 120 | } 121 | 122 | /** 123 | * Find out which terms were previously saved so we can figure out how to update count_terms. 124 | * 125 | * I hate node revisions. 126 | */ 127 | function count_terms_saved_terms($node) { 128 | $saveds_tids = array(); 129 | $sql = 'SELECT tid FROM {term_node} tn INNER JOIN {node} n ON tn.vid = n.vid WHERE tn.vid = %d AND n.status = 1'; 130 | if (!$node->revision) { 131 | // if there's no revisioning involved then this is a piece of cake 132 | $result = db_query($sql, $node->vid); 133 | } 134 | else { 135 | // this is a revision 136 | if (!$node->revision_timestamp) { 137 | // the user is creating a new revision 138 | $result = db_query($sql, $node->vid); 139 | } 140 | else { 141 | // the user is reverting back to an older revision 142 | // the problem here is that instead of telling me the most recent vid 143 | // drupal gives me the vid associated with whatever version we're reverting to 144 | // to get around that i do some sql magic to get the revision for this node directly preceding the newest one 145 | // i wish there was a better way to do it 146 | $old_vid = db_result(db_query('SELECT vid FROM {node_revisions} WHERE nid = %d ORDER BY timestamp DESC LIMIT 1', $node->nid)); 147 | //print $old_vid; 148 | $result = db_query($sql, $old_vid); 149 | } 150 | } 151 | while ($tids = db_fetch_object($result)) { 152 | $saveds_tids[$tids->tid] = $tids->tid; 153 | } 154 | return $saveds_tids; 155 | } 156 | 157 | /** 158 | * Takes array provided by $node->taxonomy and filters it 159 | */ 160 | function count_terms_submitted_terms($taxonomy) { 161 | $submitted_tids = array(); 162 | foreach ($taxonomy as $key => $value) { 163 | if (!empty($value)) { 164 | if ($key != 'tags') { 165 | if (is_object($value)) { 166 | // we're updating from admin/content/node 167 | // objects with no properties are not considered empty 168 | if (!empty($value->tid)) { 169 | $submitted_tids[$value->tid] = $value->tid; 170 | } 171 | } 172 | elseif (is_array($value)) { 173 | foreach ($value as $tid) { 174 | $submitted_tids[$tid] = $tid; 175 | } 176 | } 177 | else { 178 | $submitted_tids[$value] = $value; 179 | } 180 | } 181 | else { 182 | // special processing must be done for tagging vocabularies 183 | foreach ($value as $vid => $tags) { 184 | $tags = drupal_explode_tags($tags); 185 | foreach ($tags as $tag) { 186 | // we only get a term name, so we have to query term_data for a tid 187 | // there could already be terms with the same name, so we select the 188 | // highest tid because that will be the most recent one 189 | $tid = db_result(db_query("SELECT MAX(tid) FROM {term_data} WHERE name = '%s'", $tag)); 190 | $submitted_tids[$tid] = $tid; 191 | } 192 | } 193 | } 194 | } 195 | } 196 | 197 | return $submitted_tids; 198 | } 199 | 200 | /** 201 | * Implementation of hook_views_api(). 202 | */ 203 | function count_terms_views_api() { 204 | return array( 205 | 'api' => 2 206 | ); 207 | } -------------------------------------------------------------------------------- /count_terms_handler_field.inc: -------------------------------------------------------------------------------- 1 | additional_fields['tid'] = 'tid'; 13 | } 14 | 15 | function option_definition() { 16 | $options = parent::option_definition(); 17 | $options['link_to_term'] = array('default' => FALSE); 18 | return $options; 19 | } 20 | 21 | /** 22 | * Provide link to term option 23 | */ 24 | function options_form(&$form, &$form_state) { 25 | parent::options_form($form, $form_state); 26 | $form['link_to_term'] = array( 27 | '#title' => t('Link this field to its term'), 28 | '#type' => 'checkbox', 29 | '#default_value' => !empty($this->options['link_to_term']), 30 | ); 31 | } 32 | 33 | /** 34 | * Render whatever the data is as a link to the term. 35 | * 36 | * Data should be made XSS safe prior to calling this function. 37 | */ 38 | function render_link($data, $values) { 39 | if (!empty($this->options['link_to_term']) && $data !== NULL && $data !== '') { 40 | return l($data, 'taxonomy/term/' . $values->{$this->aliases['tid']}, array('html' => TRUE)); 41 | } 42 | else { 43 | return $data; 44 | } 45 | } 46 | 47 | function render($values) { 48 | $value = $values->{$this->field_alias}; 49 | if (!empty($this->options['set_precision'])) { 50 | $value = number_format($value, $this->options['precision'], $this->options['decimal'], $this->options['separator']); 51 | } 52 | else { 53 | $remainder = abs($value) - intval(abs($value)); 54 | $value = number_format(floor($value), 0, '', $this->options['separator']); 55 | if ($remainder) { 56 | // The substr may not be locale safe. 57 | $value .= $this->options['decimal'] . substr($remainder, 2); 58 | } 59 | } 60 | 61 | return $this->render_link(check_plain($this->options['prefix'] . $value . $this->options['suffix']), $values); 62 | } 63 | } --------------------------------------------------------------------------------