| 1 |
<?php
|
| 2 |
// $Id: taxonomy_similar.module,v 1.6 2005/05/18 18:42:09 morbus Exp $
|
| 3 |
|
| 4 |
/**
|
| 5 |
* @file
|
| 6 |
* Allows users to choose similar tags after content submission.
|
| 7 |
*/
|
| 8 |
|
| 9 |
/**
|
| 10 |
* Implementation of hook_help().
|
| 11 |
*/
|
| 12 |
function taxonomy_similar_help($section) {
|
| 13 |
switch ($section) {
|
| 14 |
case 'taxonomy_similar/node/'. arg(2):
|
| 15 |
return '<p>'. t("Edit your tags below based on their similiarity to others. By choosing similar or stronger tags with higher usage, you'll make it easier for your users to find relevant and related material based on your content. This step is entirely optional - skipping it will keep your tags exactly as you've entered them.") .'</p>';
|
| 16 |
}
|
| 17 |
}
|
| 18 |
|
| 19 |
/**
|
| 20 |
* Implementation of hook_menu().
|
| 21 |
*/
|
| 22 |
function taxonomy_similar_menu($may_cache) {
|
| 23 |
$items = array();
|
| 24 |
|
| 25 |
if ($may_cache) {
|
| 26 |
$items[] = array('title' => t('Similar tags'),
|
| 27 |
'path' => 'taxonomy_similar/node',
|
| 28 |
'callback' => 'drupal_get_form',
|
| 29 |
'callback arguments' => array('taxonomy_similar_overview'),
|
| 30 |
'access' => user_access('choose similar tags'),
|
| 31 |
'type' => MENU_CALLBACK);
|
| 32 |
}
|
| 33 |
|
| 34 |
return $items;
|
| 35 |
}
|
| 36 |
|
| 37 |
/**
|
| 38 |
* Implementation of hook_perm().
|
| 39 |
*/
|
| 40 |
function taxonomy_similar_perm() {
|
| 41 |
return array('choose similar tags');
|
| 42 |
}
|
| 43 |
|
| 44 |
/**
|
| 45 |
* Implementation of hook_nodeapi().
|
| 46 |
*
|
| 47 |
* This is the "trickiest" part of the code, not because it's difficult, but
|
| 48 |
* because it is relatively odd in its implementation. The need here is to
|
| 49 |
* interrupt the normal drupal_goto that happens after a node insert or
|
| 50 |
* update. drupal_goto will either a) go to the passed location during its
|
| 51 |
* invocation or b) listen to the $_REQUEST['destination'] instead.
|
| 52 |
*
|
| 53 |
* We use 'destination' here instead of our own hardcoded drupal_goto so
|
| 54 |
* other modules have a chance to perform their own _nodeapi code (with
|
| 55 |
* drupal_goto, we'd interrupt the processing of all modules after ours).
|
| 56 |
* The (small) downside of this approach is that we can be the ONLY module
|
| 57 |
* that uses this hack (as any module using 'destination' before ours would
|
| 58 |
* be overwritten, and any module after ours would disable us). This should
|
| 59 |
* work for awhile, at least, since no other module implements this trickery.
|
| 60 |
*/
|
| 61 |
function taxonomy_similar_nodeapi($node, $op, $arg) {
|
| 62 |
switch ($op) {
|
| 63 |
case 'insert':
|
| 64 |
case 'update':
|
| 65 |
if (user_access('choose similar tags')) {
|
| 66 |
$vocabulary = taxonomy_get_vocabularies();
|
| 67 |
$terms = taxonomy_node_get_terms($node->nid);
|
| 68 |
if (is_array($terms)) {
|
| 69 |
foreach ($terms as $term) {
|
| 70 |
if (!$vocabulary[$term->vid]->tags) { continue; } // not free tags.
|
| 71 |
$_REQUEST['destination'] = check_url('taxonomy_similar/node/'. $node->nid);
|
| 72 |
return; // if we're this far, our destination has been set.
|
| 73 |
}
|
| 74 |
}
|
| 75 |
}
|
| 76 |
}
|
| 77 |
}
|
| 78 |
|
| 79 |
/**
|
| 80 |
* Display a form list of all similar terms for the passed node.
|
| 81 |
*/
|
| 82 |
function taxonomy_similar_overview($nid = 0) {
|
| 83 |
if (!$nid || !is_numeric($nid)) { drupal_not_found(); return; }
|
| 84 |
|
| 85 |
// load up all our requisite data. loading of the entire node
|
| 86 |
// is really a waste here, but makes the title seem nicer.
|
| 87 |
$vocabulary = taxonomy_get_vocabularies();
|
| 88 |
$terms = taxonomy_node_get_terms($nid);
|
| 89 |
$node = node_load($nid);
|
| 90 |
drupal_set_title(t('%name similar tags', array('%name' => $node->title)));
|
| 91 |
$form['taxonomy']['#tree'] = TRUE;
|
| 92 |
|
| 93 |
// loop through every term associated with this node,
|
| 94 |
// find similar keywords, and display checkboxes.
|
| 95 |
if (is_array($terms)) { // perform if array.
|
| 96 |
foreach ($terms as $term) {
|
| 97 |
if (!$vocabulary[$term->vid]->tags) { continue; }
|
| 98 |
$similars = taxonomy_similar_tags($term);
|
| 99 |
|
| 100 |
$form['taxonomy'][$term->vid][$term->tid] = array(
|
| 101 |
'#default_value' => 1,
|
| 102 |
'#title' => t('%name (@matches) is just perfect, so keep it.', array('%name' => $term->name, '@matches' => format_plural(taxonomy_similar_term_count_nodes($term->tid), '1 match', '@count matches'))),
|
| 103 |
'#type' => 'checkbox',
|
| 104 |
);
|
| 105 |
|
| 106 |
if (is_array($similars)) {
|
| 107 |
foreach ($similars as $similar) {
|
| 108 |
$form['taxonomy'][$similar->vid][$similar->tid] = array(
|
| 109 |
'#attributes' => array('style' => 'margin-left: 1.5em;'),
|
| 110 |
'#default_value' => 0,
|
| 111 |
'#title' => t('Use similar tag: %name (@matches).', array('%name' => $similar->name, '@matches' => format_plural($similar->count, '1 match', '@count matches'))),
|
| 112 |
'#type' => 'checkbox',
|
| 113 |
);
|
| 114 |
}
|
| 115 |
}
|
| 116 |
else {
|
| 117 |
$form['taxonomy'][$term->vid][$term->tid] = array(
|
| 118 |
'#value' => '<div style="margin-left: 1.5em;">'. t('There were no similar tags found for %name', array('%name' => $term->name)) .'</div>',
|
| 119 |
);
|
| 120 |
}
|
| 121 |
}
|
| 122 |
}
|
| 123 |
|
| 124 |
$form['submit'] = array(
|
| 125 |
'#type' => 'submit',
|
| 126 |
'#value' => t('Submit'),
|
| 127 |
);
|
| 128 |
|
| 129 |
$form['#submit']['taxonomy_similar_overview_submit'] = array($nid);
|
| 130 |
return $form;
|
| 131 |
}
|
| 132 |
|
| 133 |
/**
|
| 134 |
* Submit callback; saves term associations and redirects back to node view.
|
| 135 |
*/
|
| 136 |
function taxonomy_similar_overview_submit($form_id, &$form_values, $nid) {
|
| 137 |
taxonomy_similar_node_save($nid, $form_values['taxonomy']);
|
| 138 |
return "node/$nid";
|
| 139 |
}
|
| 140 |
|
| 141 |
/**
|
| 142 |
* A wrapper around taxonomy_node_save, which allows us to handle bum data
|
| 143 |
* (keys of 0) as well as potential future support for automatic saving to
|
| 144 |
* term_relations or merging one term with another.
|
| 145 |
*/
|
| 146 |
function taxonomy_similar_node_save($nid, $form_terms) {
|
| 147 |
if (!$nid) { return; }
|
| 148 |
$final_terms = array();
|
| 149 |
|
| 150 |
// remove keys that has a value of 0. these are
|
| 151 |
// created by the zeroing out of checkbox items.
|
| 152 |
foreach ($form_terms as $vid => $tids) {
|
| 153 |
foreach ($tids as $tid => $value) {
|
| 154 |
if ($form_terms[$vid][$tid]) {
|
| 155 |
$final_terms[$vid][] = $tid;
|
| 156 |
}
|
| 157 |
}
|
| 158 |
}
|
| 159 |
|
| 160 |
// load in non-free tagging terms associated with
|
| 161 |
// this $nid. if we don't, they'll all get deleted
|
| 162 |
// once our new $terms reaches taxonomy_node_save.
|
| 163 |
$nonfree_terms = taxonomy_similar_node_get_nonfree_terms($nid);
|
| 164 |
$final_terms = $final_terms + $nonfree_terms;
|
| 165 |
|
| 166 |
taxonomy_node_save($nid, $final_terms);
|
| 167 |
}
|
| 168 |
|
| 169 |
/**
|
| 170 |
* Determines a list of terms similar to the one passed.
|
| 171 |
* $tags/$tag: all terms in the database for a vocabulary.
|
| 172 |
* $term: the passed term object to find similarities of.
|
| 173 |
*/
|
| 174 |
function taxonomy_similar_tags($term, $percentage = 54) {
|
| 175 |
static $tags;
|
| 176 |
|
| 177 |
// cache the result of our "all keywords in vid".
|
| 178 |
if (!isset($tags[$term->vid])) {
|
| 179 |
$tags[$term->vid] = array();
|
| 180 |
$result = db_query('SELECT t.* FROM {term_data} t WHERE t.vid = %d ORDER BY name', $term->vid);
|
| 181 |
while ($tag = db_fetch_object($result)) {
|
| 182 |
$tags[$term->vid][] = $tag;
|
| 183 |
}
|
| 184 |
}
|
| 185 |
|
| 186 |
// look for similars.
|
| 187 |
$similars = array();
|
| 188 |
foreach ($tags[$term->vid] as $tag) {
|
| 189 |
if ($tag->tid == $term->tid) { continue; }
|
| 190 |
|
| 191 |
// create some percentage and soundalike testers.
|
| 192 |
similar_text(strtoupper($term->name), strtoupper($tag->name), $similar_percentage);
|
| 193 |
$tag_phone = metaphone($tag->name); $term_phone = metaphone($term->name);
|
| 194 |
|
| 195 |
// if our term has the same sound as this tag, or percentage
|
| 196 |
// for similar text is good, add to array for later returning.
|
| 197 |
if (preg_match ("/$term_phone/", $tag_phone) && $similar_percentage >= $percentage) {
|
| 198 |
$tag->percentage = $similar_percentage;
|
| 199 |
$tag->count = taxonomy_similar_term_count_nodes($tag->tid);
|
| 200 |
$similars[] = $tag;
|
| 201 |
}
|
| 202 |
}
|
| 203 |
|
| 204 |
// sort similars by their percentage.
|
| 205 |
usort($similars, "_sort_by_percentage");
|
| 206 |
|
| 207 |
return $similars;
|
| 208 |
}
|
| 209 |
|
| 210 |
/**
|
| 211 |
* A rewrite of taxonomy_term_count_nodes, because we don't need the call to
|
| 212 |
* taxonomy_term_children to get the count of a term's children. Also, the
|
| 213 |
* core function loads counts for every term in the database, which can be
|
| 214 |
* really prohibitive for very large tagging vocabularies.
|
| 215 |
*/
|
| 216 |
function taxonomy_similar_term_count_nodes($tid) {
|
| 217 |
static $count;
|
| 218 |
|
| 219 |
if (!isset($count[$tid])) {
|
| 220 |
$result = db_fetch_object(db_query('SELECT COUNT(n.nid) AS count FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid WHERE n.status = 1 AND t.tid = %d GROUP BY t.tid', $tid));
|
| 221 |
$count[$tid] = $result->count ? $result->count : 0;
|
| 222 |
}
|
| 223 |
|
| 224 |
return $count[$tid];
|
| 225 |
}
|
| 226 |
|
| 227 |
/**
|
| 228 |
* This is a slight rewrite of taxonomy_node_get_terms, with the primary
|
| 229 |
* difference being we'll return terms that are NOT in any free tagging
|
| 230 |
* vocabularies. This function is called before we do any saving - if
|
| 231 |
* we didn't, all these nonfree terms would be accidentally deleted.
|
| 232 |
*/
|
| 233 |
function taxonomy_similar_node_get_nonfree_terms($nid) {
|
| 234 |
static $terms;
|
| 235 |
|
| 236 |
if (!isset($terms[$nid])) {
|
| 237 |
$result = db_query('SELECT t.* FROM {term_data} t, {term_node} r, {vocabulary} v WHERE r.tid = t.tid AND r.nid = %d AND t.vid = v.vid AND v.tags = 0', $nid);
|
| 238 |
$terms[$nid] = array();
|
| 239 |
while ($term = db_fetch_object($result)) {
|
| 240 |
$terms[$nid][$term->vid][] = $term->tid;
|
| 241 |
}
|
| 242 |
}
|
| 243 |
|
| 244 |
return $terms[$nid];
|
| 245 |
}
|
| 246 |
|
| 247 |
/**
|
| 248 |
* Sorts terms by their percentage, via usort().
|
| 249 |
*/
|
| 250 |
function _sort_by_percentage($a, $b) {
|
| 251 |
if ($a->percentage == $b->percentage) { return 0; }
|
| 252 |
return ($a->percentage > $b->percentage) ? -1 : 1;
|
| 253 |
}
|