/[drupal]/drupal/modules/taxonomy/taxonomy.module
ViewVC logotype

Contents of /drupal/modules/taxonomy/taxonomy.module

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


Revision 1.534 - (show annotations) (download) (as text)
Mon Nov 2 06:34:36 2009 UTC (3 weeks, 5 days ago) by webchick
Branch: MAIN
Changes since 1.533: +4 -4 lines
File MIME type: text/x-php
#503834 by lisarex and dcor: Improved help text for taxonomy module's drag and drop behaviour.
1 <?php
2 // $Id: taxonomy.module,v 1.533 2009/11/01 14:05:32 dries Exp $
3
4 /**
5 * @file
6 * Enables the organization of content into categories.
7 */
8
9 /**
10 * Implement hook_permission().
11 */
12 function taxonomy_permission() {
13 $permissions = array(
14 'administer taxonomy' => array(
15 'title' => t('Administer taxonomy'),
16 'description' => t('Manage taxonomy vocabularies and terms.'),
17 ),
18 );
19 foreach (taxonomy_get_vocabularies() as $vocabulary) {
20 $permissions += array(
21 'edit terms in ' . $vocabulary->vid => array(
22 'title' => t('Edit terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
23 'description' => t('Edit terms in the %vocabulary vocabulary.', array('%vocabulary' => $vocabulary->name)),
24 ),
25 );
26 $permissions += array(
27 'delete terms in ' . $vocabulary->vid => array(
28 'title' => t('Delete terms in %vocabulary', array('%vocabulary' => $vocabulary->name)),
29 'description' => t('Delete terms in the %vocabulary vocabulary.', array('%vocabulary' => $vocabulary->name)),
30 ),
31 );
32 }
33 return $permissions;
34 }
35
36 /**
37 * Implement hook_entity_info().
38 */
39 function taxonomy_entity_info() {
40 $return = array(
41 'taxonomy_term' => array(
42 'label' => t('Taxonomy term'),
43 'controller class' => 'TaxonomyTermController',
44 'base table' => 'taxonomy_term_data',
45 'fieldable' => TRUE,
46 'object keys' => array(
47 'id' => 'tid',
48 'bundle' => 'vocabulary_machine_name',
49 ),
50 'bundle keys' => array(
51 'bundle' => 'machine_name',
52 ),
53 'bundles' => array(),
54 ),
55 );
56 foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
57 $return['taxonomy_term']['bundles'][$machine_name] = array(
58 'label' => $vocabulary->name,
59 'admin' => array(
60 'path' => 'admin/structure/taxonomy/%taxonomy_vocabulary',
61 'real path' => 'admin/structure/taxonomy/' . $vocabulary->vid,
62 'bundle argument' => 3,
63 'access arguments' => array('administer taxonomy'),
64 ),
65 );
66 }
67 $return['taxonomy_vocabulary'] = array(
68 'label' => t('Taxonomy vocabulary'),
69 'controller class' => 'TaxonomyVocabularyController',
70 'base table' => 'taxonomy_vocabulary',
71 'object keys' => array(
72 'id' => 'vid',
73 ),
74 'fieldable' => FALSE,
75 );
76
77 return $return;
78 }
79
80 /**
81 * Return nodes attached to a term across all field instances.
82 *
83 * This function requires taxonomy module to be maintaining its own tables,
84 * and will return an empty array if it is not. If using other field storage
85 * methods alternatives methods for listing terms will need to be used.
86 *
87 * @param $tid
88 * The term ID.
89 * @param $pager
90 * Boolean to indicate whether a pager should be used.
91 * @param $limit
92 * Integer. The maximum number of nodes to find.
93 * Set to FALSE for no limit.
94 * @order
95 * An array of fields and directions.
96 *
97 * @return
98 * An array of nids matching the query.
99 */
100 function taxonomy_select_nodes($tid, $pager = TRUE, $limit = FALSE, $order = array('t.sticky' => 'DESC', 't.created' => 'DESC')) {
101 if (!variable_get('taxonomy_maintain_index_table', TRUE)) {
102 return array();
103 }
104 $query = db_select('taxonomy_index', 't');
105 $query->addTag('node_access');
106 if ($pager) {
107 $count_query = clone $query;
108 $count_query->addExpression('COUNT(t.nid)');
109
110 $query = $query->extend('PagerDefault');
111 if ($limit !== FALSE) {
112 $query = $query->limit($limit);
113 }
114 $query->setCountQuery($count_query);
115 }
116 else {
117 if ($limit !== FALSE) {
118 $query->range(0, $limit);
119 }
120 }
121 $query->condition('tid', $tid);
122 $query->addField('t', 'nid');
123 $query->addField('t', 'tid');
124 foreach ($order as $field => $direction) {
125 $query->orderBy($field, $direction);
126 // ORDER BY fields need to be loaded too, assume they are in the form
127 // table_alias.name
128 list($table_alias, $name) = explode('.', $field);
129 $query->addField($table_alias, $name);
130 }
131 return $query->execute()->fetchCol();
132 }
133
134 /**
135 * Implement hook_field_build_modes();
136 *
137 * @TODO: build mode for display as a field (when attached to nodes etc.).
138 */
139 function taxonomy_field_build_modes($obj_type) {
140 $modes = array();
141 if ($obj_type == 'term') {
142 $modes = array(
143 'full' => t('Taxonomy term page'),
144 );
145 }
146 return $modes;
147 }
148
149 /**
150 * Implement hook_theme().
151 */
152 function taxonomy_theme() {
153 return array(
154 'taxonomy_overview_vocabularies' => array(
155 'render element' => 'form',
156 ),
157 'taxonomy_overview_terms' => array(
158 'render element' => 'form',
159 ),
160 'taxonomy_autocomplete' => array(
161 'render element' => 'element',
162 ),
163 );
164 }
165
166 /**
167 * Implement hook_menu().
168 */
169 function taxonomy_menu() {
170 $items['admin/structure/taxonomy'] = array(
171 'title' => 'Taxonomy',
172 'description' => 'Manage tagging, categorization, and classification of your content.',
173 'page callback' => 'drupal_get_form',
174 'page arguments' => array('taxonomy_overview_vocabularies'),
175 'access arguments' => array('administer taxonomy'),
176 'file' => 'taxonomy.admin.inc',
177 );
178 $items['admin/structure/taxonomy/list'] = array(
179 'title' => 'List',
180 'type' => MENU_DEFAULT_LOCAL_TASK,
181 'weight' => -10,
182 );
183 $items['admin/structure/taxonomy/add'] = array(
184 'title' => 'Add vocabulary',
185 'page callback' => 'drupal_get_form',
186 'page arguments' => array('taxonomy_form_vocabulary'),
187 'access arguments' => array('administer taxonomy'),
188 'type' => MENU_LOCAL_ACTION,
189 'file' => 'taxonomy.admin.inc',
190 );
191
192 $items['taxonomy/term/%taxonomy_term'] = array(
193 'title' => 'Taxonomy term',
194 'title callback' => 'taxonomy_term_title',
195 'title arguments' => array(2),
196 'page callback' => 'taxonomy_term_page',
197 'page arguments' => array(2),
198 'access arguments' => array('access content'),
199 'type' => MENU_CALLBACK,
200 'file' => 'taxonomy.pages.inc',
201 );
202 $items['taxonomy/term/%taxonomy_term/view'] = array(
203 'title' => 'View',
204 'type' => MENU_DEFAULT_LOCAL_TASK,
205 );
206 $items['taxonomy/term/%taxonomy_term/edit'] = array(
207 'title' => 'Edit term',
208 'title callback' => 'taxonomy_term_title',
209 'title arguments' => array(2),
210 'page callback' => 'drupal_get_form',
211 'page arguments' => array('taxonomy_form_term', 2),
212 'access callback' => 'taxonomy_term_edit_access',
213 'access arguments' => array(2),
214 'type' => MENU_LOCAL_TASK,
215 'weight' => 10,
216 'file' => 'taxonomy.admin.inc',
217 );
218 $items['taxonomy/term/%taxonomy_term/feed'] = array(
219 'title' => 'Taxonomy term',
220 'title callback' => 'taxonomy_term_title',
221 'title arguments' => array(2),
222 'page callback' => 'taxonomy_term_feed',
223 'page arguments' => array(2),
224 'access arguments' => array('access content'),
225 'type' => MENU_CALLBACK,
226 'file' => 'taxonomy.feeds.inc',
227 );
228 $items['taxonomy/autocomplete'] = array(
229 'title' => 'Autocomplete taxonomy',
230 'page callback' => 'taxonomy_autocomplete',
231 'access arguments' => array('access content'),
232 'type' => MENU_CALLBACK,
233 'file' => 'taxonomy.pages.inc',
234 );
235
236 $items['admin/structure/taxonomy/%taxonomy_vocabulary'] = array(
237 'title' => 'Vocabulary', // this is replaced by callback
238 'page callback' => 'drupal_get_form',
239 'page arguments' => array('taxonomy_form_vocabulary', 3),
240 'title callback' => 'taxonomy_admin_vocabulary_title_callback',
241 'title arguments' => array(3),
242 'access arguments' => array('administer taxonomy'),
243 'type' => MENU_CALLBACK,
244 'file' => 'taxonomy.admin.inc',
245 );
246 $items['admin/structure/taxonomy/%taxonomy_vocabulary/edit'] = array(
247 'title' => 'Edit vocabulary',
248 'type' => MENU_DEFAULT_LOCAL_TASK,
249 'weight' => -20,
250 );
251 $items['admin/structure/taxonomy/%taxonomy_vocabulary/list'] = array(
252 'title' => 'List terms',
253 'page callback' => 'drupal_get_form',
254 'page arguments' => array('taxonomy_overview_terms', 3),
255 'access arguments' => array('administer taxonomy'),
256 'type' => MENU_LOCAL_TASK,
257 'weight' => -10,
258 'file' => 'taxonomy.admin.inc',
259 );
260 $items['admin/structure/taxonomy/%taxonomy_vocabulary/list/add'] = array(
261 'title' => 'Add term',
262 'page callback' => 'drupal_get_form',
263 'page arguments' => array('taxonomy_form_term', array(), 3),
264 'access arguments' => array('administer taxonomy'),
265 'type' => MENU_LOCAL_ACTION,
266 'file' => 'taxonomy.admin.inc',
267 );
268
269 return $items;
270 }
271
272 /**
273 * Return edit access for a given term.
274 */
275 function taxonomy_term_edit_access($term) {
276 return user_access("edit terms in $term->vid") || user_access('administer taxonomy');
277 }
278
279 /**
280 * Return the vocabulary name given the vocabulary object.
281 */
282 function taxonomy_admin_vocabulary_title_callback($vocabulary) {
283 return check_plain($vocabulary->name);
284 }
285
286 /**
287 * Save a vocabulary given a vocabulary object.
288 */
289 function taxonomy_vocabulary_save($vocabulary) {
290
291 if (!empty($vocabulary->name)) {
292 // Prevent leading and trailing spaces in vocabulary names.
293 $vocabulary->name = trim($vocabulary->name);
294 }
295
296 if (!isset($vocabulary->module)) {
297 $vocabulary->module = 'taxonomy';
298 }
299
300 if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
301 $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
302 module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
303 }
304 elseif (empty($vocabulary->vid)) {
305 $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
306 field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name);
307 taxonomy_vocabulary_create_field($vocabulary);
308 module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
309 }
310
311 cache_clear_all();
312 entity_get_controller('taxonomy_vocabulary')->resetCache();
313
314 return $status;
315 }
316
317 /**
318 * Delete a vocabulary.
319 *
320 * @param $vid
321 * A vocabulary ID.
322 * @return
323 * Constant indicating items were deleted.
324 */
325 function taxonomy_vocabulary_delete($vid) {
326 $vocabulary = (array) taxonomy_vocabulary_load($vid);
327
328 db_delete('taxonomy_vocabulary')
329 ->condition('vid', $vid)
330 ->execute();
331 $result = db_query('SELECT tid FROM {taxonomy_term_data} WHERE vid = :vid', array(':vid' => $vid))->fetchCol();
332 foreach ($result as $tid) {
333 taxonomy_term_delete($tid);
334 }
335
336 field_attach_delete_bundle('taxonomy_term', $vocabulary['machine_name']);
337 module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary);
338
339 cache_clear_all();
340 entity_get_controller('taxonomy_vocabulary')->resetCache();
341
342 return SAVED_DELETED;
343 }
344
345 /**
346 * Dynamically check and update the hierarchy flag of a vocabulary.
347 *
348 * Checks the current parents of all terms in a vocabulary and updates the
349 * vocabularies hierarchy setting to the lowest possible level. A hierarchy with
350 * no parents in any of its terms will be given a hierarchy of 0. If terms
351 * contain at most a single parent, the vocabulary will be given a hierarchy of
352 * 1. If any term contain multiple parents, the vocabulary will be given a
353 * hierarchy of 2.
354 *
355 * @param $vocabulary
356 * A vocabulary object.
357 * @param $changed_term
358 * An array of the term structure that was updated.
359 */
360 function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
361 $tree = taxonomy_get_tree($vocabulary->vid);
362 $hierarchy = 0;
363 foreach ($tree as $term) {
364 // Update the changed term with the new parent value before comparison.
365 if ($term->tid == $changed_term['tid']) {
366 $term = (object)$changed_term;
367 $term->parents = $term->parent;
368 }
369 // Check this term's parent count.
370 if (count($term->parents) > 1) {
371 $hierarchy = 2;
372 break;
373 }
374 elseif (count($term->parents) == 1 && 0 !== array_shift($term->parents)) {
375 $hierarchy = 1;
376 }
377 }
378 if ($hierarchy != $vocabulary->hierarchy) {
379 $vocabulary->hierarchy = $hierarchy;
380 taxonomy_vocabulary_save($vocabulary);
381 }
382
383 return $hierarchy;
384 }
385
386 /**
387 * Create a default field when a vocabulary is created.
388 *
389 * @param $vocabulary
390 * A taxonomy vocabulary object.
391 */
392 function taxonomy_vocabulary_create_field($vocabulary) {
393 $field = array(
394 'field_name' => 'taxonomy_' . $vocabulary->machine_name,
395 'type' => 'taxonomy_term',
396 // Set cardinality to unlimited so that select
397 // and autocomplete widgets behave as normal.
398 'cardinality' => FIELD_CARDINALITY_UNLIMITED,
399 'settings' => array(
400 'allowed_values' => array(
401 array(
402 'vid' => $vocabulary->vid,
403 'parent' => 0,
404 ),
405 ),
406 ),
407 );
408 field_create_field($field);
409 }
410
411 /**
412 * Save a term object to the database.
413 *
414 * @param $term
415 * A term object.
416 * @return
417 * Status constant indicating if term was inserted or updated.
418 */
419 function taxonomy_term_save($term) {
420 if ($term->name) {
421 // Prevent leading and trailing spaces in term names.
422 $term->name = trim($term->name);
423 }
424 if (!isset($term->vocabulary_machine_name)) {
425 $vocabulary = taxonomy_vocabulary_load($term->vid);
426 $term->vocabulary_machine_name = $vocabulary->machine_name;
427 }
428
429 field_attach_presave('taxonomy_term', $term);
430
431 if (!empty($term->tid) && $term->name) {
432 $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
433 field_attach_update('taxonomy_term', $term);
434 module_invoke_all('taxonomy_term_update', $term);
435 }
436 else {
437 $status = drupal_write_record('taxonomy_term_data', $term);
438 _taxonomy_clean_field_cache($term);
439 field_attach_insert('taxonomy_term', $term);
440 module_invoke_all('taxonomy_term_insert', $term);
441 }
442
443 db_delete('taxonomy_term_hierarchy')
444 ->condition('tid', $term->tid)
445 ->execute();
446
447 if (!isset($term->parent) || empty($term->parent)) {
448 $term->parent = array(0);
449 }
450 if (!is_array($term->parent)) {
451 $term->parent = array($term->parent);
452 }
453 $query = db_insert('taxonomy_term_hierarchy')
454 ->fields(array('tid', 'parent'));
455 if (is_array($term->parent)) {
456 foreach ($term->parent as $parent) {
457 if (is_array($parent)) {
458 foreach ($parent as $tid) {
459 $query->values(array(
460 'tid' => $term->tid,
461 'parent' => $tid
462 ));
463 }
464 }
465 else {
466 $query->values(array(
467 'tid' => $term->tid,
468 'parent' => $parent
469 ));
470 }
471 }
472 }
473 $query->execute();
474
475 cache_clear_all();
476 taxonomy_terms_static_reset();
477
478 return $status;
479 }
480
481 /**
482 * Delete a term.
483 *
484 * @param $tid
485 * The term ID.
486 * @return
487 * Status constant indicating deletion.
488 */
489 function taxonomy_term_delete($tid) {
490 $tids = array($tid);
491 while ($tids) {
492 $children_tids = $orphans = array();
493 foreach ($tids as $tid) {
494 // See if any of the term's children are about to be become orphans:
495 if ($children = taxonomy_get_children($tid)) {
496 foreach ($children as $child) {
497 // If the term has multiple parents, we don't delete it.
498 $parents = taxonomy_get_parents($child->tid);
499 if (count($parents) == 1) {
500 $orphans[] = $child->tid;
501 }
502 }
503 }
504
505 $term = taxonomy_term_load($tid);
506
507 db_delete('taxonomy_term_data')
508 ->condition('tid', $tid)
509 ->execute();
510 db_delete('taxonomy_term_hierarchy')
511 ->condition('tid', $tid)
512 ->execute();
513
514 field_attach_delete('taxonomy_term', $term);
515 _taxonomy_clean_field_cache($term);
516 module_invoke_all('taxonomy_term_delete', $term);
517 }
518
519 $tids = $orphans;
520 }
521
522 cache_clear_all();
523 taxonomy_terms_static_reset();
524
525 return SAVED_DELETED;
526 }
527
528 /**
529 * Clear all static cache variables for terms..
530 */
531 function taxonomy_terms_static_reset() {
532 drupal_static_reset('taxonomy_term_count_nodes');
533 drupal_static_reset('taxonomy_get_tree');
534 entity_get_controller('taxonomy_term')->resetCache();
535 }
536
537 /**
538 * Generate a set of options for selecting a term from all vocabularies.
539 */
540 function taxonomy_form_all() {
541 $vocabularies = taxonomy_get_vocabularies();
542 $options = array();
543 foreach ($vocabularies as $vid => $vocabulary) {
544 $tree = taxonomy_get_tree($vid);
545 if ($tree && (count($tree) > 0)) {
546 $options[$vocabulary->name] = array();
547 foreach ($tree as $term) {
548 $options[$vocabulary->name][$term->tid] = str_repeat('-', $term->depth) . $term->name;
549 }
550 }
551 }
552 return $options;
553 }
554
555 /**
556 * Return an array of all vocabulary objects.
557 *
558 * @param $type
559 * If set, return only those vocabularies associated with this node type.
560 */
561 function taxonomy_get_vocabularies() {
562 return taxonomy_vocabulary_load_multiple(FALSE, array());
563 }
564
565 /**
566 * Get names for all taxonomy vocabularies.
567 *
568 * @return
569 * An array of vocabulary ids, names, machine names, keyed by machine name.
570 */
571 function taxonomy_vocabulary_get_names() {
572 $names = db_query('SELECT name, machine_name, vid FROM {taxonomy_vocabulary}')->fetchAllAssoc('machine_name');
573 return $names;
574 }
575
576 /**
577 * Find all parents of a given term ID.
578 */
579 function taxonomy_get_parents($tid, $key = 'tid') {
580 if ($tid) {
581 $query = db_select('taxonomy_term_data', 't');
582 $query->join('taxonomy_term_hierarchy', 'h', 'h.parent = t.tid');
583 $result = $query
584 ->addTag('translatable')
585 ->addTag('term_access')
586 ->fields('t')
587 ->condition('h.tid', $tid)
588 ->orderBy('weight')
589 ->orderBy('name')
590 ->execute();
591 $parents = array();
592 foreach ($result as $parent) {
593 $parents[$parent->$key] = $parent;
594 }
595 return $parents;
596 }
597 else {
598 return array();
599 }
600 }
601
602 /**
603 * Find all ancestors of a given term ID.
604 */
605 function taxonomy_get_parents_all($tid) {
606 $parents = array();
607 if ($term = taxonomy_term_load($tid)) {
608 $parents[] = $term;
609 $n = 0;
610 while ($parent = taxonomy_get_parents($parents[$n]->tid)) {
611 $parents = array_merge($parents, $parent);
612 $n++;
613 }
614 }
615 return $parents;
616 }
617
618 /**
619 * Find all children of a term ID.
620 */
621 function taxonomy_get_children($tid, $vid = 0, $key = 'tid') {
622 $query = db_select('taxonomy_term_data', 't');
623 $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
624 $query
625 ->addTag('translatable')
626 ->addTag('term_access')
627 ->fields('t')
628 ->condition('parent', $tid)
629 ->orderBy('weight')
630 ->orderBy('name');
631 if ($vid) {
632 $query->condition('t.vid', $vid);
633 }
634 $result = $query->execute();
635
636 $children = array();
637 foreach ($result as $term) {
638 $children[$term->$key] = $term;
639 }
640 return $children;
641 }
642
643 /**
644 * Create a hierarchical representation of a vocabulary.
645 *
646 * @param $vid
647 * Which vocabulary to generate the tree for.
648 * @param $parent
649 * The term ID under which to generate the tree. If 0, generate the tree
650 * for the entire vocabulary.
651 * @param $max_depth
652 * The number of levels of the tree to return. Leave NULL to return all levels.
653 * @param $depth
654 * Internal use only.
655 *
656 * @return
657 * An array of all term objects in the tree. Each term object is extended
658 * to have "depth" and "parents" attributes in addition to its normal ones.
659 * Results are statically cached.
660 */
661 function taxonomy_get_tree($vid, $parent = 0, $max_depth = NULL, $depth = -1) {
662 $children = &drupal_static(__FUNCTION__, array());
663 $parents = &drupal_static(__FUNCTION__ . 'parents', array());
664 $terms = &drupal_static(__FUNCTION__ . 'terms', array());
665
666 $depth++;
667
668 // We cache trees, so it's not CPU-intensive to call get_tree() on a term
669 // and its children, too.
670 if (!isset($children[$vid])) {
671 $children[$vid] = array();
672 $parents[$vid] = array();
673 $terms[$vid] = array();
674
675 $query = db_select('taxonomy_term_data', 't');
676 $query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
677 $result = $query
678 ->addTag('translatable')
679 ->addTag('term_access')
680 ->fields('t')
681 ->fields('h', array('parent'))
682 ->condition('t.vid', $vid)
683 ->orderBy('weight')
684 ->orderBy('name')
685 ->execute();
686 foreach ($result as $term) {
687 $children[$vid][$term->parent][] = $term->tid;
688 $parents[$vid][$term->tid][] = $term->parent;
689 $terms[$vid][$term->tid] = $term;
690 }
691 }
692
693 $max_depth = (is_null($max_depth)) ? count($children[$vid]) : $max_depth;
694 $tree = array();
695 if ($max_depth > $depth && !empty($children[$vid][$parent])) {
696 foreach ($children[$vid][$parent] as $child) {
697 $term = clone $terms[$vid][$child];
698 $term->depth = $depth;
699 // The "parent" attribute is not useful, as it would show one parent only.
700 unset($term->parent);
701 $term->parents = $parents[$vid][$child];
702 $tree[] = $term;
703 if (!empty($children[$vid][$child])) {
704 $tree = array_merge($tree, taxonomy_get_tree($vid, $child, $max_depth, $depth));
705 }
706 }
707 }
708
709 return $tree;
710 }
711
712 /**
713 * Try to map a string to an existing term, as for glossary use.
714 *
715 * Provides a case-insensitive and trimmed mapping, to maximize the
716 * likelihood of a successful match.
717 *
718 * @param name
719 * Name of the term to search for.
720 *
721 * @return
722 * An array of matching term objects.
723 */
724 function taxonomy_get_term_by_name($name) {
725 return taxonomy_term_load_multiple(array(), array('name' => trim($name)));
726 }
727
728 /**
729 * Controller class for taxonomy terms.
730 *
731 * This extends the DrupalDefaultEntityController class. Only alteration is
732 * that we match the condition on term name case-independently.
733 */
734 class TaxonomyTermController extends DrupalDefaultEntityController {
735 protected $type;
736 public function load($ids = array(), $conditions = array()) {
737 if (isset($conditions['type'])) {
738 $this->type = $conditions['type'];
739 unset($conditions['type']);
740 }
741 return parent::load($ids, $conditions);
742 }
743
744 protected function buildQuery() {
745 parent::buildQuery();
746 $this->query->addTag('translatable');
747 $this->query->addTag('term_access');
748 // When name is passed as a condition use LIKE.
749 if (isset($this->conditions['name'])) {
750 $conditions = &$this->query->conditions();
751 foreach ($conditions as $key => $condition) {
752 if ($condition['field'] == 'base.name') {
753 $conditions[$key]['operator'] = 'LIKE';
754 }
755 }
756 }
757 // Add the machine name field from the {taxonomy_vocabulary} table.
758 $this->query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
759 $this->query->addField('v', 'machine_name', 'vocabulary_machine_name');
760 }
761
762 protected function cacheGet($ids, $conditions = array()) {
763 $terms = parent::cacheGet($ids, $conditions);
764 // Name matching is case insensitive, note that with some collations
765 // LOWER() and drupal_strtolower() may return different results.
766 foreach ($terms as $term) {
767 $term_values = (array) $term;
768 if (isset($this->conditions['name']) && drupal_strtolower($this->conditions['name'] != drupal_strtolower($term_values['name']))) {
769 unset($terms[$term->tid]);
770 }
771 }
772 return $terms;
773 }
774 }
775
776 /**
777 * Controller class for taxonomy vocabularies.
778 *
779 * This extends the DrupalDefaultEntityController class, adding required
780 * special handling for taxonomy vocabulary objects.
781 */
782 class TaxonomyVocabularyController extends DrupalDefaultEntityController {
783 protected function buildQuery() {
784 parent::buildQuery();
785 $this->query->addTag('translatable');
786 $this->query->orderBy('base.weight');
787 $this->query->orderBy('base.name');
788 }
789
790 protected function attachLoad(&$records) {
791 foreach ($records as $record) {
792 // If no node types are associated with a vocabulary, the LEFT JOIN will
793 // return a NULL value for type.
794 $queried_vocabularies[$record->vid] = $record;
795 }
796 $records = $queried_vocabularies;
797 parent::attachLoad($records);
798 }
799 }
800
801 /**
802 * Load multiple taxonomy terms based on certain conditions.
803 *
804 * This function should be used whenever you need to load more than one term
805 * from the database. Terms are loaded into memory and will not require
806 * database access if loaded again during the same page request.
807 *
808 * @see entity_load()
809 *
810 * @param $tids
811 * An array of taxonomy term IDs.
812 * @param $conditions
813 * An array of conditions to add to the query.
814 *
815 * @return
816 * An array of term objects, indexed by tid.
817 */
818 function taxonomy_term_load_multiple($tids = array(), $conditions = array()) {
819 return entity_load('taxonomy_term', $tids, $conditions);
820 }
821
822 /**
823 * Load multiple taxonomy vocabularies based on certain conditions.
824 *
825 * This function should be used whenever you need to load more than one
826 * vocabulary from the database. Terms are loaded into memory and will not
827 * require database access if loaded again during the same page request.
828 *
829 * @see entity_load()
830 *
831 * @param $vids
832 * An array of taxonomy vocabulary IDs, or FALSE to load all vocabularies.
833 * @param $conditions
834 * An array of conditions to add to the query.
835 *
836 * @return
837 * An array of vocabulary objects, indexed by vid.
838 */
839 function taxonomy_vocabulary_load_multiple($vids = array(), $conditions = array()) {
840 return entity_load('taxonomy_vocabulary', $vids, $conditions);
841 }
842
843 /**
844 * Return the vocabulary object matching a vocabulary ID.
845 *
846 * @param $vid
847 * The vocabulary's ID.
848 *
849 * @return
850 * The vocabulary object with all of its metadata, if exists, FALSE otherwise.
851 * Results are statically cached.
852 */
853 function taxonomy_vocabulary_load($vid) {
854 return reset(taxonomy_vocabulary_load_multiple(array($vid)));
855 }
856
857 /**
858 * Return the term object matching a term ID.
859 *
860 * @param $tid
861 * A term's ID
862 *
863 * @return
864 * A term object. Results are statically cached.
865 */
866 function taxonomy_term_load($tid) {
867 if (!is_numeric($tid)) {
868 return FALSE;
869 }
870 $term = taxonomy_term_load_multiple(array($tid), array());
871 return $term ? $term[$tid] : FALSE;
872 }
873
874 /**
875 * Implement hook_help().
876 */
877 function taxonomy_help($path, $arg) {
878 switch ($path) {
879 case 'admin/help#taxonomy':
880 $output = '<p>' . t('The taxonomy module allows you to categorize content using various systems of classification. Free-tagging vocabularies are created by users on the fly when they submit posts (as commonly found in blogs and social bookmarking applications). Controlled vocabularies allow for administrator-defined short lists of terms as well as complex hierarchies with multiple relationships between different terms. These methods can be applied to different content types and combined together to create a powerful and flexible method of classifying and presenting your content.') . '</p>';
881 $output .= '<p>' . t('For example, when creating a recipe site, you might want to classify posts by both the type of meal and preparation time. A vocabulary for each allows you to categorize using each criteria independently instead of creating a tag for every possible combination.') . '</p>';
882 $output .= '<p>' . t('Type of Meal: <em>Appetizer, Main Course, Salad, Dessert</em>') . '</p>';
883 $output .= '<p>' . t('Preparation Time: <em>0-30mins, 30-60mins, 1-2 hrs, 2hrs+</em>') . '</p>';
884 $output .= '<p>' . t("Each taxonomy term (often called a 'category' or 'tag' in other systems) automatically provides lists of posts and a corresponding RSS feed. These taxonomy/term URLs can be manipulated to generate AND and OR lists of posts classified with terms. In our recipe site example, it then becomes easy to create pages displaying 'Main courses', '30 minute recipes', or '30 minute main courses and appetizers' by using terms on their own or in combination with others. There are a significant number of contributed modules which you to alter and extend the behavior of the core module for both display and organization of terms.") . '</p>';
885 $output .= '<p>' . t("Terms can also be organized in parent/child relationships from the admin interface. An example would be a vocabulary grouping countries under their parent geo-political regions. The taxonomy module also enables advanced implementations of hierarchy, for example placing Turkey in both the 'Middle East' and 'Europe'.") . '</p>';
886 $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@taxonomy">Taxonomy module</a>.', array('@taxonomy' => 'http://drupal.org/handbook/modules/taxonomy/')) . '</p>';
887 return $output;
888 case 'admin/structure/taxonomy':
889 $output = '<p>' . t('Configure the vocabularies and terms for your site.') . '</p>';
890 return $output;
891 case 'admin/structure/taxonomy/%/list':
892 $vocabulary = taxonomy_vocabulary_load($arg[3]);
893 switch ($vocabulary->hierarchy) {
894 case 0:
895 return '<p>' . t('You can reorganize the terms in %capital_name using their drag and drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
896 case 1:
897 return '<p>' . t('%capital_name contains terms grouped with parent terms. You can reorganize the terms in %capital_name using their drag and drop handles.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) . '</p>';
898 case 2:
899 return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag and drop support by editing each term to include only a single parent.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) . '</p>';
900 }
901 case 'admin/structure/taxonomy/add':
902 return '<p>' . t('Define how your vocabulary will be presented to administrators and users, and which content types to categorize with it. Tags allows users to create terms when submitting posts by typing a comma separated list. Otherwise terms are chosen from a select list and can only be created by users with the "administer taxonomy" permission.') . '</p>';
903 }
904 }
905
906 /**
907 * Helper function for array_map purposes.
908 */
909 function _taxonomy_get_tid_from_term($term) {
910 return $term->tid;
911 }
912
913 /**
914 * Implode a list of tags of a certain vocabulary into a string.
915 */
916 function taxonomy_implode_tags($tags, $vid = NULL) {
917 $typed_tags = array();
918 foreach ($tags as $tag) {
919 // Extract terms belonging to the vocabulary in question.
920 if (is_null($vid) || $tag->vid == $vid) {
921
922 // Commas and quotes in tag names are special cases, so encode 'em.
923 if (strpos($tag->name, ',') !== FALSE || strpos($tag->name, '"') !== FALSE) {
924 $tag->name = '"' . str_replace('"', '""', $tag->name) . '"';
925 }
926
927 $typed_tags[] = $tag->name;
928 }
929 }
930 return implode(', ', $typed_tags);
931 }
932
933 /**
934 * Implement hook_field_info().
935 *
936 * Field settings:
937 * - allowed_values: a list array of one or more vocabulary trees:
938 * - vid: a vocabulary ID.
939 * - parent: a term ID of a term whose children are allowed. This should be
940 * '0' if all terms in a vocabulary are allowed. The allowed values do not
941 * include the parent term.
942 *
943 */
944 function taxonomy_field_info() {
945 return array(
946 'taxonomy_term' => array(
947 'label' => t('Taxonomy term'),
948 'description' => t('This field stores a reference to a taxonomy term.'),
949 'default_widget' => 'options_select',
950 'default_formatter' => 'taxonomy_term_link',
951 'settings' => array(
952 'allowed_values' => array(
953 array(
954 'vid' => '0',
955 'parent' => '0',
956 ),
957 ),
958 ),
959 ),
960 );
961 }
962
963 /**
964 * Implement hook_field_widget_info().
965 *
966 * We need custom handling of multiple values because we need
967 * to combine them into a options list rather than display
968 * cardinality elements. We will use the field module's default
969 * handling for default values.
970 *
971 * Callbacks can be omitted if default handing is used.
972 * They're included here just so this module can be used
973 * as an example for custom modules that might do things
974 * differently.
975 */
976 function taxonomy_field_widget_info() {
977 return array(
978 'taxonomy_autocomplete' => array(
979 'label' => t('Autocomplete term widget (tagging)'),
980 'field types' => array('taxonomy_term'),
981 'settings' => array(
982 'size' => 60,
983 'autocomplete_path' => 'taxonomy/autocomplete',
984 ),
985 'behaviors' => array(
986 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
987 ),
988 ),
989 );
990 }
991
992 /**
993 * Implement hook_field_widget_info_alter().
994 */
995 function taxonomy_field_widget_info_alter(&$info) {
996 $info['options_select']['field types'][] = 'taxonomy_term';
997 $info['options_buttons']['field types'][] = 'taxonomy_term';
998 }
999
1000 /**
1001 * Implement hook_field_schema().
1002 */
1003 function taxonomy_field_schema($field) {
1004 return array(
1005 'columns' => array(
1006 'value' => array(
1007 'type' => 'int',
1008 'unsigned' => TRUE,
1009 'not null' => FALSE,
1010 ),
1011 ),
1012 'indexes' => array(
1013 'value' => array('value'),
1014 ),
1015 );
1016 }
1017
1018 /**
1019 * Implement hook_field_validate().
1020 *
1021 * Possible error codes:
1022 * - 'taxonomy_term_illegal_value': The value is not part of the list of allowed values.
1023 */
1024 function taxonomy_field_validate($obj_type, $object, $field, $instance, $langcode, $items, &$errors) {
1025 $allowed_values = taxonomy_allowed_values($field);
1026 $widget = field_info_widget_types($instance['widget']['type']);
1027
1028 // Check we don't exceed the allowed number of values for widgets with custom
1029 // behavior for multiple values (taxonomy_autocomplete widget).
1030 if ($widget['behaviors']['multiple values'] == FIELD_BEHAVIOR_CUSTOM && $field['cardinality'] >= 2) {
1031 if (count($items) > $field['cardinality']) {
1032 $errors[$field['field_name']][0][] = array(
1033 'error' => 'taxonomy_term_illegal_value',
1034 'message' => t('%name: this field cannot hold more that @count values.', array('%name' => t($instance['label']), '@count' => $field['cardinality'])),
1035 );
1036 }
1037 }
1038
1039 foreach ($items as $delta => $item) {
1040 if (!empty($item['value'])) {
1041 if (!isset($allowed_values[$item['value']])) {
1042 $errors[$field['field_name']][$delta][] = array(
1043 'error' => 'taxonomy_term_illegal_value',
1044 'message' => t('%name: illegal value.', array('%name' => t($instance['label']))),
1045 );
1046 }
1047 }
1048 }
1049 }
1050
1051 /**
1052 * Implement hook_field_is_empty().
1053 */
1054 function taxonomy_field_is_empty($item, $field) {
1055 if (!is_array($item) || (empty($item['value']) && (string) $item['value'] !== '0')) {
1056 return TRUE;
1057 }
1058 return FALSE;
1059 }
1060
1061 /**
1062 * Implement hook_field_formatter_info().
1063 */
1064 function taxonomy_field_formatter_info() {
1065 return array(
1066 'taxonomy_term_link' => array(
1067 'label' => t('Link'),
1068 'field types' => array('taxonomy_term'),
1069 'behaviors' => array(
1070 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1071 ),
1072 ),
1073 'taxonomy_term_plain' => array(
1074 'label' => t('Plain text'),
1075 'field types' => array('taxonomy_term'),
1076 'behaviors' => array(
1077 'multiple values' => FIELD_BEHAVIOR_DEFAULT,
1078 ),
1079 ),
1080 );
1081 }
1082
1083 /**
1084 * Theme function for 'link' term field formatter.
1085 */
1086 function theme_field_formatter_taxonomy_term_link($variables) {
1087 $term = $variables['element']['#item']['taxonomy_term'];
1088 $attributes = empty($variables['link_options']) ? array() : $variables['link_options'];
1089 return l($term->name, 'taxonomy/term/' . $term->tid, $attributes);
1090 }
1091
1092 /**
1093 * Theme function for 'plain' term field formatter.
1094 */
1095 function theme_field_formatter_taxonomy_term_plain($variables) {
1096 $term = $variables['element']['#item']['taxonomy_term'];
1097 return check_plain($term->name);
1098 }
1099
1100 /**
1101 * Create an array of the allowed values for this field.
1102 *
1103 * Call the field's allowed_values function to retrieve the allowed
1104 * values array.
1105 *
1106 * @see _taxonomy_term_select()
1107 */
1108 function taxonomy_allowed_values($field) {
1109 $options = array();
1110 foreach ($field['settings']['allowed_values'] as $tree) {
1111 $terms = taxonomy_get_tree($tree['vid'], $tree['parent']);
1112 if ($terms) {
1113 foreach ($terms as $term) {
1114 $options[$term->tid] = str_repeat('-', $term->depth) . $term->name;
1115 }
1116 }
1117 }
1118 return $options;
1119 }
1120
1121 /**
1122 * Implement hook_field_load().
1123 *
1124 * This preloads all taxonomy terms for multiple loaded objects at once and
1125 * unsets values for invalid terms that do not exist.
1126 */
1127 function taxonomy_field_formatter_prepare_view($obj_type, $objects, $field, $instances, $langcode, &$items, $age) {
1128 $tids = array();
1129
1130 // Collect every possible term attached to any of the fieldable entities.
1131 foreach ($objects as $id => $object) {
1132 foreach ($items[$id] as $delta => $item) {
1133 // Force the array key to prevent duplicates.
1134 $tids[$item['value']] = $item['value'];
1135 }
1136 }
1137 if ($tids) {
1138 $terms = taxonomy_term_load_multiple($tids);
1139
1140 // Iterate through the fieldable entities again to attach the loaded term data.
1141 foreach ($objects as $id => $object) {
1142 foreach ($items[$id] as $delta => $item) {
1143 // Check whether the taxonomy term field instance value could be loaded.
1144 if (isset($terms[$item['value']])) {
1145 // Replace the instance value with the term data.
1146 $items[$id][$delta]['taxonomy_term'] = $terms[$item['value']];
1147 }
1148 // Otherwise, unset the instance value, since the term does not exist.
1149 else {
1150 unset($items[$id][$delta]);
1151 }
1152 }
1153 }
1154 }
1155 }
1156
1157 /**
1158 * Helper function that clears field cache when terms are updated or deleted
1159 */
1160 function _taxonomy_clean_field_cache($term) {
1161 $cids = array();
1162
1163 // Determine object types that are not cacheable.
1164 $obj_types = array();
1165 foreach (entity_get_info() as $obj_type => $info) {
1166 if (isset($info['cacheable']) && !$info['cacheable']) {
1167 $obj_types[] = $obj_type;
1168 }
1169 }
1170
1171 // Load info for all taxonomy term fields.
1172 $fields = field_read_fields(array('type' => 'taxonomy_term'));
1173 foreach ($fields as $field_name => $field) {
1174
1175 // Assemble an array of vocabulary IDs that are used in this field.
1176 foreach ($field['settings']['allowed_values'] as $tree) {
1177 $vids[$tree['vid']] = $tree['vid'];
1178 }
1179
1180 // Check this term's vocabulary against those used for the field's options.
1181 if (in_array($term->vid, $vids)) {
1182 $conditions = array(array('value', $term->tid));
1183 if ($obj_types) {
1184 $conditions[] = array('type', $obj_types, 'NOT IN');
1185 }
1186 $results = field_attach_query($field['id'], $conditions, array('limit' => FIELD_QUERY_NO_LIMIT));
1187 foreach ($results as $obj_type => $objects) {
1188 foreach (array_keys($objects) as $id) {
1189 $cids[] = "field:$obj_type:$id";
1190 }
1191 }
1192 }
1193 }
1194 if ($cids) {
1195 cache_clear_all($cids, 'cache_field');
1196 }
1197 }
1198
1199 /**
1200 * Title callback for term pages.
1201 *
1202 * @param $term
1203 * A term object.
1204 * @return
1205 * The term name to be used as the page title.
1206 */
1207 function taxonomy_term_title($term) {
1208 return check_plain($term->name);
1209 }
1210
1211 /**
1212 * Implement hook_field_widget().
1213 */
1214 function taxonomy_field_widget(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
1215 $element += array(
1216 '#type' => $instance['widget']['type'],
1217 '#default_value' => !empty($items) ? $items : array(),
1218 );
1219 return $element;
1220 }
1221
1222 /**