/[drupal]/contributions/modules/community_tags/community_tags.module
ViewVC logotype

Contents of /contributions/modules/community_tags/community_tags.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.37 - (show annotations) (download) (as text)
Fri Jul 18 03:32:55 2008 UTC (16 months, 1 week ago) by herc
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
Changes since 1.36: +154 -269 lines
File MIME type: text/x-php
Moving back up to date files to HEAD in preparation for 6.x porting.
1 <?php
2 // $Id: community_tags.module,v 1.32.2.6 2008/02/19 10:59:22 owahab Exp $
3
4 /**
5 * Display modes.
6 */
7 define('COMMUNITY_TAGS_MODE_BLOCK', 0);
8 define('COMMUNITY_TAGS_MODE_TAB', 1);
9 define('COMMUNITY_TAGS_MODE_INLINE', 2);
10
11 /**
12 * Implementation of hook_help().
13 */
14 function community_tags_help($section) {
15 switch ($section) {
16 case 'admin/settings/community-tags':
17 return t('To set up community tagging, you must first <a href="@taxonomy">create a normal free tagged vocabulary</a>. Then activate community tagging on such a vocabulary below, and set the <a href="@workflow">workflow options</a> for node types to control how users can tag nodes.', array('@taxonomy' => url('admin/content/taxonomy'), '@workflow' => url('admin/content/types')));
18 break;
19 }
20 }
21
22 /**
23 * Implementation of hook_theme().
24 */
25 function community_tags_theme() {
26 return array(
27 'community_tags_form' => array(
28 'arguments' => array('form' => NULL),
29 'file' => 'community_tags.pages.inc',
30 ),
31 'community_tags' => array(
32 'arguments' => array('tags' => NULL),
33 ),
34 );
35 }
36
37 /**
38 * Implementation of hook_update_index().
39 */
40 function community_tags_update_index() {
41 // Last cron run
42 $last = variable_get('node_cron_last', 0);
43 $limit = (int)variable_get('search_cron_limit', 100);
44
45 $sql = "SELECT nid FROM {community_tags} WHERE date > %d";
46 $result = db_query_range($sql, $last, 0, $limit);
47
48 if ($result && db_num_rows($result) > 0) {
49 while ($data = db_fetch_object($result)) {
50 $node = node_load($data->nid);
51 // Build the node body.
52 $node = node_build_content($node, FALSE, FALSE);
53 $node->body = drupal_render($node->content);
54
55 // Allow modules to modify the fully-built node.
56 node_invoke_nodeapi($node, 'alter');
57
58 $text = '<h1>'. check_plain($node->title) .'</h1>'. $node->body;
59
60 // Fetch extra data normally not visible
61 $extra = node_invoke_nodeapi($node, 'update index');
62 foreach ($extra as $t) {
63 $text .= $t;
64 }
65
66 // Update index
67 search_index($node->nid, 'node', $text);
68 }
69 }
70 }
71
72 /**
73 * Implementation of hook_menu().
74 */
75 function community_tags_menu() {
76 $items = array();
77
78 $items['admin/settings/community-tags'] = array(
79 'title' => 'Community tags',
80 'description' => t('Configure community tagging.'),
81 'page callback' => 'drupal_get_form',
82 'page arguments' => array('community_tags_settings'),
83 'access arguments' => array('administer site configuration'),
84 'file' => 'community_tags.pages.inc',
85 );
86
87 $items['community-tags/js'] = array(
88 'page callback' => 'community_tags_from_js',
89 'access callback' => '_community_tags_menu_access',
90 'type' => MENU_CALLBACK,
91 'file' => 'community_tags.ajax.inc',
92 );
93
94 $items['community-tags/user'] = array(
95 'page callback' => 'community_tags_by_user',
96 'access callback' => '_community_tags_menu_access',
97 'type' => MENU_CALLBACK,
98 );
99
100 $items['node/%node/tag'] = array(
101 'title' => 'Tags',
102 'page callback' => 'community_tags_node_view',
103 'page arguments' => array(1, FALSE),
104 'access callback' => '_community_tags_tag_access',
105 'access arguments' => array(1),
106 'type' => MENU_LOCAL_TASK,
107 'weight' => 2,
108 'file' => 'community_tags.pages.inc',
109 );
110
111 return $items;
112 }
113
114 /**
115 * Resync community_tags table with term_node table, after vocabulary change.
116 */
117 function community_tags_rehash($silent = FALSE) {
118 // Get the community tagged vocabularies.
119 $community_tagged = variable_get('community_tags_vocabularies', array());
120
121 $changed = FALSE;
122
123 // If community tagging is in use, resync.
124 if (count($community_tagged)) {
125 // Delete all community tag relations that are no longer in one of these vocabularies.
126
127 $placeholders = implode(',', array_fill(0, count($community_tagged), '%d'));
128 $result = db_query('SELECT c.tid FROM {community_tags} c
129 INNER JOIN {term_data} d ON c.tid = d.tid
130 WHERE d.vid NOT IN ('. $placeholders .')',
131 $community_tagged);
132 while ($term = db_fetch_object($result)) {
133 db_query('DELETE FROM {community_tags} WHERE tid = %d', $term->tid);
134 $changed = TRUE;
135 }
136
137 // Insert new community tag relations for new tags based on the original node author.
138 $result = db_query('SELECT t.tid, t.nid, n.uid, n.created FROM {term_node} t INNER JOIN {node} n ON t.nid = n.nid LEFT JOIN {community_tags} c ON c.tid = t.tid AND c.nid = t.nid INNER JOIN {term_data} d ON t.tid = d.tid WHERE c.tid IS NULL AND d.vid IN ('. $placeholders .')', $community_tagged);
139 while ($term = db_fetch_object($result)) {
140 db_query('INSERT INTO {community_tags} (tid, nid, uid, date) VALUES (%d, %d, %d, %d)', $term->tid, $term->nid, $term->uid, $term->created);
141 $changed = TRUE;
142 }
143 }
144 else {
145 // Delete all community tag relations.
146 if (db_result(db_query('SELECT COUNT(*) FROM {community_tags}'))) {
147 db_query('DELETE FROM {community_tags}');
148 $changed = TRUE;
149 }
150 }
151
152 if (!$silent && $changed) {
153 drupal_set_message('Your community tags have been updated.');
154 }
155 }
156
157 /**
158 * Implementation of hook_block().
159 */
160 function community_tags_block($op = 'list', $delta = 0, $edit = array()) {
161 global $user;
162 switch ($op) {
163
164 case 'list':
165 $block[0]['info'] = t('Community tagging form');
166 return $block;
167
168 case 'view':
169 if (user_access('access content') && user_access('tag content')) {
170 if (arg(0) == 'node' && is_numeric(arg(1)) && (arg(2) == '' || arg(2) == 'view')) {
171 $node = node_load(arg(1));
172 if ($node && variable_get('community_tags_display_'. $node->type, COMMUNITY_TAGS_MODE_TAB) == COMMUNITY_TAGS_MODE_BLOCK) {
173 $block['subject'] = t('Tag this');
174 $block['content'] = community_tags_node_view($node, TRUE);
175 return $block;
176 }
177 }
178 }
179 break;
180 }
181 }
182
183 /**
184 * Check whether a given node has one or more community tagged vocabularies associated with its type.
185 */
186 function community_tags_vids_for_node($node) {
187 // Allow both nids and nodes
188 if (is_numeric($node)) {
189 $node = node_load($node);
190 }
191
192 $community_tagged = variable_get('community_tags_vocabularies', array());
193 $result = db_query("SELECT vid FROM {vocabulary_node_types} WHERE type = '%s'", $node->type);
194 $vids = array();
195 while ($vid = db_fetch_object($result)) {
196 if (isset($community_tagged[$vid->vid])) {
197 $vids[] = $vid->vid;
198 }
199 }
200 return $vids;
201 }
202
203 /**
204 * Implementation of hook_perm().
205 */
206 function community_tags_perm() {
207 return array('tag content', 'edit own tags');
208 }
209
210 /**
211 * Implementation of hook_nodeapi().
212 */
213 function community_tags_nodeapi(&$node, $op, $teaser) {
214 switch ($op) {
215 case 'load':
216 $node->tags = tagadelic_node_get_terms($node);
217 $node->community_tags_form = variable_get('community_tags_display_'. $node->type, COMMUNITY_TAGS_MODE_TAB) == COMMUNITY_TAGS_MODE_INLINE;
218 break;
219
220 case 'insert':
221 case 'update':
222 // If this node has tags associated with it, we want to save these in
223 // the community_tags table so we can keep track of the count.
224 // When adding a tag from the add/edit node page, the count will always
225 // be one. Only when using a "quick add" form can we increase the count.
226 if (is_array($node->taxonomy['tags'])) {
227 global $user;
228 community_tags_taxonomy_node_save($node, $node->taxonomy, TRUE, $user->uid);
229 }
230 break;
231
232 case 'view':
233 global $user;
234 // Show quick tag form for this node if we're on a node page view and the
235 // form is enabled for this node and the default quick tag vocab is set.
236 if (!$teaser && $node->community_tags_form) {
237 $node->content['community_tags'] = array(
238 '#value' => community_tags_node_view($node, TRUE),
239 '#weight' => 50,
240 );
241 }
242 break;
243 }
244 }
245
246 /**
247 * Implementation of hook_form_alter().
248 */
249 function community_tags_form_alter(&$form, &$form_state, $form_id) {
250 // Provide option to enable Community Tags per node type.
251 if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
252 $modes = array(
253 COMMUNITY_TAGS_MODE_BLOCK => t('Block'),
254 COMMUNITY_TAGS_MODE_TAB => t('Tab'),
255 COMMUNITY_TAGS_MODE_INLINE => t('Inline'),
256 );
257 $form['workflow']['community_tags_display'] = array(
258 '#type' => 'radios',
259 '#title' => t('Community tagging form'),
260 '#default_value' => variable_get('community_tags_display_'. $form['#node_type']->type, COMMUNITY_TAGS_MODE_TAB),
261 '#options' => $modes,
262 '#description' => t('How should users be allowed to tag content?')
263 );
264 }
265 }
266
267 /**
268 * Retrieve list of tags for a given node that belong to a user.
269 */
270 function community_tags_get_user_node_tags($uid, $nid) {
271 $tags = array();
272 $result = db_query("SELECT t.tid, t.name, c.uid, c.nid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid WHERE c.nid = %d AND c.uid = %d ORDER BY t.name", $nid, $uid);
273 while ($term = db_fetch_object($result)) {
274 $tags[$term->tid] = $term;
275 }
276
277 return $tags;
278 }
279
280 /**
281 * Save community_tags term associations and counts for a given node.
282 */
283 function community_tags_taxonomy_node_save($node, $terms, $is_owner, $uid) {
284 $community_tagged = variable_get('community_tags_vocabularies', array());
285 if (count($community_tagged) == 0) {
286 return;
287 }
288 $nid = $node->nid;
289
290 // If we're adding tags from the node add/edit page, then we want to delete
291 // any tags that aren't entered to allow admins to remove node tags.
292 // If not, we at least want the existing counts to update them.
293
294 $old_tags_by_uid = array();
295 $result = db_query('SELECT * FROM {community_tags} ttn WHERE nid = %d', $nid);
296 while ($old_tag_node = db_fetch_object($result)) {
297 $old_tags_by_uid[$old_tag_node->uid][$old_tag_node->tid] = $old_tag_node;
298 }
299
300 $tag_nodes = array();
301 $inserted_tids = array();
302
303 $tids = array();
304 // Free tagging vocabularies do not send their tids in the form,
305 // so we'll detect them here and process them independently.
306 if (isset($terms['tags'])) {
307 $typed_input = $terms['tags'];
308 unset($terms['tags']);
309
310 foreach ($typed_input as $vid => $vid_value) {
311 $typed_terms = is_array($vid_value) ? $vid_value : taxonomy_explode_tags($vid_value);
312 foreach ($typed_terms as $typed_term) {
313 // See if the term exists in the chosen vocabulary
314 // and return the tid, otherwise, add a new record.
315 $possibilities = taxonomy_get_term_by_name($typed_term);
316 $typed_term_tid = NULL; // tid match if any.
317 foreach ($possibilities as $possibility) {
318 if ($possibility->vid == $vid) {
319 $typed_term_tid = $possibility->tid;
320 }
321 }
322
323 if (!$typed_term_tid) {
324 $edit = array('vid' => $vid, 'name' => $typed_term);
325 $status = taxonomy_save_term($edit);
326 $typed_term_tid = $edit['tid'];
327
328 // Add only new terms to return value
329 $tids[$typed_term_tid] = $typed_term;
330 }
331
332 // If we already added this term in this cycle, skip this duplicate.
333 if (isset($inserted_tids[$typed_term_tid])) {
334 continue;
335 }
336
337 // If there was an existing tag by this user, load that data.
338 $tag_node = new StdClass();
339 $tag_node->tid = $typed_term_tid;
340 $tag_node->nid = $nid;
341 $tag_node->uid = $uid;
342 $tag_node->date = time();
343 $tag_node->vid = $node->vid;
344
345 // So we don't add this tag again on this run or re-insert the old tag for this user.
346 $inserted_tids[$typed_term_tid] = $typed_term_tid;
347 $tag_nodes[] = $tag_node;
348 }
349 }
350 }
351
352 // Re-insert the existing tags we haven't already added back in (incl. other users' tags).
353 foreach ($old_tags_by_uid as $old_uid => $old_tags) {
354 foreach ($old_tags as $old_tag) {
355 // If we're editing as the owner, only re-insert this other user's old tag
356 // when I've kept this tag in the node edit form.
357 if ($is_owner && isset($inserted_tids[$old_tag->tid]) && $old_tag->uid != $uid) {
358 $tag_nodes[] = $old_tag;
359 }
360 elseif (!$is_owner && $old_tag->uid != $uid) {
361 $tag_nodes[] = $old_tag;
362 }
363 }
364 }
365
366 // Get existing term-node associations to see if we need to re-insert a term-node
367 // relation for each old tag we're putting back in since we're acting after
368 // taxonomy.module has done its business.
369 $result = db_query('SELECT tid FROM {term_node} WHERE nid = %d', $nid);
370 $existing_term_nodes = array();
371 $add_term_nodes = array();
372 while ($term_node = db_fetch_object($result)) {
373 $existing_term_nodes[$term_node->tid] = TRUE;
374 }
375
376 // Re-insert tag-node-user associations and term-node if not existing.
377 db_lock_table('{community_tags}');
378 db_query('DELETE FROM {community_tags} WHERE nid = %d', $nid);
379 foreach ($tag_nodes as $tag_node) {
380 db_query('INSERT INTO {community_tags} (tid, nid, uid, date) VALUES (%d, %d, %d, %d)', $tag_node->tid, $tag_node->nid, $tag_node->uid, $tag_node->date);
381
382 // Remember to insert term-node relation into term_node table if it doesn't exist already.
383 if (!isset($existing_term_nodes[$tag_node->tid])) {
384 $add_term_nodes[] = $tag_node;
385 $existing_term_nodes[$tag_node->tid] = TRUE;
386 }
387 }
388 db_unlock_tables();
389
390 // Avoid locking term_node so we loop again.
391 foreach ($add_term_nodes as $tag_node) {
392 // Insert term-node relation into term_node table since it doesn't exist already.
393 db_query('INSERT INTO {term_node} (nid, tid, vid) VALUES (%d, %d, %d)', $tag_node->nid, $tag_node->tid, $tag_node->vid);
394 }
395
396 // Match real tags to community tags, if necessary (e.g. after quick tagging).
397 if (!$is_owner) {
398 $placeholders = implode(',', array_fill(0, count($community_tagged), '%d'));
399 $result = db_query('SELECT n.tid, n.nid FROM {term_node} n LEFT JOIN {community_tags} c ON n.tid = c.tid AND n.nid = c.nid INNER JOIN {term_data} d ON n.tid = d.tid WHERE n.nid = %d AND c.nid IS NULL AND d.vid IN ('. $placeholders .')', $nid, $community_tagged);
400 while ($tag = db_fetch_object($result)) {
401 db_query('DELETE FROM {term_node} WHERE nid = %d AND tid = %d', $tag->nid, $tag->tid);
402 }
403 }
404
405 }
406
407 /**
408 * Helper function for retrieving a query result to pass along to the Tagadelic
409 * functions prior to theming.
410 *
411 * @param $type
412 * The type of query to perform. Possible values:
413 * - node: get tag count for a given node.
414 * - type: get tag count for a given node type.
415 * - user: get tag count for a given user.
416 * - user_node: get tag count for a given user on a given node.
417 * - global: get tag count across entire site (default).
418 * @param $args
419 * An array of arguments that correspond to the result type:
420 * - If type is 'node', $arg1 is a node ID.
421 * - If type is 'type', $arg1 is a node type.
422 * - If type is 'user', $arg1 is a user ID.
423 * - If type is 'user_node', $arg1 is a user ID, and $arg2 is a node ID.
424 * - If type is 'global', neither $args are used.
425 * @param $limit
426 * Only display a certain number of tags.
427 * @return $result
428 * A database result set.
429 */
430 function _community_tags_get_tag_result($type = 'global', $limit = NULL, $arg1 = NULL, $arg2 = NULL) {
431 $sql = '';
432
433 switch ($type) {
434 case 'node':
435 $arg1 = (int)$arg1;
436 $arg2 = NULL;
437 $sql = "SELECT COUNT(c.tid) AS count, t.tid, t.name, t.vid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid WHERE c.nid = %d GROUP BY c.tid ORDER BY count DESC";
438 break;
439 case 'type':
440 $arg1 = (string)$arg1;
441 $arg2 = NULL;
442 $sql = "SELECT COUNT(c.tid) AS count, t.tid, t.name, t.vid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid INNER JOIN {node} n ON n.nid = c.nid WHERE n.type = '%s' GROUP BY c.tid ORDER BY count DESC";
443 break;
444 case 'user':
445 $arg1 = (int)$arg1;
446 $arg2 = NULL;
447 $sql = "SELECT COUNT(c.tid) AS count, t.tid, t.name, t.vid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid WHERE c.uid = %d GROUP BY c.tid ORDER BY count DESC";
448 break;
449 case 'user_node':
450 $arg1 = (int)$arg1;
451 $arg2 = (int)$arg2;
452 $sql = "SELECT COUNT(c.tid) AS count, t.tid, t.name, t.vid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid WHERE c.nid = %d AND c.uid = %d GROUP BY c.tid ORDER BY count DESC";
453 default:
454 $sql = "SELECT COUNT(c.tid) AS count, t.tid, t.name, t.vid FROM {term_data} t INNER JOIN {community_tags} c ON c.tid = t.tid GROUP BY c.tid ORDER BY count DESC";
455 }
456
457 if ($limit) {
458 $limit = (int)$limit;
459 return db_query_range($sql, $arg1, $arg2, 0, $limit);
460 }
461 else {
462 return db_query($sql, $arg1, $arg2);
463 }
464 }
465
466 /**
467 * Community tags callback for node view.
468 */
469 function community_tags_node_view($node, $inline = TRUE) {
470 global $user;
471 if (is_numeric($node)) {
472 $node = node_load($node);
473 }
474
475 if (!$inline) {
476 drupal_set_title(check_plain($node->title));
477 }
478
479 $cloud = community_tags_display('node', NULL, $node->nid);
480
481 $vid = array_shift(community_tags_vids_for_node($node));
482 $tags = community_tags_get_user_node_tags($user->uid, $node->nid);
483 $names = array();
484 $output = '';
485
486 module_load_include('inc', 'community_tags', 'community_tags.pages');
487
488 if (!count($tags)) {
489 // User has not yet added tags to this node yet. Show form.
490 $output .= drupal_get_form('community_tags_form', array('node'=> $node, 'cloud' => $cloud, 'nid' => $node->nid, 'vid' => $vid, 'tags' => NULL, 'inline' => $inline));
491 }
492 elseif (user_access('edit own tags')) {
493 // User has already tagged this node, but can edit their tags. Show form
494 // with the user's tags pre-populated.
495 $names = community_tags_flatten($tags);
496 $tags = taxonomy_implode_tags($tags);
497 $output .= drupal_get_form('community_tags_form', array('node'=> $node, 'cloud' => $cloud, 'nid' => $node->nid, 'vid' => $vid, 'tags' => $tags, 'inline' => $inline));
498 }
499 else {
500 // Sorry, no more adding tags for you!
501 $output .= '<p>'. t('You have already tagged this post. Your tags: ') . theme('community_tags', 'user_node', NULL, $user->uid, $node->nid) .'</p>';
502 }
503
504 drupal_add_js(array('communityTags' => array('tags' => $names, 'url' => url('community-tags/js/'. $node->nid), 'add' => t('Add'))), 'setting');
505
506 return $output;
507 }
508
509 /**
510 * Display community tags. See _community_tags_get_tag_result() for definitions
511 * of $type and the arguments.
512 */
513 function community_tags_display($type = 'global', $limit = NULL, $arg1 = NULL, $arg2 = NULL) {
514 $result = _community_tags_get_tag_result($type, $limit, $arg1, $arg2);
515 $weighted_tags = tagadelic_build_weighted_tags($result);
516 $sorted_tags = tagadelic_sort_tags($weighted_tags);
517 return theme('community_tags', $sorted_tags);
518 }
519
520 /**
521 * Theme function to display a list of community tags.
522 *
523 * @ingroup themeable
524 */
525 function theme_community_tags($tags) {
526 return '<div class="cloud">'. (count($tags) ? theme('tagadelic_weighted', $tags) : t('None')) .'</div>';
527 }
528
529 // Patch for Drupal 5.x. Remove for 6.x.
530 if (!function_exists('taxonomy_explode_tags')) {
531
532 /**
533 * Explode a string of given tags into an array.
534 */
535 function taxonomy_explode_tags($tags) {
536 // This regexp allows the following types of user input:
537 // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar
538 $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x';
539 preg_match_all($regexp, $tags, $matches);
540 $typed_tags = array_unique($matches[1]);
541
542 $tags = array();
543 foreach ($typed_tags as $tag) {
544 // If a user has escaped a term (to demonstrate that it is a group,
545 // or includes a comma or quote character), we remove the escape
546 // formatting so to save the term into the database as the user intends.
547 $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag)));
548 if ($tag != "") {
549 $tags[] = $tag;
550 }
551 }
552
553 return $tags;
554 }
555 }
556
557 /**
558 * Menu access callbacks
559 */
560 function _community_tags_menu_access() {
561 return user_access('access content') && user_access('tag content');
562 }
563
564 function _community_tags_tag_access($node) {
565 $vids = community_tags_vids_for_node($node);
566 return _community_tags_menu_access() && count($vids);
567 }
568
569 /**
570 * Helper function for the JS tagger.
571 */
572 function community_tags_flatten($tags) {
573 $names = array();
574 foreach ($tags as $tag) {
575 $names[] = $tag->name;
576 }
577 return $names;
578 }

  ViewVC Help
Powered by ViewVC 1.1.2