| 1 |
<?php
|
| 2 |
// $Id: cmt.module,v 1.32 2007/08/20 18:59:47 agaric Exp $
|
| 3 |
/**
|
| 4 |
* @file
|
| 5 |
* Community-managed taxonomy enables users to collaborate on categorization decisions.
|
| 6 |
*/
|
| 7 |
|
| 8 |
/**
|
| 9 |
* Display modes (if any). I also want to make it as available to custom theming as possible
|
| 10 |
*/
|
| 11 |
define('CMT_NODE_FORM_INLINE', 2);
|
| 12 |
define('CMT_NODE_FORM_TAB', 1);
|
| 13 |
define('CMT_NODE_FORM_BLOCK', 0);
|
| 14 |
// follows community_tags' numbering convention, but inline will be default (possibly only)
|
| 15 |
|
| 16 |
// include admin functions; look up if there's a Drupal-standard way to prevent this from loading unless it needs to
|
| 17 |
include_once './'. drupal_get_path('module', 'cmt') .'/cmt_admin.inc';
|
| 18 |
|
| 19 |
/**
|
| 20 |
* Implementation of hook_perm().
|
| 21 |
*/
|
| 22 |
function cmt_perm() {
|
| 23 |
return array(
|
| 24 |
'access community managed taxonomy', // aka manage community categories
|
| 25 |
'administer community managed taxonomy',
|
| 26 |
);
|
| 27 |
}
|
| 28 |
|
| 29 |
/**
|
| 30 |
* Implementation of hook_menu().
|
| 31 |
*/
|
| 32 |
function cmt_menu($may_cache) {
|
| 33 |
$items = array();
|
| 34 |
if ($may_cache) {
|
| 35 |
/* -- administrative menu items ----------------------------------------- */
|
| 36 |
$items[] = array(
|
| 37 |
'access' => user_access('administer community managed taxonomy'),
|
| 38 |
'callback' => 'cmt_overview_vocabularies',
|
| 39 |
'description' => t('Edit settings for community-managed vocabularies (taxonomy enhanced by the cmt module).'),
|
| 40 |
'path' => 'admin/content/cmt',
|
| 41 |
'title' => t('Community-managed Categories'),
|
| 42 |
);
|
| 43 |
$items[] = array(
|
| 44 |
'access' => user_access('administer community managed taxonomy'),
|
| 45 |
'path' => 'admin/content/cmt/list',
|
| 46 |
// optional? 'callback' => 'cmt_overview_vocabularies',
|
| 47 |
'type' => MENU_DEFAULT_LOCAL_TASK,
|
| 48 |
'title' => t('List vocabularies'),
|
| 49 |
'weight' => -10,
|
| 50 |
);
|
| 51 |
$items[] = array(
|
| 52 |
'path' => 'admin/content/cmt/edit/vocabulary',
|
| 53 |
'title' => t('Edit community-managed vocabulary'),
|
| 54 |
'callback' => 'cmt_admin_vocabulary_edit',
|
| 55 |
'access' => user_access('administer community managed taxonomy'),
|
| 56 |
'type' => MENU_CALLBACK,
|
| 57 |
);
|
| 58 |
$items[] = array(
|
| 59 |
'access' => user_access('administer community managed taxonomy'),
|
| 60 |
'callback' => 'drupal_get_form',
|
| 61 |
'callback arguments' => array('cmt_settings'),
|
| 62 |
'description' => t('Configure Community-managed Taxonomy options that apply to all vocabularies with these settings.'),
|
| 63 |
'path' => 'admin/content/cmt/settings',
|
| 64 |
'title' => t('Settings'),
|
| 65 |
'type' => MENU_LOCAL_TASK,
|
| 66 |
);
|
| 67 |
/* -- other menu items ---------------------------------------------- */
|
| 68 |
$items[] = array(
|
| 69 |
'access' => user_access('access community managed taxonomy'),
|
| 70 |
'callback' => 'cmt_autocomplete',
|
| 71 |
'path' => 'cmt/autocomplete',
|
| 72 |
'title' => t('Community-managed taxonomy autocomplete'),
|
| 73 |
'type' => MENU_CALLBACK,
|
| 74 |
);
|
| 75 |
}
|
| 76 |
else { // no cache
|
| 77 |
/* ADD later from community_tags ?
|
| 78 |
global $user;
|
| 79 |
*/
|
| 80 |
}
|
| 81 |
return $items;
|
| 82 |
}
|
| 83 |
|
| 84 |
/**
|
| 85 |
* Implementation of hook_form_alter().
|
| 86 |
*
|
| 87 |
* An important hook for CMT.
|
| 88 |
* First, to add vocabularies to community management by making it an option
|
| 89 |
* on the create and edit vocabulary forms.
|
| 90 |
*/
|
| 91 |
function cmt_form_alter($form_id, &$form) {
|
| 92 |
if ($form_id == 'taxonomy_form_vocabulary') {
|
| 93 |
if (!$form['vid']['#value'] || !cmt_is_vid_cmt($form['vid']['#value'])) {
|
| 94 |
$cmt_vocabulary['cmt_vocabulary'] = array(
|
| 95 |
'#type' => 'radios',
|
| 96 |
'#title' => t('Community management'),
|
| 97 |
'#default_value' => 0,
|
| 98 |
'#options' => array(
|
| 99 |
0 => t('Disabled'),
|
| 100 |
1 => t('No threshold'),
|
| 101 |
2 => t('Default threshold'),
|
| 102 |
3 => t('Default threshold (with related terms derived)'),
|
| 103 |
4 => t('High threshold (with related terms derived)'),
|
| 104 |
),
|
| 105 |
'#description' => t('Allows <a href="@help-url">community managing</a> of terms in this vocabulary. Users can influence both what terms nodes are tagged with and how these terms are themselves organized, named, and described.', array('@help-url' => url('admin/help/cmt'))),
|
| 106 |
);
|
| 107 |
}
|
| 108 |
else {
|
| 109 |
$cmt_vocabulary['cmt_vocabulary'] = array(
|
| 110 |
'#type' => 'markup',
|
| 111 |
'#title' => t('Community management'),
|
| 112 |
'#value' => '<h2>' . t('Community management:') . '</h2> <div>' . t('This vocabulary is open to <a href="@help-url">community managing</a> of its terms. Users can influence both what terms nodes are tagged with and how these terms are themselves organized, named, and described. To edit community-management settings for this vocabulary (including disabling it) please see its <a href="@cmtsettings">Community managed Categories administration page</a>.', array('@help-url' => url('admin/help/cmt'), '@cmtsettings' => url('admin/content/cmt/edit/vocabulary/' . $form['vid']['#value']))) . '</div>',
|
| 113 |
);
|
| 114 |
}
|
| 115 |
$pos = array_search('submit', array_keys($form));
|
| 116 |
$form = array_merge(array_slice($form, 0, $pos), $cmt_vocabulary, array_slice($form, $pos));
|
| 117 |
}
|
| 118 |
elseif ($form_id == 'taxonomy_vocabulary_confirm_delete') {
|
| 119 |
$form['cmt_deleete'] = array(
|
| 120 |
'#type' => 'markup',
|
| 121 |
'#value' => '<div>' . t('<em>Deleting the community-managed vocabulary</em> @name <em>will also delete all community suggestions and choices about term position and information that was associated with it.</em>', array('@name' => $form['name']['#value'])) . '</div>',
|
| 122 |
'#weight' => 10,
|
| 123 |
);
|
| 124 |
$form['actions']['#weight'] = 11;
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
| 128 |
/**
|
| 129 |
* Implementation of hook_taxonomy()
|
| 130 |
*
|
| 131 |
* This could be moved to an admin-only section that is loaded for *every*
|
| 132 |
* administration page. It stays here until that is determined.
|
| 133 |
*/
|
| 134 |
function cmt_taxonomy($op, $type, $array) {
|
| 135 |
switch ($type) {
|
| 136 |
case 'vocabulary':
|
| 137 |
// we're only interested if it's newly community-managed
|
| 138 |
if ($array['cmt_vocabulary']) {
|
| 139 |
// would it be good, bad, or neutral practice to put this line in the if condition?
|
| 140 |
// initial threshold key, making 'reset' feature possible. Not after disabling though.
|
| 141 |
$array['cmt_enabled'] = $array['cmt_vocabulary']['#value'];
|
| 142 |
// we would require_once('cmt_admin.inc') here but you can't require functions inside a function. So right now we save no bandwidth, but we get the admin stuff out of MY way
|
| 143 |
// this file assigns default thresholds directly to the $array variable:
|
| 144 |
require_once('default_thresholds.inc');
|
| 145 |
$msg_end = t('You may want to review its <a href="@cmtsettings">community-management settings</a>.', array('@cmtsettings' => url('admin/content/cmt/edit/vocabulary/' . $array['vid'])));
|
| 146 |
switch($op) {
|
| 147 |
case 'update':
|
| 148 |
if (!cmt_is_vid_cmt($array['vid'])) $op = 'insert';
|
| 149 |
if (cmt_save_vocabulary($array, $op)) {
|
| 150 |
$msg = t('The vocabulary %name is now community-managed. ', array('%name' => $array['name']));
|
| 151 |
drupal_set_message($msg . $msg_end);
|
| 152 |
}
|
| 153 |
else drupal_set_message('<strong>Making this vocabulary community-managed failed somehow.</strong>');
|
| 154 |
break;
|
| 155 |
case 'insert':
|
| 156 |
$msg = t('Your new vocabulary is community-managed. ');
|
| 157 |
if(cmt_save_vocabulary($array, $op)) {
|
| 158 |
drupal_set_message($msg . $msg_end);
|
| 159 |
}
|
| 160 |
else drupal_set_message('<strong>Making your new vocabulary community-managed failed somehow.</strong>');
|
| 161 |
break;
|
| 162 |
case 'delete':
|
| 163 |
if(_cmt_del_vocabulary($array['vid'])) {
|
| 164 |
// there's no reason to say 'you just deleted a CMT' - drupal_set_message('');
|
| 165 |
// we do insert an extra notice in the delete confirmation form
|
| 166 |
}
|
| 167 |
break;
|
| 168 |
} // end switch on op
|
| 169 |
break;
|
| 170 |
} // end if cmt_vocabulary set
|
| 171 |
} // end switch on type
|
| 172 |
}
|
| 173 |
|
| 174 |
/**
|
| 175 |
* Implementation of hook_nodeapi().
|
| 176 |
*/
|
| 177 |
function cmt_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
|
| 178 |
switch ($op) {
|
| 179 |
|
| 180 |
case 'insert':
|
| 181 |
case 'update':
|
| 182 |
// Form alter to remove the community managed taxonomy vocabularies from the edit form?
|
| 183 |
break;
|
| 184 |
|
| 185 |
case 'load':
|
| 186 |
|
| 187 |
// This may be wrong, but since I don't want to put any data in the node load---
|
| 188 |
// if some other module wants community tags information, we'll have a function for that
|
| 189 |
// Therefore we shall save all logic for node view
|
| 190 |
/*
|
| 191 |
// modeled on community_tags
|
| 192 |
// compare cmt_node_form_display option with inline constant, store TRUE if match
|
| 193 |
$node->cmt_node_form_display = variable_get('cmt_node_form_display', CMT_NODE_FORM_INLINE) == CMT_NODE_FORM_INLINE;
|
| 194 |
*/
|
| 195 |
break;
|
| 196 |
|
| 197 |
case 'view':
|
| 198 |
/* for deciding which nodes we even have to mess with, we have two options:
|
| 199 |
* 1. use an array of node->types affected by CMT vocabularies saved as a variable
|
| 200 |
if (in_array($node->type, variable_get('casetracker_case_node_types', array('casetracker_basic_case')), TRUE)) {
|
| 201 |
}
|
| 202 |
* 2. select directly from the cmt_vocabularies and vocabulary_node_types tables
|
| 203 |
vocabulary_node_types: vid, type
|
| 204 |
we'll go with the second version, outsourced to a static variable function,
|
| 205 |
cmt_node_types
|
| 206 |
*/
|
| 207 |
if (in_array($node->type, cmt_node_types()) && !$teaser) // make teasers an option here
|
| 208 |
{
|
| 209 |
$node->content['body']['#value'] .= $output;
|
| 210 |
// modeled on community_tags
|
| 211 |
$node->content['cmt_terms'] = array(
|
| 212 |
'#value' => cmt_terms_node_view($node, TRUE),
|
| 213 |
'#weight' => 15,
|
| 214 |
);
|
| 215 |
}
|
| 216 |
break;
|
| 217 |
}
|
| 218 |
}
|
| 219 |
|
| 220 |
/**
|
| 221 |
* List all node types affected by any CMT vocabulary.
|
| 222 |
*/
|
| 223 |
function cmt_node_types() {
|
| 224 |
static $cmt_node_types;
|
| 225 |
if (!$cmt_node_types) {
|
| 226 |
$result = db_query('SELECT DISTINCT(v.type) AS type FROM {cmt_vocabulary} c LEFT JOIN {vocabulary_node_types} v ON c.vid = v.vid WHERE c.cmt_enabled = 1');
|
| 227 |
$cmt_node_types = array();
|
| 228 |
while ($data = db_fetch_object($result)) {
|
| 229 |
$cmt_node_types[] = $data->type;
|
| 230 |
}
|
| 231 |
}
|
| 232 |
return $cmt_node_types;
|
| 233 |
}
|
| 234 |
|
| 235 |
/**
|
| 236 |
* Delete all CMT information associated with a node.
|
| 237 |
*
|
| 238 |
* @param nid
|
| 239 |
* The nid of the node that is being deleted.
|
| 240 |
*/
|
| 241 |
function cmt_node_delete($nid) {
|
| 242 |
/* I don't think there's any percentage in wrapping in a function like this:
|
| 243 |
if (in_array($node->type, variable_get('cmt_node_types', array('default')), TRUE)) {
|
| 244 |
*/
|
| 245 |
db_query('DELETE FROM {cmt_term_node} AS t, {votingapi_vote} AS v, {votingapi_cache} AS c USING v LEFT JOIN c LEFT JOIN t WHERE v.content_id = c.content_id AND c.content_id = t.content_id AND c.content_type = "cmt_term" AND nid = %d', $nid);
|
| 246 |
}
|
| 247 |
|
| 248 |
/**
|
| 249 |
* Find all exposed community-managed terms associated with the given node,
|
| 250 |
* ordered by vocabulary and term weight.
|
| 251 |
*
|
| 252 |
* Like taxonomy_node_get_terms, but for cmt vocabs only.
|
| 253 |
* This loads ONLY exposed terms, not suggested cmt terms (aka official not proposed)
|
| 254 |
*/
|
| 255 |
function cmt_taxonomy_node_get_terms($nid, $key = 'tid') {
|
| 256 |
// this is all encased in a function and so will be happily inefficient for now
|
| 257 |
$cmt_taxonomy_terms = array();
|
| 258 |
// taxonomy_node_get_terms should already be loaded, so we'll just use it
|
| 259 |
$terms = taxonomy_node_get_terms($nid);
|
| 260 |
$cmt_vocab_ids = array_keys(cmt_get_vocabularies_by_node($nid));
|
| 261 |
// in_array could be replaced by cmt_is_vid_cmt, but this may be more efficient
|
| 262 |
$c = count($terms);
|
| 263 |
// for($i=0;$i<$c;$i++) - won't work- terms key is tid
|
| 264 |
foreach($terms as $term) {
|
| 265 |
if (in_array($term->vid, $cmt_vocab_ids)) {
|
| 266 |
$cmt_taxonomy_terms[$term->$key] = $term;
|
| 267 |
}
|
| 268 |
}
|
| 269 |
return $cmt_taxonomy_terms;
|
| 270 |
}
|
| 271 |
|
| 272 |
/*
|
| 273 |
* Find all community-managed terms associated with the given node, ordered by
|
| 274 |
* vocabulary and term weight.
|
| 275 |
*
|
| 276 |
* Both exposed (official taxonomy) and proposed (below-threshold CMT) terms
|
| 277 |
* are returned.
|
| 278 |
*/
|
| 279 |
function cmt_node_get_terms($nid, $key = 'tid') {
|
| 280 |
static $cmt_terms;
|
| 281 |
if (!isset($cmt_terms[$nid])) { // do we need any {vocabulary} info?
|
| 282 |
$result = db_query('SELECT t.*, tv.vid FROM {cmt_term_node} r INNER JOIN {cmt_term_data} t ON r.tid = t.tid INNER JOIN {cmt_term_vocab} tv ON t.tid = tv.tid INNER JOIN {cmt_vocabulary} cv ON tv.vid = cv.vid INNER JOIN {vocabulary} v ON v.vid = cv.vid WHERE r.nid = %d AND cv.cmt_enabled > 0 ORDER BY v.weight, r.vote DESC, t.weight, t.name', $nid);
|
| 283 |
$cmt_terms[$nid] = array();
|
| 284 |
while ($term = db_fetch_object($result)) {
|
| 285 |
$cmt_terms[$nid][$term->$key] = $term;
|
| 286 |
}
|
| 287 |
}
|
| 288 |
return $cmt_terms[$nid];
|
| 289 |
}
|
| 290 |
|
| 291 |
/**
|
| 292 |
* Community-managed taxonomy callback for node view.
|
| 293 |
*/
|
| 294 |
function cmt_terms_node_view($node, $inline = TRUE) {
|
| 295 |
if (!$inline) {
|
| 296 |
drupal_set_title(check_plain($node->title));
|
| 297 |
}
|
| 298 |
if (user_access('access community managed taxonomy') && count($cmt_vocabs = cmt_get_vocabularies_by_node($node->nid))) {
|
| 299 |
$module_path = drupal_get_path('module', 'cmt') .'/';
|
| 300 |
drupal_add_css($module_path .'cmt.css');
|
| 301 |
drupal_add_js($module_path .'js/cmt_terms_node_view.js');
|
| 302 |
$output = '<div class="cmt-terms-node-view">';
|
| 303 |
|
| 304 |
// array('nid' => $node->nid, 'vid' => $vid, 'inline' => $inline)
|
| 305 |
// we only need to include this: 'tags' => $tags,
|
| 306 |
// if the form doesn't track that itself for errors
|
| 307 |
|
| 308 |
/* so I need to get the real taxonomy metrics and compare them to the community taxonomy variables. This way I can hide access-controlled terms. This will require me to also figure out thresholds on my own, and exclude those that are above the threshold (or, simpler, include only those below the threshold). Uh, actually, I can't fathom what the implications of restricted terms would be for a community-managed vocabulary. So I'm not going to worry about it for now.
|
| 309 |
|
| 310 |
I'm also trying to think what I can expose here for testing, 'cause this would be a great place for very hard-to-find bugs to hide
|
| 311 |
*/
|
| 312 |
$cmt_terms = cmt_node_get_terms($node->nid);
|
| 313 |
$tax_terms = cmt_taxonomy_node_get_terms($node->nid);
|
| 314 |
$exposed_terms = array_intersect_key($cmt_terms, $tax_terms);
|
| 315 |
// official terms first
|
| 316 |
foreach ($exposed_terms as $tid => $term) {
|
| 317 |
// we don't want to run the same term twice, so remove the official from the proposed
|
| 318 |
unset($cmt_terms[$tid]);
|
| 319 |
}
|
| 320 |
// now, remaining proposed terms
|
| 321 |
foreach ($cmt_terms as $tid => $term) {
|
| 322 |
$output .= '<h4><a href="#">' . $term->name . '</a></h4>';
|
| 323 |
$output .= '<div class="term">';
|
| 324 |
$hierarchies = cmt_get_hierarchies($tid);
|
| 325 |
// drupal_set_message("tid ". $tid . " with vid " . $term->vid);
|
| 326 |
// $tree = cmt_get_tree($term->vid, 1);
|
| 327 |
// agaric_a($hierarchies);
|
| 328 |
// drupal_set_message("And now my way: ");
|
| 329 |
// $ancestors = array();
|
| 330 |
// cmt_get_ancestors($tid, $term->vid, $ancestors);
|
| 331 |
// agaric_a($ancestors);
|
| 332 |
$edit = array(
|
| 333 |
'nid' => $node->nid,
|
| 334 |
'tid' => $tid,
|
| 335 |
'vid' => $term->vid,
|
| 336 |
// 'term_hierarchy' => implode(' > ', $hierarchies[0]),
|
| 337 |
);
|
| 338 |
$output .= drupal_get_form('cmt_term_hierarchy_form', $edit);
|
| 339 |
$output .= '</div>';
|
| 340 |
}
|
| 341 |
foreach ($cmt_vocabs AS $vid => $vocab) {
|
| 342 |
$edit = array(
|
| 343 |
'nid' => $node->nid,
|
| 344 |
'vid' => $vid,
|
| 345 |
);
|
| 346 |
if (count($cmt_vocabs) > 1) $edit['for'] = ' '.t('for').' '. $vocab->name;
|
| 347 |
else $edit['for'] = '';
|
| 348 |
$output .= drupal_get_form('cmt_new_term_form', $edit);
|
| 349 |
}
|
| 350 |
$output .= '</div>';
|
| 351 |
}
|
| 352 |
return $output;
|
| 353 |
}
|
| 354 |
|
| 355 |
/**
|
| 356 |
* Community-managed taxonomy form for managing terms from a node
|
| 357 |
*
|
| 358 |
* Called by cmt_terms_node_view
|
| 359 |
*/
|
| 360 |
function cmt_term_hierarchy_form($edit) { // $edit, $title = NULL
|
| 361 |
// redundant check... should be removed somewhere? Should come before anything?
|
| 362 |
if (!user_access('access community managed taxonomy')) {
|
| 363 |
return;
|
| 364 |
}
|
| 365 |
$form['term_hierarchy'] = array(
|
| 366 |
'#type' => 'markup',
|
| 367 |
// '#title' => t('Hierarchy'),
|
| 368 |
'#prefix' => '<div>',
|
| 369 |
'#value' => $edit['term_hierarchy'],
|
| 370 |
'#suffix' => '</div>',
|
| 371 |
);
|
| 372 |
$form['submit'] = array(
|
| 373 |
'#type' => 'submit',
|
| 374 |
'#value' => t('Endorse term at this location'),
|
| 375 |
);
|
| 376 |
$form['content_id'] = array(
|
| 377 |
'#value' => $edit['content_id'],
|
| 378 |
);
|
| 379 |
$form['nid'] = array(
|
| 380 |
'#type' => 'value',
|
| 381 |
'#value' => $edit['nid'],
|
| 382 |
);
|
| 383 |
$form['vid'] = array(
|
| 384 |
'#type' => 'value',
|
| 385 |
'#value' => $edit['vid'],
|
| 386 |
);
|
| 387 |
return $form;
|
| 388 |
}
|
| 389 |
|
| 390 |
/**
|
| 391 |
* Form for node view that allows adding of a new term suggestion.
|
| 392 |
*
|
| 393 |
* Look into making this a multi-form to gather initial description and weight suggestions.
|
| 394 |
*/
|
| 395 |
function cmt_new_term_form($edit) {
|
| 396 |
$form['cmt_terms'] = array(
|
| 397 |
'#type' => 'textfield',
|
| 398 |
'#title' => t('New term') . $edit['for'],
|
| 399 |
'#maxlength' => 265,
|
| 400 |
'#size' => 40,
|
| 401 |
// '#autocomplete_path' => 'cmt/autocomplete/'. $edit['vid'],
|
| 402 |
'#attributes' => array('class' => 'cmt-new-term'),
|
| 403 |
'#description' => t('Text for a term name (optionally preceded by parent terms delimited by ">" to put the term in a hierarchy, which need not already be created).<span class="no-js"></span> Example: "Horn of Plenty", Vegetables > rutabaga'),
|
| 404 |
// it would be polite to insert the help text each vocabulary provides here
|
| 405 |
);
|
| 406 |
$form['submit'] = array(
|
| 407 |
'#type' => 'submit',
|
| 408 |
'#value' => t('Create term'),
|
| 409 |
);
|
| 410 |
$form['nid'] = array(
|
| 411 |
'#type' => 'value',
|
| 412 |
'#value' => $edit['nid'],
|
| 413 |
);
|
| 414 |
$form['vid'] = array(
|
| 415 |
'#type' => 'value',
|
| 416 |
'#value' => $edit['vid'],
|
| 417 |
);
|
| 418 |
return $form;
|
| 419 |
}
|
| 420 |
|
| 421 |
/**
|
| 422 |
* Validate new term form
|
| 423 |
*/
|
| 424 |
function cmt_new_term_form_validate($form_id, $form_values) {
|
| 425 |
if ($form_values['cmt_terms'] == '') {
|
| 426 |
form_set_error('cmt_terms', t('To suggest a new term to associate with this content, you must enter some text for a term name (optionally preceded by parent terms delimited by ">" to put the term in a hierarchy, which need not already be created).'));
|
| 427 |
}
|
| 428 |
}
|
| 429 |
|
| 430 |
/**
|
| 431 |
* Submit callback for new term form
|
| 432 |
*/
|
| 433 |
function cmt_new_term_form_submit($form_id, $form_values) {
|
| 434 |
// Add parsing for hierarchical, even comma-separated multiple hierarchical vocabularies, later
|
| 435 |
// this will technically be "cmt_new_terms_form_submit" but I think name stays
|
| 436 |
|
| 437 |
// parsing function below. Save called as many times as necessary.
|
| 438 |
// OR do the initial parsing in the validate command, if there's any such thing as invalid input
|
| 439 |
|
| 440 |
// first split on commas
|
| 441 |
$typed_value = $form_values['cmt_terms'];
|
| 442 |
unset($form_values['cmt_terms']);
|
| 443 |
$vid = $form_values['vid'];
|
| 444 |
// lifted from taxonomy_node_save()
|
| 445 |
// This regexp allows the following types of user input:
|
| 446 |
// this, "somecmpany, llc", "and ""this"" w,o.rks", foo bar
|
| 447 |
// (and, I hope, lot > of > tags)
|
| 448 |
$regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
|
| 449 |
preg_match_all($regexp, $typed_value, $set_matches);
|
| 450 |
$typed_termsets = array_unique($set_matches[1]);
|
| 451 |
foreach ($typed_termsets as $typed_termset) {
|
| 452 |
// next, split each section into components of the hierarchical path, if any
|
| 453 |
// the regular expression here needs serious attention
|
| 454 |
// it is supposed to separate terms by ">"
|
| 455 |
$regexp = '%(?:^|>\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^">]*))%x';
|
| 456 |
preg_match_all($regexp, $typed_termset, $term_matches);
|
| 457 |
$typed_terms = array_unique($term_matches[1]);
|
| 458 |
$count = count($typed_terms);
|
| 459 |
$i = 1;
|
| 460 |
$pid = 0;
|
| 461 |
foreach ($typed_terms as $typed_term) {
|
| 462 |
// If a user has escaped a term (to demonstrate that it is a group,
|
| 463 |
// or includes a comma or quote character), we remove the escape
|
| 464 |
// formatting so to save the term into the database as the user intends.
|
| 465 |
// $typed_term = str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $typed_term));
|
| 466 |
$typed_term = trim($typed_term);
|
| 467 |
$terms = cmt_get_term_by_name($typed_term, $vid);
|
| 468 |
if (count($terms)) { // we found a match
|
| 469 |
if (count($terms) > 1) drupal_set_message("There is more than one term by the name '$typed_term'. We will use the first one (ok, we're choosing one at random as far as you're concerned), but hope to provide an interface for choosing the one you want to use soon.", 'status');
|
| 470 |
// @TODO: do what the status message above says
|
| 471 |
$term = $terms[0];
|
| 472 |
$tid = $term->tid;
|
| 473 |
/*
|
| 474 |
if (!strcmp($term->name, $typed_term)) {
|
| 475 |
}
|
| 476 |
// actually we want to cast a vote for the typed name, new or not
|
| 477 |
// and that's what the function below does. We need to get the content_id
|
| 478 |
// for the cmt_term_name table entry, and cmt_get_term_by_name uses
|
| 479 |
// cmt_term_data. Performance could surely be improved here.
|
| 480 |
*/
|
| 481 |
// create new name for term honoring user's exact capitalization
|
| 482 |
$tid_array = array(
|
| 483 |
'value' => $tid,
|
| 484 |
);
|
| 485 |
$name_array = array(
|
| 486 |
'value' => $typed_term,
|
| 487 |
'field' => 'name',
|
| 488 |
'type' => "'%s'",
|
| 489 |
);
|
| 490 |
cmt_term_attribute_set($tid_array, $name_array, 'cmt_term_name', $vid);
|
| 491 |
}
|
| 492 |
else { // no match
|
| 493 |
// get a term ID, insert a new term name, and join to a vocabulary
|
| 494 |
$cmt_term = cmt_term_create($typed_term, $vid);
|
| 495 |
$tid = $cmt_term['tid'];
|
| 496 |
}
|
| 497 |
if ($i == $count) { // last term in set
|
| 498 |
// needs to be associated with the node in question
|
| 499 |
$nid_array = array(
|
| 500 |
'value' => $form_values['nid'],
|
| 501 |
'field' => 'nid',
|
| 502 |
);
|
| 503 |
$tid_array = array(
|
| 504 |
'value' => $tid,
|
| 505 |
'field' => 'tid',
|
| 506 |
);
|
| 507 |
cmt_term_attribute_set($nid_array, $tid_array, 'cmt_term_node', $vid);
|
| 508 |
}
|
| 509 |
// set hierarchy (parent)
|
| 510 |
$tid_array = array(
|
| 511 |
'value' => $tid,
|
| 512 |
);
|
| 513 |
$pid_array = array(
|
| 514 |
'value' => $pid,
|
| 515 |
'field' => 'parent',
|
| 516 |
);
|
| 517 |
cmt_term_attribute_set($tid_array, $pid_array, 'cmt_term_hierarchy', $vid);
|
| 518 |
$pid = $tid;
|
| 519 |
$i++;
|
| 520 |
}
|
| 521 |
}
|
| 522 |
// return not needed unless you're going somewhere else
|
| 523 |
// return array('node/'. $form_values['nid']);
|
| 524 |
}
|
| 525 |
|
| 526 |
function cmt_new_term_full_form_submit($form_id, $form_values) {
|
| 527 |
$vid = $form_values['vid'];
|
| 528 |
$tid = array(
|
| 529 |
'value' => $form_values['tid'],
|
| 530 |
);
|
| 531 |
$description = array(
|
| 532 |
'value' => $form_values['description'],
|
| 533 |
'field' => 'description',
|
| 534 |
'type' => "'%s'",
|
| 535 |
);
|
| 536 |
cmt_term_attribute_set($tid, $description, 'cmt_term_description', $vid);
|
| 537 |
}
|
| 538 |
|
| 539 |
/**
|
| 540 |
* Create a CMT term with an initial name and acquire a tid, and vote for that
|
| 541 |
* name and assign the term to a vocabulary
|
| 542 |
*
|
| 543 |
* This does NOT check if it already exists. You have to have done that
|
| 544 |
* already, and you'll have to add the parent afterward.
|
| 545 |
*
|
| 546 |
* @param $name
|
| 547 |
* Name of the term to create.
|
| 548 |
* @param $vid
|
| 549 |
* A vocabulary vid to assign the term to.
|
| 550 |
*
|
| 551 |
* @return
|
| 552 |
* An array with the term ID (tid)
|
| 553 |
* (The content_id has already been used to vote and isn't needed,
|
| 554 |
* but an array gives us easier future expansion.)
|
| 555 |
*/
|
| 556 |
function cmt_term_create($name, $vid, $vote = 1, $uid = NULL) {
|
| 557 |
// in which cmt_term_data becomes much more than a denormalised table
|
| 558 |
// and becomes the keeper of the CMT tid sequence
|
| 559 |
$result = db_query("INSERT INTO {cmt_term_data} SET name = '%s'", $name);
|
| 560 |
if (!$result) {
|
| 561 |
drupal_set_error("Creating new term (inserting int cmt_term_data to get a tid) failed.");
|
| 562 |
return FALSE;
|
| 563 |
}
|
| 564 |
$tid = cmt_db_last_insert_id('cmt_term_data', 'tid');
|
| 565 |
drupal_set_message("TID: " . $tid, 'status');
|
| 566 |
$tid_array = array(
|
| 567 |
'value' => $tid,
|
| 568 |
);
|
| 569 |
$name_array = array(
|
| 570 |
'value' => $name,
|
| 571 |
'field' => 'name',
|
| 572 |
'type' => "'%s'",
|
| 573 |
);
|
| 574 |
// 4th parameter "TRUE" to turn off a redundant SQL check
|
| 575 |
cmt_term_attribute_set($tid_array, $name_array, 'cmt_term_name', $vid, TRUE);
|
| 576 |
|
| 577 |
// all new terms get attached to a vocabulary, so we'll put that in here and ignore it forevermore
|
| 578 |
cmt_term_vocab_set($tid, $vid, TRUE); // set $new to TRUE: we just created the tid
|
| 579 |
$cmt_term = array(
|
| 580 |
'tid' => $tid,
|
| 581 |
);
|
| 582 |
return $cmt_term;
|
| 583 |
}
|
| 584 |
|
| 585 |
/**
|
| 586 |
* Saves a community-managed term's relationship to a vocabulary.
|
| 587 |
*
|
| 588 |
* cmt_term_vocab_set is different from the cmt_term_attribute_set function
|
| 589 |
* because we do not use votingapi. Users cannot vote on what vocabulary a
|
| 590 |
* term belongs in at this time! We still need to tie a term to a vocab.
|
| 591 |
*
|
| 592 |
* @param $tid
|
| 593 |
* A CMT term ID.
|
| 594 |
* @param $vid
|
| 595 |
* A vocabulary ID of a vocabulary that is community-managed.
|
| 596 |
* @param $new
|
| 597 |
* An optional flag to skip the if-existing check
|
| 598 |
*
|
| 599 |
* @return
|
| 600 |
* A database query result resource or TRUE if the term is already assigned
|
| 601 |
* to the vocabulary; FALSE if the query was not executed correctly.
|
| 602 |
*/
|
| 603 |
function cmt_term_vocab_set($tid, $vid, $new = FALSE) {
|
| 604 |
// we won't need this to fetch the content_id, but we will for the others
|
| 605 |
if ($new || !$result = db_query("SELECT * FROM {cmt_term_vocab} WHERE tid=%d AND vid=%d", $tid, $vid)) {
|
| 606 |
// returns a resource ID or FALSE - a resource ID is as good as TRUE, right?
|
| 607 |
return db_query("INSERT INTO {cmt_term_vocab} (tid, vid) VALUES (%d, %d)", $tid, $vid);
|
| 608 |
// no reason to check against a term being in multiple vocabularies
|
| 609 |
// not that this should be able to happen in the current setup
|
| 610 |
}
|
| 611 |
else return TRUE; // it's already in there
|
| 612 |
}
|
| 613 |
|
| 614 |
/**
|
| 615 |
* Saves a community-managed term's attribute and updates votingAPI.
|
| 616 |
*
|
| 617 |
* First it checks to make sure the attribute doesn't already exist for
|
| 618 |
* this term (as unlikely as that might be), and if it does, uses that
|
| 619 |
* content_id rather than inserting a new row.
|
| 620 |
*
|
| 621 |
* @param $id
|
| 622 |
* An array with a 'value' set to the CMT term ID or node ID, and
|
| 623 |
* optionally a field name other than tid
|
| 624 |
* @param $attribute
|
| 625 |
* An array with the attribute for that CMT term, including:
|
| 626 |
* 'value' which can be a numeric type, longtext
|
| 627 |
* 'field' the field name that will be saved
|
| 628 |
* 'type' (optional) if not %d, define how db_query should take this
|
| 629 |
* @param $content_type
|
| 630 |
* A string of the content_type, also the cmt_term_* table name
|
| 631 |
* @param $new
|
| 632 |
* Optional parameter to assert a new insert, skip the SQL select
|
| 633 |
* @param $value
|
| 634 |
* Optional vote value if more than 1
|
| 635 |
* @param $uid
|
| 636 |
* Optional uid if not active user
|
| 637 |
*
|
| 638 |
* @return
|
| 639 |
* INSERT if a new description, UPDATE if the description was already
|
| 640 |
* proposed for the term, and FALSE if a query failed.
|
| 641 |
*/
|
| 642 |
function cmt_term_attribute_set($id, $attribute, $content_type, $vid, $new = FALSE, $value = 1, $uid = NULL) {
|
| 643 |
// set defaults
|
| 644 |
if (!$id['field']) $id['field'] = 'tid';
|
| 645 |
if (!$attribute['type']) $attribute['type'] = '%d';
|
| 646 |
$return = 'INSERT'; // if not overridden, insert new id-attribute pair
|
| 647 |
if (!$new) {
|
| 648 |
$result = db_query("SELECT content_id FROM {".$content_type."} WHERE ".$id['field']."=%d AND ".$attribute['field']."=".$attribute['type'], $id['value'], $attribute['value']);
|
| 649 |
if (!$result) {
|
| 650 |
drupal_set_error("Select query to check for description failed.", 'error');
|
| 651 |
// how do i do this right? http://agaricdesign.com/throwing-errors-in-drupal-agarics-complete-guide
|
| 652 |
return FALSE;
|
| 653 |
}
|
| 654 |
if ($cmt_term_attribute = db_fetch_object($result)) {
|
| 655 |
// should I check to make sure nothing crazy has happened and there's somehow two rows?
|
| 656 |
$content_id = $cmt_term_attribute->content_id;
|
| 657 |
// this is not a new attribute, we won't insert
|
| 658 |
$return = 'UPDATE';
|
| 659 |
}
|
| 660 |
}
|
| 661 |
if ($return == 'INSERT') {
|
| 662 |
$result = db_query("INSERT INTO {".$content_type."} (".$id['field'].", ".$attribute['field'].") VALUES (%d, ".$attribute['type'].")", $id['value'], $attribute['value']);
|
| 663 |
if (!$result) {
|
| 664 |
drupal_set_error("Inserting new term description failed.");
|
| 665 |
return FALSE;
|
| 666 |
}
|
| 667 |
$content_id = cmt_db_last_insert_id($content_type, 'content_id');
|
| 668 |
}
|
| 669 |
// content_id has been set by select or cmt_db_last_insert_id
|
| 670 |
cmt_vote($content_type, $content_id, $vid);
|
| 671 |
// downstream of cmt_vote will have to throw its own errors if anything goes wrong
|
| 672 |
// this function can't take any more feedback!
|
| 673 |
return $return;
|
| 674 |
}
|
| 675 |
|
| 676 |
/**
|
| 677 |
* Inserts or updates a vote count for a facet of community-managed taxonomy, and
|
| 678 |
* checks to see if votes have passed thresholds or surpassed existing results.
|
| 679 |
*
|
| 680 |
* This function accepts a content_type, content_id, and user object (defaults to current)
|
| 681 |
* optionally it can also take a value, the default is one
|
| 682 |
* It calls votingapi
|
| 683 |
*/
|
| 684 |
function cmt_vote($content_type, $content_id, $vid, $value = 1, $uid = NULL) {
|
| 685 |
$vote = new stdClass();
|
| 686 |
$vote->tag = 'vote';
|
| 687 |
$vote->value_type = 'points';
|
| 688 |
$vote->value = $value;
|
| 689 |
$votingapi_cache = votingapi_set_vote($content_type, $content_id, $vote, $uid);
|
| 690 |
return cmt_push($content_type, $content_id, $votingapi_cache, $vid);
|
| 691 |
}
|
| 692 |
|
| 693 |
/**
|
| 694 |
* Calls the appropriate function check what effect a vote had vis-a-vis
|
| 695 |
* other votes and thresholds, and adjusts official taxonomy as needed.
|
| 696 |
*/
|
| 697 |
function cmt_push($content_type, $content_id, $votingapi_cache, $vid) {
|
| 698 |
// will not be necessary every call so will be moved later
|
| 699 |
$high_content = cmt_get_high($content_id, $vid);
|
| 700 |
$votes = cmt_get_votes($content_type, $content_id);
|
| 701 |
$threshold = cmt_get_threshold($content_type, $vid);
|
| 702 |
switch ($content_type) {
|
| 703 |
case 'cmt_term_description':
|
| 704 |
return cmt_term_description_push($content_id);
|
| 705 |
case 'cmt_term_name':
|
| 706 |
if($votes > $threshold) {
|
| 707 |
return cmt_term_name_push($content_id);
|
| 708 |
}
|
| 709 |
break;
|
| 710 |
case 'cmt_term_node':
|
| 711 |
if($content_id == $high_content && $votes > $threshold) {
|
| 712 |
return cmt_term_node_push($content_id);
|
| 713 |
}
|
| 714 |
break;
|
| 715 |
}
|
| 716 |
}
|
| 717 |
|
| 718 |
function cmt_term_description_push($content_id) {
|
| 719 |
// there can be only one description, makes the SQL simpler
|
| 720 |
agaric_pr($votingapi_cache);
|
| 721 |
}
|
| 722 |
|
| 723 |
function cmt_term_name_push($content_id) {
|
| 724 |
}
|
| 725 |
|
| 726 |
function cmt_term_node_push($content_id) {
|
| 727 |
|
| 728 |
}
|
| 729 |
|
| 730 |
/**
|
| 731 |
* A simple helper function that returns a single cached voting result.
|
| 732 |
*
|
| 733 |
* @param $content_type
|
| 734 |
* A string identifying the type of content (cmt_term_*) whose votes are
|
| 735 |
* being retrieved.
|
| 736 |
* @param $content_id
|
| 737 |
* The key ID of the content whose votes are being retrieved.
|
| 738 |
* @return
|
| 739 |
* A single votes result.
|
| 740 |
*/
|
| 741 |
function cmt_get_votes($content_type, $content_id) {
|
| 742 |
$result = db_query("SELECT value FROM {votingapi_cache} WHERE content_type='%s' AND content_id=%d AND value_type='points' AND tag='vote' AND function='sum'", $content_type, $content_id);
|
| 743 |
$voting_results = db_fetch_object($result);
|
| 744 |
return $voting_results->value;
|
| 745 |
}
|
| 746 |
|
| 747 |
/**
|
| 748 |
* Get highest vote for a content type
|
| 749 |
*
|
| 750 |
* Big old logical flaw. We need to know the highest vote *by vocabulary*
|
| 751 |
* We'll need to add an inner join to cmt_term_* on content_id
|
| 752 |
* and to cmt_term_vocab on tid to get the vocab
|
| 753 |
*/
|
| 754 |
function cmt_get_high($content_type, $vid, $limit = 1) {
|
| 755 |
$result = db_query("SELECT content_id FROM {votingapi_cache} v WHERE content_type='%s' AND value_type='points' AND tag='vote' AND function='sum' ORDER BY value DESC LIMIT %d", $content_type, $limit);
|
| 756 |
$voting_results = db_fetch_object($result);
|
| 757 |
return $voting_results->content_id;
|
| 758 |
}
|
| 759 |
|
| 760 |
/**
|
| 761 |
* Get thresholds for votes.
|
| 762 |
*
|
| 763 |
* Static function so multiple calls are not a performance hit.
|
| 764 |
*/
|
| 765 |
function cmt_get_threshold($content_type, $vid) {
|
| 766 |
static $thresholds = array();
|
| 767 |
if (!isset($thresholds[$content_type][$vid])) {
|
| 768 |
$threshold = substr_replace($content_type, '', 4, 5) . '_th';
|
| 769 |
$c = db_query("SELECT %s FROM {cmt_vocabulary} WHERE vid = %d", $threshold, $vid);
|
| 770 |
$results = db_fetch_object($c);
|
| 771 |
$thresholds[$content_type][$vid] = $results->$threshold;
|
| 772 |
}
|
| 773 |
return $thresholds[$content_type];
|
| 774 |
}
|
| 775 |
|
| 776 |
/******************************************************************************
|
| 777 |
* Utility, helper and wrapper functions
|
| 778 |
*/
|
| 779 |
|
| 780 |
/**
|
| 781 |
* Return an array of all vocabulary objects for community-managed
|
| 782 |
* vocabularies.
|
| 783 |
*
|
| 784 |
* @param $cmt_enabled
|
| 785 |
* Return only those CMT vocabularies which are either 'enabled' or
|
| 786 |
* 'disabled'. Default both.
|
| 787 |
*/
|
| 788 |
function cmt_get_vocabularies($cmt_enabled = 'both') {
|
| 789 |
$where = "";
|
| 790 |
if ($cmt_enabled == 'enabled') $where = "WHERE cv.cmt_enabled > 0 ";
|
| 791 |
elseif ($cmt_enabled == 'disabled') $where = "WHERE cv.cmt_enabled = 0 ";
|
| 792 |
$c = db_query(db_rewrite_sql("SELECT v.*, n.type FROM {vocabulary} v INNER JOIN {cmt_vocabulary} cv ON v.vid = cv.vid LEFT JOIN {vocabulary_node_types} n ON v.vid = n.vid $where ORDER BY v.weight, v.name", 'v', 'vid'), $where);
|
| 793 |
// below identical to taxonomy_get_vocabularies
|
| 794 |
// couldn't we - and taxonomy core - just use taxonomy_get_vocabulary in a loop?
|
| 795 |
$vocabularies = array();
|
| 796 |
$node_types = array();
|
| 797 |
while ($voc = db_fetch_object($c)) {
|
| 798 |
$node_types[$voc->vid][] = $voc->type;
|
| 799 |
unset($voc->type);
|
| 800 |
$voc->nodes = $node_types[$voc->vid];
|
| 801 |
$vocabularies[$voc->vid] = $voc;
|
| 802 |
}
|
| 803 |
return $vocabularies;
|
| 804 |
}
|
| 805 |
|
| 806 |
/**
|
| 807 |
* Return an array of vocabulary objects for enabled community-managed
|
| 808 |
* vocabularies applicable to a given node type.
|
| 809 |
*
|
| 810 |
* Static by node nid for performance.
|
| 811 |
* Vocabulary objects do not include node types.
|
| 812 |
*/
|
| 813 |
function cmt_get_vocabularies_by_node($nid) {
|
| 814 |
static $vocabularies = array();
|
| 815 |
if (!array_key_exists($nid, $vocabularies)) {
|
| 816 |
$node = node_load($nid);
|
| 817 |
$c = db_query(db_rewrite_sql("SELECT v.* FROM {vocabulary} v INNER JOIN {cmt_vocabulary} cv ON v.vid = cv.vid INNER JOIN {vocabulary_node_types} n ON v.vid = n.vid WHERE n.type = '%s' AND cv.cmt_enabled > 0 ORDER BY v.weight, v.name", 'v', 'vid'), $node->type);
|
| 818 |
while ($voc = db_fetch_object($c)) {
|
| 819 |
$vocabularies[$nid][$voc->vid] = $voc;
|
| 820 |
}
|
| 821 |
}
|
| 822 |
return $vocabularies[$nid];
|
| 823 |
}
|
| 824 |
|
| 825 |
/**
|
| 826 |
* Return CMT settings information for the vocabulary object matching a vocabulary ID.
|
| 827 |
*
|
| 828 |
* @param $vid
|
| 829 |
* The vocabulary's ID
|
| 830 |
*
|
| 831 |
* @return Object
|
| 832 |
* The vocabulary object with all of its metadata.
|
| 833 |
* Results are statically cached.
|
| 834 |
*/
|
| 835 |
function cmt_get_vocabulary($vid) {
|
| 836 |
static $cmt_vocabularies = array();
|
| 837 |
if (!array_key_exists($vid, $cmt_vocabularies)) {
|
| 838 |
$result = db_query('SELECT c.* FROM {cmt_vocabulary} c WHERE c.vid = %d', $vid);
|
| 839 |
$cmt_vocabularies[$vid] = (array)db_fetch_object($result);
|
| 840 |
}
|
| 841 |
return $cmt_vocabularies[$vid];
|
| 842 |
}
|
| 843 |
|
| 844 |
/**
|
| 845 |
* Get basic term data for a given tid.
|
| 846 |
*/
|
| 847 |
function cmt_get_term($tid) {
|
| 848 |
return db_fetch_object(db_query("SELECT t.* FROM {cmt_term_data} t WHERE tid = %d", $tid));
|
| 849 |
}
|
| 850 |
/**
|
| 851 |
* Try to map a string to an existing CMT term.
|
| 852 |
*
|
| 853 |
* Provides a case-insensitive and trimmed mapping, to maximize the
|
| 854 |
* likelihood of a successful match.
|
| 855 |
*
|
| 856 |
* Modeled on taxonomy_get_term_by_name, but with the enhancement of taking an
|
| 857 |
* optional vocabulary ID (vid) to restrict the searching up front
|
| 858 |
* db_rewrite_sql not used, anyone trying to do access control on proposed cmt
|
| 859 |
* terms will have to go through me first
|
| 860 |
*
|
| 861 |
* @param name
|
| 862 |
* Name of the cmt term to search for.
|
| 863 |
*
|
| 864 |
* @return
|
| 865 |
* An array of matching cmt term objects.
|
| 866 |
*/
|
| 867 |
function cmt_get_term_by_name($name, $vid = NULL) {
|
| 868 |
if ($vid) {
|
| 869 |
$db_result = db_query("SELECT t.tid, t.* FROM {cmt_term_data} t INNER JOIN {cmt_term_vocab} v ON t.tid = v.tid WHERE v.vid=%d AND LOWER('%s') LIKE LOWER(t.name)", $vid, trim($name));
|
| 870 |
}
|
| 871 |
else {
|
| 872 |
$db_result = db_query("SELECT t.tid, t.* FROM {cmt_term_data} t WHERE LOWER('%s') LIKE LOWER(t.name)", trim($name));
|
| 873 |
}
|
| 874 |
$result = array();
|
| 875 |
while ($term = db_fetch_object($db_result)) {
|
| 876 |
$result[] = $term;
|
| 877 |
}
|
| 878 |
return $result;
|
| 879 |
}
|
| 880 |
|
| 881 |
/**
|
| 882 |
* Find all parents of a given term ID.
|
| 883 |
*/
|
| 884 |
function cmt_get_parents($tid, $key = 'tid') {
|
| 885 |
if ($tid) {
|
| 886 |
// need object, so no good: 'SELECT parent FROM {cmt_term_hierarchy} WHERE tid = %d'
|
| 887 |
$result = db_query('SELECT t.tid, t.* FROM {cmt_term_data} t INNER JOIN {cmt_term_hierarchy} h ON h.parent = t.tid WHERE h.tid = %d ORDER BY h.vote DESC, weight, name', $tid);
|
| 888 |
$parents = array();
|
| 889 |
while ($parent = db_fetch_object($result)) {
|
| 890 |
$parents[$parent->$key] = $parent;
|
| 891 |
}
|
| 892 |
return $parents;
|
| 893 |
}
|
| 894 |
else {
|
| 895 |
return array();
|
| 896 |
}
|
| 897 |
}
|
| 898 |
|
| 899 |
/**
|
| 900 |
*
|
| 901 |
*
|
| 902 |
* Extremely loosely based on taxonomy_form_term_submit($form_id, $form_values)
|
| 903 |
* "Accept the form submission for a taxonomy term and save the result."
|
| 904 |
*
|
| 905 |
* The fundamental questions all people must ask themselves at one point in their lives:
|
| 906 |
* What is the core nature of a taxonomy term?
|
| 907 |
* What is the minimum aspects of the term necessary before we push it live?
|
| 908 |
* According to this taxonomy_form_term_submit:
|
| 909 |
* tid
|
| 910 |
* name
|
| 911 |
* description (we know that's optional)
|
| 912 |
* weight (can default to 0)
|
| 913 |
* relation (optional)
|
| 914 |
* hierarchy (0 if nothing else)
|
| 915 |
* synonym
|
| 916 |
*/
|
| 917 |
function cmt_expose_term(&$form_values) {
|
| 918 |
if ($form_values['tid'] && $form_values['name']) {
|
| 919 |
db_query("UPDATE {term_data} SET name = '%s', description = '%s', weight = %d WHERE tid = %d", $form_values['name'], $form_values['description'], $form_values['weight'], $form_values['tid']);
|
| 920 |
$hook = 'update';
|
| 921 |
$status = SAVED_UPDATED;
|
| 922 |
}
|
| 923 |
else if ($form_values['tid']) {
|
| 924 |
return taxonomy_del_term($form_values['tid']);
|
| 925 |
}
|
| 926 |
else {
|
| 927 |
$form_values['tid'] = db_next_id('{term_data}_tid');
|
| 928 |
db_query("INSERT INTO {term_data} (tid, name, description, vid, weight) VALUES (%d, '%s', '%s', %d, %d)", $form_values['tid'], $form_values['name'], $form_values['description'], $form_values['vid'], $form_values['weight']);
|
| 929 |
$hook = 'insert';
|
| 930 |
$status = SAVED_NEW;
|
| 931 |
}
|
| 932 |
db_query('DELETE FROM {term_relation} WHERE tid1 = %d OR tid2 = %d', $form_values['tid'], $form_values['tid']);
|
| 933 |
if ($form_values['relations']) {
|
| 934 |
foreach ($form_values['relations'] as $related_id) {
|
| 935 |
if ($related_id != 0) {
|
| 936 |
db_query('INSERT INTO {term_relation} (tid1, tid2) VALUES (%d, %d)', $form_values['tid'], $related_id);
|
| 937 |
}
|
| 938 |
}
|
| 939 |
}
|
| 940 |
|
| 941 |
db_query('DELETE FROM {term_hierarchy} WHERE tid = %d', $form_values['tid']);
|
| 942 |
if (!isset($form_values['parent']) || empty($form_values['parent'])) {
|
| 943 |
$form_values['parent'] = array(0);
|
| 944 |
}
|
| 945 |
if (is_array($form_values['parent'])) {
|
| 946 |
foreach ($form_values['parent'] as $parent) {
|
| 947 |
if (is_array($parent)) {
|
| 948 |
foreach ($parent as $tid) {
|
| 949 |
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $tid);
|
| 950 |
}
|
| 951 |
}
|
| 952 |
else {
|
| 953 |
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $parent);
|
| 954 |
}
|
| 955 |
}
|
| 956 |
}
|
| 957 |
else {
|
| 958 |
db_query('INSERT INTO {term_hierarchy} (tid, parent) VALUES (%d, %d)', $form_values['tid'], $form_values['parent']);
|
| 959 |
}
|
| 960 |
|
| 961 |
db_query('DELETE FROM {term_synonym} WHERE tid = %d', $form_values['tid']);
|
| 962 |
if ($form_values['synonyms']) {
|
| 963 |
foreach (explode ("\n", str_replace("\r", '', $form_values['synonyms'])) as $synonym) {
|
| 964 |
if ($synonym) {
|
| 965 |
db_query("INSERT INTO {term_synonym} (tid, name) VALUES (%d, '%s')", $form_values['tid'], chop($synonym));
|
| 966 |
}
|
| 967 |
}
|
| 968 |
}
|
| 969 |
|
| 970 |
if (isset($hook)) {
|
| 971 |
module_invoke_all('taxonomy', $hook, 'term', $form_values);
|
| 972 |
}
|
| 973 |
|
| 974 |
cache_clear_all();
|
| 975 |
|
| 976 |
return $status;
|
| 977 |
}
|
| 978 |
|
| 979 |
|
| 980 |
|
| 981 |
/**
|
| 982 |
* Create a hierarchical representation of a vocabulary.
|
| 983 |
*
|
| 984 |
* @param $vid
|
| 985 |
* Which vocabulary to generate the tree for.
|
| 986 |
*
|
| 987 |
* @param $parent
|
| 988 |
* The term ID under which to generate the tree. If 0, generate the tree
|
| 989 |
* for the entire vocabulary.
|
| 990 |
*
|
| 991 |
* @param $depth
|
| 992 |
* Internal use only.
|
| 993 |
*
|
| 994 |
* @param $max_depth
|
| 995 |
* The number of levels of the tree to return. Leave NULL to return all levels.
|
| 996 |
*
|
| 997 |
* @return
|
| 998 |
* An array of all term objects in the tree. Each term object is extended
|
| 999 |
* to have "depth" and "parents" attributes in addition to its normal ones.
|
| 1000 |
* Results are statically cached.
|
| 1001 |
*/
|
| 1002 |
function cmt_get_tree($vid, $parent = 0, $depth = -1, $max_depth = NULL) {
|
| 1003 |
static $children, $parents, $terms;
|
| 1004 |
$depth++;
|
| 1005 |
// We cache trees, so it's not CPU-intensive to call get_tree() on a term
|
| 1006 |
// and its children, too.
|
| 1007 |
if (!isset($children[$vid])) {
|
| 1008 |
$children[$vid] = array();
|
| 1009 |
$result = db_query('SELECT t.tid, t.*, parent FROM {cmt_term_data} t INNER JOIN {cmt_term_vocab} v ON t.tid = v.tid INNER JOIN {cmt_term_hierarchy} h ON t.tid = h.tid WHERE v.vid = %d ORDER BY weight, name', $vid);
|
| 1010 |
while ($term = db_fetch_object($result)) {
|
| 1011 |
$children[$vid][$term->parent][] = $term->tid;
|
| 1012 |
$parents[$vid][$term->tid][] = $term->parent;
|
| 1013 |
$terms[$vid][$term->tid] = $term;
|
| 1014 |
}
|
| 1015 |
}
|
| 1016 |
$max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
|
| 1017 |
if ($children[$vid][$parent]) {
|
| 1018 |
foreach ($children[$vid][$parent] as $child) {
|
| 1019 |
if ($max_depth > $depth) {
|
| 1020 |
$term = drupal_clone($terms[$vid][$child]);
|
| 1021 |
$term->depth = $depth;
|
| 1022 |
// The "parent" attribute is not useful, as it would show one parent only.
|
| 1023 |
unset($term->parent);
|
| 1024 |
$term->parents = $parents[$vid][$child];
|
| 1025 |
$tree[] = $term;
|
| 1026 |
if ($children[$vid][$child]) {
|
| 1027 |
$tree = array_merge($tree, cmt_get_tree($vid, $child, $depth, $max_depth));
|
| 1028 |
}
|
| 1029 |
}
|
| 1030 |
}
|
| 1031 |
}
|
| 1032 |
return $tree ? $tree : array();
|
| 1033 |
}
|
| 1034 |
|
| 1035 |
/**
|
| 1036 |
* Create a hierarchical representation of a vocabulary.
|
| 1037 |
*
|
| 1038 |
* @param $vid
|
| 1039 |
* Which vocabulary to generate the tree for.
|
| 1040 |
*
|
| 1041 |
* @param $parent
|
| 1042 |
* The term ID under which to generate the tree. If 0, generate the tree
|
| 1043 |
* for the entire vocabulary.
|
| 1044 |
*
|
| 1045 |
* @param $depth
|
| 1046 |
* Internal use only.
|
| 1047 |
*
|
| 1048 |
* @param $max_depth
|
| 1049 |
* The number of levels of the tree to return. Leave NULL to return all levels.
|
| 1050 |
*
|
| 1051 |
* @return
|
| 1052 |
* An array of all term objects in the tree. Each term object is extended
|
| 1053 |
* to have "depth" and "parents" attributes in addition to its normal ones.
|
| 1054 |
* Results are statically cached.
|
| 1055 |
*/
|
| 1056 |
function cmt_get_roots($vid, $tid = 0, $depth = -1, $max_depth = NULL) {
|
| 1057 |
static $ancestors, $parents, $terms;
|
| 1058 |
$depth++;
|
| 1059 |
// We cache trees, so it's not CPU-intensive to call get_tree() on a term
|
| 1060 |
// and its children, too.
|
| 1061 |
if (TRUE || !isset($ancestors[$vid][$tid])) {
|
| 1062 |
$ancestors[$vid][$tid] = array();
|
| 1063 |
$result = db_query('SELECT t.tid, t.*, parent FROM {cmt_term_data} t INNER JOIN {cmt_term_vocab} v ON t.tid = v.tid INNER JOIN {cmt_term_hierarchy} h ON t.tid = h.tid WHERE t.tid = %d AND v.vid = %d ORDER BY weight, name', $tid, $vid);
|
| 1064 |
while ($term = db_fetch_object($result)) {
|
| 1065 |
$ancestors[$vid][$tid][] = $term->parent;
|
| 1066 |
/*
|
| 1067 |
$children[$vid][$tid][$term->parent][] = $term->tid;
|
| 1068 |
$parents[$vid][$term->tid][] = $term->parent;
|
| 1069 |
$terms[$vid][$term->tid] = $term;
|
| 1070 |
*/
|
| 1071 |
}
|
| 1072 |
}
|
| 1073 |
$max_depth = (is_null($max_depth)) ? count($ancestors[$vid][$tid]) : $max_depth;
|
| 1074 |
if ($children[$vid][$parent]) {
|
| 1075 |
foreach ($children[$vid][$parent] as $child) {
|
| 1076 |
if ($max_depth > $depth) {
|
| 1077 |
$term = drupal_clone($terms[$vid][$child]);
|
| 1078 |
$term->depth = $depth;
|
| 1079 |
// The "parent" attribute is not useful, as it would show one parent only.
|
| 1080 |
unset($term->parent);
|
| 1081 |
$term->parents = $parents[$vid][$child];
|
| 1082 |
$roots[] = $term;
|
| 1083 |
if ($children[$vid][$child]) {
|
| 1084 |
$roots = array_merge($roots, cmt_get_roots($vid, $parent, $depth, $max_depth));
|
| 1085 |
}
|
| 1086 |
}
|
| 1087 |
}
|
| 1088 |
}
|
| 1089 |
return $roots ? $roots : array();
|
| 1090 |
}
|
| 1091 |
|
| 1092 |
function cmt_get_ancestors($tid, $vid, &$ancestors, $stack=array(), $thread = 0) {
|
| 1093 |
$result = db_query('SELECT t.tid, t.*, parent FROM {cmt_term_data} t INNER JOIN {cmt_term_vocab} v ON t.tid = v.tid INNER JOIN {cmt_term_hierarchy} h ON t.tid = h.tid WHERE t.tid = %d AND v.vid = %d ORDER BY weight, name', $tid, $vid);
|
| 1094 |
$parents = array();
|
| 1095 |
while ($term = db_fetch_object($result)) {
|
| 1096 |
$parents[] = $term->parent;
|
| 1097 |
}
|
| 1098 |
// agaric_a($parents);
|
| 1099 |
if (count($parents) == 0) {
|
| 1100 |
$ancestors[$thread] = $stack;
|
| 1101 |
$stack = array();
|
| 1102 |
$thread++;
|
| 1103 |
}
|
| 1104 |
else {
|
| 1105 |
foreach ($parents as $parent) {
|
| 1106 |
$stack[] = cmt_get_ancestors($parent, $vid, $ancestors, $stack, $thread);
|
| 1107 |
}
|
| 1108 |
}
|
| 1109 |
return $parents;
|
| 1110 |
}
|
| 1111 |
|
| 1112 |
function cmt_brian($tid) {
|
| 1113 |
|
| 1114 |
}
|
| 1115 |
|
| 1116 |
/**
|
| 1117 |
* I am the funkiest function. ... and I don't work. Anyone want to explain why?
|
| 1118 |
*/
|
| 1119 |
function cmt_get_hierarchies($tid) {
|
| 1120 |
$term = cmt_get_term($tid);
|
| 1121 |
$hierarchies = array();
|
| 1122 |
$hierarchies[0] = array(0 => $term);
|
| 1123 |
$root = 0;
|
| 1124 |
$h = count($hierarchies);
|
| 1125 |
for ($i = 0; $i < $h; $i++) {
|
| 1126 |
if ($hierarchies[$i][count($hierarchies[$i])-1] == "root") {
|
| 1127 |
$root++;
|
| 1128 |
if($root == $h) {
|
| 1129 |
// for($k=0;$k<$h;$k++) {
|
| 1130 |
// $hierarchies[$k] = array_reverse($hierarchies[$k]);
|
| 1131 |
// }
|
| 1132 |
return $hierarchies;
|
| 1133 |
}
|
| 1134 |
}
|
| 1135 |
else {
|
| 1136 |
$parents = array_values(cmt_get_parents($hierarchies[$i][count($hierarchies[$i])-1]->tid));
|
| 1137 |
// agaric_a($parents);
|
| 1138 |
$c = count($parents);
|
| 1139 |
if ($c > 1) $copy = $hierarchies[$i];
|
| 1140 |
if (!$parents[0]) $parents[0] = "root";
|
| 1141 |
$hierarchies[$i][] = $parents[0];
|
| 1142 |
for ($j = 1; $j < $c; $j++) {
|
| 1143 |
$hierarchies[$i+$j] = $copy;
|
| 1144 |
if(!$parents[$j]) $parents[$j] = "root";
|
| 1145 |
$hierarchies[$i+$j][] = $parents[$j];
|
| 1146 |
}
|
| 1147 |
if ($hierarchies[$i][0] == "root") unset($hierarchies[$i]);
|
| 1148 |
$h = count($hierarchies);
|
| 1149 |
$i = 0;
|
| 1150 |
}
|
| 1151 |
}
|
| 1152 |
}
|
| 1153 |
|
| 1154 |
/**
|
| 1155 |
* Find all ancestors of a given term ID.
|
| 1156 |
*/
|
| 1157 |
function cmt_get_parents_all($tid) {
|
| 1158 |
$parents = array();
|
| 1159 |
if ($tid) {
|
| 1160 |
$parents[] = cmt_get_term($tid);
|
| 1161 |
$n = 0;
|
| 1162 |
while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
|
| 1163 |
$parents = array_merge($parents, $parent);
|
| 1164 |
$n++;
|
| 1165 |
}
|
| 1166 |
}
|
| 1167 |
return $parents;
|
| 1168 |
}
|
| 1169 |
|
| 1170 |
/**
|
| 1171 |
* Return true if a vocabulary ID belongs to a community-managed vocabulary.
|
| 1172 |
*
|
| 1173 |
* I used this a few times and it could probably be more efficient as a direct
|
| 1174 |
* query, so let's wrap it up for easy optimization later.
|
| 1175 |
*/
|
| 1176 |
function cmt_is_vid_cmt($vid) {
|
| 1177 |
return in_array($vid, array_keys(cmt_get_vocabularies()));
|
| 1178 |
}
|
| 1179 |
|
| 1180 |
/**
|
| 1181 |
* Returns the last insert id.
|
| 1182 |
*
|
| 1183 |
* The need for this will go away in Drupal 6
|
| 1184 |
*
|
| 1185 |
* @param table
|
| 1186 |
* The name of the table you inserted into.
|
| 1187 |
* @param field
|
| 1188 |
* The name of the autoincrement field.
|
| 1189 |
*/
|
| 1190 |
function cmt_db_last_insert_id($table, $field) {
|
| 1191 |
switch ($GLOBALS['db_type']) {
|
| 1192 |
case 'mysql':
|
| 1193 |
case 'mysqli':
|
| 1194 |
return db_result(db_query('SELECT LAST_INSERT_ID()'));
|
| 1195 |
break;
|
| 1196 |
case 'pgsql':
|
| 1197 |
// from http://api.drupal.org/api/file/includes/database.pgsql.inc/6/source
|
| 1198 |
return db_result(db_query("SELECT currval('%s_seq')", db_prefix_tables('{'. $table .'}') . '_'. $field));
|
| 1199 |
break;
|
| 1200 |
default:
|
| 1201 |
return drupal_set_message(t('Database type @type not supported', array('@type' => $GLOBALS['db_type'])), 'error');
|
| 1202 |
}
|
| 1203 |
}
|
| 1204 |
|
| 1205 |
/* to be split out into an agaric devel module */
|
| 1206 |
function agaric_pr($array) {
|
| 1207 |
print '<pre>';
|
| 1208 |
print_r($array);
|
| 1209 |
print '</pre>';
|
| 1210 |
}
|
| 1211 |
|
| 1212 |
function agaric_a($array) {
|
| 1213 |
drupal_set_message('<pre>' . print_r($array, true) . '</pre>');
|
| 1214 |
}
|